1. 从项目需求到器件选型:为什么是DS1821?
最近在做一个基于英飞凌XMC系列MCU的嵌入式项目,其中一个核心功能是环境温度监测。在选型温度传感器时,我几乎没怎么犹豫就圈定了DS1821。可能有人会问,市面上温度传感器那么多,从模拟输出的热敏电阻、PT100到数字输出的各种IC,为什么偏偏是它?尤其是在ESP32这类自带温度传感功能的MCU大行其道的今天,为一个专门的MCU外接一个温度传感器,是不是有点“多此一举”?
我的回答是:这完全取决于你的应用场景和精度要求。ESP32的内部温度传感器,其主要设计目的是监测芯片本身的结温,用于过热保护或性能调节,其测量环境温度的精度和稳定性通常难以满足工业或高可靠性场合的需求。而像PT100这类高精度模拟传感器,虽然性能优异,但需要额外的信号调理电路(如恒流源、高精度运放)和复杂的校准过程,对于很多中小型项目来说,成本和复杂度都偏高。
DS1821恰好卡在了一个非常舒服的位置上。它是一款单总线数字温度传感器,这意味着你只需要一根数据线(加上电源和地)就能与MCU通信,极大地简化了布线。其测温范围是-55°C到+125°C,分辨率为0.5°C,对于绝大多数工业控制、环境监测、设备状态监控等应用来说,这个精度已经绰绰有余。更重要的是,它内部集成了温度传感、A/D转换和单总线接口,MCU只需要通过简单的数字协议就能读取温度值,省去了模拟电路设计和抗干扰处理的麻烦。在XMC这种资源丰富、外设强大的MCU上,实现单总线通信更是小菜一碟。
所以,当你的项目需要在有限的IO口、简单的电路下,实现一个可靠、够用的温度监测功能时,DS1821是一个非常务实且经典的选择。它没有DS18B20那么高的分辨率(DS18B20可达0.0625°C),但协议更简单,在某些对时序要求不那么严苛或资源受限的系统中,反而更容易稳定驱动。
2. 硬件连接与电路设计要点
确定了DS1821,接下来就是把它和XMC MCU连接起来。硬件连接看似简单,但有几个细节不注意,很容易导致通信失败或数据不稳。
2.1 经典的单总线电路
DS1821遵循Dallas单总线协议。最基本的连接方式如下:
- VDD引脚:接+3.3V或+5V电源。DS1821的工作电压范围是3.0V至5.5V,XMC的IO口通常是3.3V电平,因此推荐使用3.3V供电,这样可以避免电平转换的问题。
- GND引脚:接系统地。
- DQ引脚(数据线):接XMC的任意一个GPIO引脚。这个引脚需要配置为开漏输出模式,并且必须连接一个4.7kΩ的上拉电阻到VDD。这是单总线协议的强制要求,用于在总线空闲时将其拉至高电平,并为器件提供通信所需的强上拉。
注意:很多初学者会忘记这个上拉电阻,或者随意用一个10kΩ的电阻代替。4.7kΩ是经过验证的推荐值,它能保证在总线电容和传输距离下,上升时间满足协议要求。电阻过大可能导致上升沿太慢,通信失败;电阻过小则会增加功耗。
2.2 电源模式的选择与寄生供电
DS1821支持两种供电模式:外部供电和寄生供电。上面描述的是标准的外部供电模式。寄生供电模式则更为“极致”,只需要将VDD引脚接地,DQ引脚通过上拉电阻提供电源和通信。这种方式可以节省一根电源线,适用于传感器分布很远的场合。
但是,我强烈不建议在初次使用或对可靠性要求高的项目中使用寄生供电。原因在于,当DS1821进行温度转换或执行EEPROM写入操作时,瞬时电流需求会比较大(最高可达1.5mA),仅靠单总线上的弱上拉可能无法满足,会导致芯片复位或操作失败。虽然协议里提供了“强上拉”的时序(在发起温度转换命令后,由MCU将DQ线强制拉至高电平一段时间),但这增加了软件时序的复杂性,且对总线长度和负载更敏感。
对于XMC项目,我们通常有充足的电源和IO,因此优先采用外部供电模式。这样电路最简单,通信也最稳定。如果你的应用场景必须使用寄生供电,那么务必仔细阅读数据手册中关于强上拉时序的部分,并在代码中严格实现。
2.3 布局与抗干扰考虑
虽然DS1821是数字器件,但良好的PCB布局依然重要:
- 去耦电容:在DS1821的VDD和GND引脚之间,尽可能靠近芯片放置一个0.1μF的陶瓷去耦电容。这有助于滤除电源噪声,尤其是在有其他数字或模拟电路共存的板子上。
- 走线长度:单总线对走线长度比较敏感。理论上,在标准速度下,可靠通信距离可达几十米。但在实际PCB上,如果传感器离MCU较远(比如超过30厘米),建议适当减小上拉电阻值(例如用3.3kΩ),并检查走线是否靠近噪声源(如电机驱动线、开关电源)。
- ESD保护:如果传感器需要通过线缆连接到外部环境,在DQ线上串联一个小的电阻(如100Ω)并在对地接一个TVS管,可以有效防止静电损坏。
3. XMC端单总线驱动代码实现
硬件搭好了,接下来就是让XMC的“大脑”学会和DS1821“说话”。单总线协议是一种严格的基于时序的通信方式,核心在于MCU要能精确地产生特定的高低电平脉冲,并检测DS1821的响应。
3.1 底层时序函数:精确的“微操作”
单总线协议的所有命令和数据交换,都建立在三种基本时序之上:复位脉冲、写时序(写0,写1)和读时序。由于协议对时间要求非常严格(微秒级),我们通常需要用精确的延时函数来实现。在XMC上,有几种方法:
- 空循环延时:最简单,但精度受编译器优化和CPU频率影响大,不推荐用于产品。
- SysTick定时器延时:利用ARM Cortex-M内核的SysTick定时器,可以产生高精度的微秒级延时。这是最通用和可靠的方法。
- 通用定时器延时:使用XMC的CCU4或CCU8定时器模块,精度最高,但配置稍复杂。
这里以SysTick为例,展示如何实现一个微秒延时函数,并在此基础上构建最底层的时序操作:
// 假设系统主频为120MHz #define CPU_FREQ_MHZ 120 // 初始化SysTick,用于微秒延时 void BSP_Delay_Init(void) { SysTick->LOAD = (CPU_FREQ_MHZ) - 1; // 设置重装载值,每计数一次为1/CPU_FREQ_MHZ 微秒 SysTick->VAL = 0; // 清空当前值 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; // 使用处理器时钟,使能 } // 微秒级延时函数 void delay_us(uint32_t us) { uint32_t start_tick = SysTick->VAL; uint32_t ticks_needed = us * CPU_FREQ_MHZ; uint32_t elapsed_ticks; if (ticks_needed > SysTick->LOAD) { // 如果需要延时的ticks超过重装载值,需要特殊处理(此处简化) // 实际应用中可能需要循环 } do { elapsed_ticks = (start_tick - SysTick->VAL) & SysTick->LOAD; } while (elapsed_ticks < ticks_needed); } // 定义DQ引脚(例如P1.0) #define DS1821_DQ_PORT XMC_GPIO_PORT1 #define DS1821_DQ_PIN 0 // 将DQ引脚配置为开漏输出,初始高电平 void DS1821_DQ_Init_Output(void) { XMC_GPIO_SetMode(DS1821_DQ_PORT, DS1821_DQ_PIN, XMC_GPIO_MODE_OUTPUT_OPEN_DRAIN); XMC_GPIO_SetOutputHigh(DS1821_DQ_PORT, DS1821_DQ_PIN); } // 将DQ引脚配置为输入(带上拉) void DS1821_DQ_Init_Input(void) { XMC_GPIO_SetMode(DS1821_DQ_PORT, DS1821_DQ_PIN, XMC_GPIO_MODE_INPUT_PULL_UP); } // 产生复位脉冲(>480us的低电平),并检测存在脉冲(60-240us的低电平) uint8_t DS1821_Reset(void) { uint8_t presence = 0; DS1821_DQ_Init_Output(); XMC_GPIO_SetOutputLow(DS1821_DQ_PORT, DS1821_DQ_PIN); // 主机拉低DQ delay_us(480); // 保持低电平至少480us XMC_GPIO_SetOutputHigh(DS1821_DQ_PORT, DS1821_DQ_PIN); // 释放总线(变高) DS1821_DQ_Init_Input(); // 切换为输入模式,准备检测 delay_us(60); // 等待15-60us后采样 if (!XMC_GPIO_GetInput(DS1821_DQ_PORT, DS1821_DQ_PIN)) { presence = 1; // 检测到低电平,表示有器件响应 } delay_us(420); // 等待存在脉冲结束 return presence; // 返回1表示有器件在线 } // 向DS1821写一位数据 void DS1821_WriteBit(uint8_t bit) { DS1821_DQ_Init_Output(); XMC_GPIO_SetOutputLow(DS1821_DQ_PORT, DS1821_DQ_PIN); // 主机拉低启动写时序 delay_us(1); // 保持低电平至少1us if (bit) { XMC_GPIO_SetOutputHigh(DS1821_DQ_PORT, DS1821_DQ_PIN); // 如果是写1,则在1us后释放总线 } // 如果是写0,则继续保持低电平 delay_us(60); // 整个写时序至少持续60us XMC_GPIO_SetOutputHigh(DS1821_DQ_PORT, DS1821_DQ_PIN); // 释放总线 // 写时序后需要至少1us的恢复时间 } // 向DS1821写一个字节(调用8次WriteBit) void DS1821_WriteByte(uint8_t byte) { uint8_t i; for (i = 0; i < 8; i++) { DS1821_WriteBit(byte & 0x01); byte >>= 1; } } // 从DS1821读一位数据 uint8_t DS1821_ReadBit(void) { uint8_t bit = 0; DS1821_DQ_Init_Output(); XMC_GPIO_SetOutputLow(DS1821_DQ_PORT, DS1821_DQ_PIN); // 主机拉低启动读时序 delay_us(1); // 保持低电平至少1us XMC_GPIO_SetOutputHigh(DS1821_DQ_PORT, DS1821_DQ_PIN); // 释放总线 DS1821_DQ_Init_Input(); // 切换为输入模式 delay_us(10); // 等待10-15us后采样 if (XMC_GPIO_GetInput(DS1821_DQ_PORT, DS1821_DQ_PIN)) { bit = 1; } delay_us(50); // 完成读时序 return bit; } // 从DS1821读一个字节(调用8次ReadBit) uint8_t DS1821_ReadByte(void) { uint8_t i, byte = 0; for (i = 0; i < 8; i++) { byte >>= 1; if (DS1821_ReadBit()) { byte |= 0x80; } } return byte; }3.2 高层命令封装:让通信更直观
有了底层的位读写和复位函数,我们就可以根据DS1821的命令集来封装更易用的函数了。DS1821的常用命令不多,核心流程是:复位 -> 发送命令 -> 读写数据。
// DS1821命令定义 #define DS1821_CMD_CONVERT_T 0x44 // 开始温度转换 #define DS1821_CMD_READ_SCRATCHPAD 0xBE // 读取暂存器(包含温度值) #define DS1821_CMD_WRITE_SCRATCHPAD 0x4E // 写入暂存器(配置TH, TL) #define DS1821_CMD_COPY_SCRATCHPAD 0x48 // 将暂存器内容拷贝到EEPROM #define DS1821_CMD_RECALL_EEPROM 0xB8 // 将EEPROM内容召回至暂存器 #define DS1821_CMD_READ_POWER_SUPPLY 0xB4 // 读电源模式 // 启动一次温度转换 void DS1821_StartConversion(void) { if (DS1821_Reset()) { DS1821_WriteByte(DS1821_CMD_CONVERT_T); // 对于外部供电,此后MCU可以去做其他事情,稍后再来读结果 // 对于寄生供电,此处可能需要立即提供强上拉 } } // 读取温度值(原始数据,需转换) int16_t DS1821_ReadTemperatureRaw(void) { uint8_t temp_lsb, temp_msb; int16_t raw_temp = 0; if (DS1821_Reset()) { DS1821_WriteByte(DS1821_CMD_READ_SCRATCHPAD); temp_lsb = DS1821_ReadByte(); // 温度低字节 temp_msb = DS1821_ReadByte(); // 温度高字节(符号位) // 可以继续读其他字节(TH, TL, 配置等),但这里我们只关心温度 raw_temp = (temp_msb << 8) | temp_lsb; } return raw_temp; // 返回16位有符号整数 } // 将原始温度值转换为摄氏度(浮点数) float DS1821_RawToCelsius(int16_t raw_temp) { // DS1821温度数据格式:高字节为符号位和整数部分,低字节高4位为小数部分(0.5°C分辨率) // 例如:0x00A2 = 162 * 0.5 = 81.0°C; 0xFF5E = -162 * 0.5 = -81.0°C float temperature = (float)raw_temp * 0.5f; return temperature; }3.3 主程序中的调用逻辑
在实际应用中,我们通常不会连续不停地读取温度。一个典型的流程是周期性采样。这里展示一个在XMC主循环或定时器中断中实现的简单逻辑:
// 全局变量存储温度 float current_temperature = 0.0f; uint32_t last_conversion_time = 0; #define CONVERSION_INTERVAL_MS 1000 // 每1秒转换一次 void App_TemperatureTask(void) { uint32_t current_time = GetSystemTick(); // 获取系统滴答时钟 if ((current_time - last_conversion_time) > CONVERSION_INTERVAL_MS) { // 步骤1:启动转换 DS1821_StartConversion(); last_conversion_time = current_time; // 注意:DS1821的转换需要时间!典型值为1秒。 // 这里我们启动转换后立即返回,下次任务执行时再读取。 // 更精细的做法是设置一个“转换完成”标志位,在转换时间到后再读取。 } // 假设我们知道转换需要1秒,可以在启动转换后延迟读取 // 这里简化处理,在启动转换的下一个周期读取 static uint8_t read_flag = 0; if (read_flag) { int16_t raw_temp = DS1821_ReadTemperatureRaw(); if (raw_temp != 0xFFFF) { // 简单的错误检查(复位失败会返回0xFFFF?需根据实际情况调整) current_temperature = DS1821_RawToCelsius(raw_temp); // 在这里可以使用温度值,例如通过串口打印,或进行控制逻辑判断 printf("Current Temperature: %.1f C\r\n", current_temperature); } read_flag = 0; } else { // 启动转换后,置位标志,下一个周期读取 if ((current_time - last_conversion_time) > 100) { // 转换启动后100ms再置位,确保转换已开始 read_flag = 1; } } }4. 精度校准、常见问题与进阶应用
代码跑通了,能读到温度了,但这只是第一步。要想让DS1821在实际项目中稳定可靠地工作,还需要考虑精度校准和排查一些常见问题。
4.1 精度校准与补偿
DS1821的标称精度在0°C到70°C范围内为±2°C。对于很多应用,这个精度足够了。但如果需要更高的准确性,可以进行一点校准。
- 单点校准:找一个你认为最准的温度计(或恒温源),在某个温度点(比如25°C室温)下,同时读取DS1821的值和参考温度计的值,计算出一个偏移量(Offset)。后续所有读数都加上这个偏移量。
T_calibrated = T_DS1821 + Offset - 两点校准:在高温和低温两个点(例如0°C冰水混合物和50°C恒温箱)分别测量,可以计算出一个斜率和偏移量,进行线性补偿。这能更好地修正传感器在整个量程内的非线性误差。公式类似于:
T_calibrated = A * T_DS1821 + B,其中A和B通过两点标定计算得出。
校准系数可以存储在XMC的Flash中,或者如果DS1821连接了多个,可以为每个传感器存储独立的校准值。
4.2 通信失败排查指南
当你发现DS1821_Reset()总是返回0(无器件响应),或者读回来的数据全是0xFF或0x00时,可以按照以下步骤排查:
- 检查硬件连接:这是最常出问题的地方。用万用表确认VDD、GND、DQ三根线是否连接正确且牢固。重点检查4.7kΩ上拉电阻是否接在DQ和VDD之间,以及是否焊接良好。
- 检查电源:测量DS1821的VDD引脚电压,确保在3.0V-5.5V之间。如果使用寄生供电,在通信期间用示波器观察DQ线上的电压,看是否能被拉高到足够的电平。
- 检查时序:用逻辑分析仪或示波器抓取DQ线上的波形,这是最直接的调试手段。对照DS1821数据手册的时序图,检查:
- 复位脉冲的低电平时间是否大于480us?
- 从主机释放总线(拉高)到采样存在脉冲的时间是否在15-60us之间?
- 写0/写1时序的低电平时间是否符合要求?
- 读时序中,主机拉低的时间是否足够短(1-15us),采样点是否在15us之后?
- 检查代码延时:确认你的
delay_us函数是准确的。可以在GPIO上产生一个已知脉宽的信号并用示波器测量,来校准延时函数。SysTick的延时精度很高,但要确保CPU_FREQ_MHZ宏定义正确。 - 检查GPIO配置:确认XMC的GPIO已正确初始化为开漏输出模式,并且初始状态为高。在切换输入输出模式时,时序是否正确。
- 总线负载过重:如果总线上挂了多个单总线器件(虽然DS1821通常单用,但协议兼容),电容会增大,可能导致上升沿变缓。可以尝试减小上拉电阻(如用2.2kΩ),或者降低通信速率(但DS1821不支持可变速率)。
4.3 多传感器管理与CRC校验
虽然本项目标题是单个DS1821的应用,但单总线协议本身支持一线挂多器件的。每个DS1821都有一个唯一的64位激光ROM码。通过“搜索ROM”命令,可以枚举总线上的所有器件,然后通过“匹配ROM”命令对特定器件进行操作。这对于需要多点测温的场景非常有用。
此外,DS1821的暂存器数据包含一个8位的CRC校验码。在读取多字节数据(如温度值、TH、TL等)后,可以计算CRC并与读取的CRC字节比较,以确保数据传输的正确性。在长距离或干扰较大的环境中,启用CRC校验能极大提高通信可靠性。XMC的CRC硬件外设(CRC单元)可以很方便地用来计算单总线CRC,减轻CPU负担。
4.4 低功耗设计考虑
如果项目是电池供电,需要关注功耗。DS1821在待机状态下功耗极低(典型值750nA)。主要的耗电来自温度转换期间。因此,低功耗策略可以是:
- 降低采样频率:根据应用需要,将温度采样间隔从1秒延长到10秒、1分钟甚至更长。
- 间歇供电:如果不使用寄生供电,可以用一个XMC的GPIO口控制一个MOSFET来给DS1821的VDD供电。仅在需要测温时打开电源,测完后立即关闭。这样可以完全消除传感器待机功耗。
- 利用XMC的低功耗模式:在两次采样间隔,让XMC进入睡眠(Sleep)或深度睡眠(Deep Sleep)模式,通过RTC或看门狗定时器唤醒,从而大幅降低系统整体功耗。
5. 从DS1821到其他温度传感器的思考
成功集成DS1821后,你可能会遇到新的需求:需要更高的分辨率(如0.1°C)、更快的转换速度、更小的封装或者I2C/SPI接口。这时,了解DS1821的优缺点能帮助你更好地选型。
- 需要更高分辨率:可以升级到DS18B20。它是DS1821的“升级版”,分辨率可配置(9-12位,最高0.0625°C),同样使用单总线,命令集高度相似,代码移植成本低。但时序要求更严格一些。
- 需要更快的转换速度:DS1821的典型转换时间是1秒,DS18B20在12位分辨率下也需要750ms。如果需要每秒多次采样,可以考虑MCP9808(I2C接口,典型转换时间30ms)或TMP117(I2C,高精度,转换时间可选)。
- 嫌弃单总线时序麻烦:如果项目IO口不紧张,希望用更标准、更易驱动的接口,I2C或SPI接口的温度传感器是更好的选择,如LM75(I2C)、MAX31865(SPI,用于PT100铂电阻)。XMC的I2C和SPI外设非常强大,配合DMA使用可以极大减轻CPU负担。
- 需要模拟输出:如果你的XMC板载了高精度ADC,并且温度传感器离MCU很近(模拟信号传输距离短),那么使用热敏电阻或热电偶配合信号调理电路和ADC,可能是成本更低的选择。但这需要你处理模拟电路的噪声、非线性校准等问题。
选择哪种传感器,没有绝对的好坏,只有是否适合。DS1821以其极简的连接、数字化的输出和足够的精度,在那些对成本敏感、布线受限、精度要求不极致的场合,依然有着强大的生命力。通过这个项目,你不仅掌握了一款具体传感器的用法,更重要的是理解了如何根据需求选择外设,如何阅读数据手册,如何调试底层通信协议,这些经验在接触任何新的传感器时都是通用的。