赞
踩
新冠疫情、元器件涨价,同一家店铺之前买的DS18B20模组体积有大拇指这么大,最近买体积只有大拇指指甲盖这么大,以前无论买多少都有6元运费,今年哪怕买一块都是包邮,今年生意难做了,祈祷国泰民安、国强民富!
言归正传,说大事分割线~
DS18B20的数字温度传感器,可以用来对环境温度进行定量的检测。DS18B20数字温度传感器是美国DALLAS公司生产的一总线数字温度传感器。其测温范围 -55℃~+125℃,固有测温分辨率0.5℃,支持多点组网功能,多个DS18B20可以并联在唯一的三线上,实现多点测温,测量结果以9~12位数字量方式串行传送。模组结构如下图所示:
模组特性如下所示:
DS18B20管脚定义如下图所示:
DS18B20采用Maxim公司专有的1-Wire总线协议,该总线协议仅需要一个控制信号进行通信。该控制信号线需要一个唤醒的上拉电阻以防止连接在该总线上的口是3态或者高阻态(DQ信号线是在DS18B20上)。在该总线系统中,微控制器(主设备)通过每个设备的64为序列号来识别该总线上的设备。因为每个设备都有一个独一无二的序列号,挂在一个总线上的设备理论上是可以无限个的。在下面的“1-Wire总线系统”章节中包含有1-Wire总线协议详细的命令和时序关系。
DS18B20的另外一个特性就是可以无需外部电源供电。当数据线DQ为高的时候由其为设备供电。总线拉高的时候为内部电容(Spp)充电,当总线拉低是由该电容向设备供电。这种由1-Wire总线为设备供电的方式称为“寄生电源”。此外,DS18B20也可以由外部电源通过VDD供电。DS18B20内部框图如下所示:
DS18B20的核心功能是直接温度—数字测量,上电后工作在低功耗闲置状态下,其温度转换可由用户自定义为9、10、11、12位精度分别为0.5℃、0.25℃、0.125℃、0.0625℃分辨率,上电默认为12位转换精度。温度数据以一个16位标志扩展二进制补码数的形式存储在温度寄存器中,符号标志位(S)温度的正负极性:正数则S=0,负数则S=1。如果DS18B20被定义为12位的转换精度,温度寄存器中的所有位都将包含有效数据。若为11位转换精度,则BIT 0为未定义的。若为10位转换精度,则BIT 1和BIT 0为未定义的。 若为9位转换精度,则BIT 2、BIT 1和BIT 0为未定义的。下表为在12位转换精度下温度输出数据与相对应温度之间的关系表。
DS18B20的温度输出数据时在摄氏度下校准的,若是在华氏度下应用的话,可以用查表法或者常规的数据换算,温度/数据对应关系如下表所示:
DS18B20可以通过VDD引脚由外部供电,或者可以由“寄生电源”供电,这使得DS18B20可以不采用当地的外部电源供电而实现其功能。
外部电源供电方式具有不需要上拉的MOSFET、该1-Wire总线在温度转换期间可执行其他动作的优点,外部电源供电方式如下图所示:
“寄生电源”供电方式在远程温度检测或空间比较有限制的地方有很大的应用,其由DQ口拉高时向其供电。总线拉高的时候为内部电容(Cpp)充电,当总线拉低是由该电容向设备供电。当DS18B20为“寄生电源”供电模式时,该VDD引脚必须连接到地。“寄生电源”供电方式在温度超过+100℃时不推荐使用,因为在超过该温度下时将会有很大的漏电流导致不能进行正常的通信。实际应用中,在类似的温度状态下强烈推荐该DS18B20由外部电源供电,“寄生电源”供电方式如下图所示。
DS18B20存储器包含了SRAM暂存寄存器、过温和低温(TH和TL)温度报警寄存器、配置寄存器的非易失性EEPROM。当温度报警功能没有用到的时候,过温和低温(TH和TL)温度报警寄存器可以当做通用功能的存储单元。
SRAM暂存寄存器中的Byte 0和Byte 1分别作为温度寄存器的低字节和高字节,同时这两个字节是只读的;Byte 2和Byte 3作为过温和低温(TH和TL)温度报警寄存器;Byte 4保存着配置寄存器的数据;Byte 5、6、7作为内部使用的字节而保留使用,不可被写入;Byte 8存储着该暂存寄存器中Byte 0至Byte 7的循环冗余校验(CRC)值,并且只读不可写入,存储器组织结构如下图所示:
DS18B20的驱动过程主要依托于1-Wire总线系统,该总线系统可以一个总线主设备控制一个或多个从设备,我们的MCU作为主设备,DS18B20永远为从设备,1-Wire总线系统上所有的命令或者数据的发送送都是遵循低位先发送的原则。
1-Wire总线系统仅有一根数据线,且需要一个5kΩ左右的外部上拉电阻,因此闲置情况下数据线是高电平。每个设备(主设备或从设备)通过一个漏极开路或3态门引脚连接至数据线上。这就允许每个设备“释放”数据线,当设备没有传递数据的时其他设备可以有效地使用数据线。DS18B20的1-Wire总线接口(DQ引脚)是其内部电路组成的漏极开路,硬件配置如下图所示:
实现DS18B20的驱动主要有三步:
第一步:初始化DS18B20;
第二步:ROM命令(紧跟任何数据交换请求);
第三步:DS18B20功能命令(紧跟任何数据交换请求);
每次对DS18B20的访问都必须遵循这样的步骤来进行,如果这些步骤中的任何一个丢失或者没有执行,则DS18B20将不会响应。这只说明驱动的思路,具体参见DS18B20数据手册。
第1步:配置GPIO,这里配置HC32L136的PB03引脚,PB03引脚连接DS18B20数据线,所以IO口的方向(输入、输出)在读、写过程中是不断变化的,代码如下所示:
- uint8_t DS18B20_Init(void)
- {
-
- stc_gpio_cfg_t stcGpioCfg;
-
- DDL_ZERO_STRUCT(stcGpioCfg);
-
- Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); //开启GPIO时钟门控
-
- stcGpioCfg.enDir = GpioDirOut; ///< 端口方向配置->输出
- stcGpioCfg.enOD = GpioOdDisable; ///< 推挽输出
- stcGpioCfg.enPu = GpioPuDisable; ///< 端口上拉配置->禁止
- stcGpioCfg.enPd = GpioPdDisable; ///< 端口下拉配置->禁止
- stcGpioCfg.enDrv=GpioDrvH; ///< 强驱动
-
- Gpio_Init(GpioPortB,GpioPin8,&stcGpioCfg); ///< 端口初始化
-
- Gpio_SetIO(GpioPortB,GpioPin8); ///< 默认置高电平
- }
第2步:配置延时,本案例基于HC32L136 24MHz时钟频率下工作的,一个机器周期约为2us(num参数,不是倍数关系,HC32L136延时相当不精准,需要用示波器验证),代码如下所示:
- void delay_us(uint32_t num)
- {
- while(num--)
- {
- __NOP();
- }
- }
第3步:初始化以及检测DS18B20是否存在,当DS18B20响应复位信号的存在脉冲后,则其向主设备表明其在该总线上,并且已经做好操作命令。
在初始化序列期间,总线上的主设备通过拉低1-Wire总线超过480us来发送(TX)复位脉冲。之后主设备释放总线而进入接收模式(RX)。当总线释放后,5kΩ左右的上拉电阻将1-Wire总线拉至高电平。当DS18B20检测到该上升边沿信号后,其等待15us至60us后通过将1-Wire总线拉低60us至240us来实现发送一个存在脉冲。初始化时序如下图所示:
初始化时序代码如下所示:
- //复位DS18B20
- void DS18B20_Rst(void)
- {
- DS18B20_IO_OUT(); ///< 配置端口输出
- Gpio_ClrIO(GpioPortB,GpioPin8); ///< 拉低DQ
- delay100us(7); ///< 拉低700us
- Gpio_SetIO(GpioPortB,GpioPin8); ///< 拉高DQ
- delay10us(1); ///< 拉高15us
- }
-
- //等待DS18B20的回应
- //返回1:未检测到DS18B20的存在
- //返回0:存在
- uint8_t DS18B20_Check(void)
- {
- uint8_t retry=0;
- DS18B20_IO_IN(); ///< 配置端口输入
- while ((Gpio_GetInputIO(GpioPortB,GpioPin8)==1) && (retry<100)) ///< 最多200us
- {
- retry++;
- delay_us(1); ///< 每次等待2us
- }
-
- if(retry>=200)
- {
- return 1;
- }
- else
- {
- retry=0;
- }
-
- while ((Gpio_GetInputIO(GpioPortB,GpioPin8)==0 )&& (retry<120)) ///< 最多240us
- {
- retry++;
- delay_us(1); ///< 每次等待2us
- }
-
- if(retry>=120)
- {
- return 1;
- }
-
- return 0;
- }
运行程序,时序效果如下所示:
第4步:开始转换DS18B20,写入ROM命令:0XCC和0X44。写时段有两种情况:“写1”时段和“写0”时段,主设备通过写1时段来向DS18B20中写入逻辑1以及通过写0时段来向DS18B20中写入逻辑0。每个写时段最小必须有60us的持续时间且独立的写时段间至少有1us的恢复时间。
为了形成写1时段,在将1-Wire总线拉低后,主设备必须在15us之内释放总线。当总线释放后,5kΩ的上拉电阻将总线拉至高。为了形成写0时段,在将1-Wire总线拉低后,在整个时段期间主设备必须一直拉低总线(至少60us)。
在主设备初始化写时段后,DS18B20将会在15us至60us的时间窗口内对总线进行采样。如果总线在采样窗口期间是高电平,则逻辑1被写入DS18B20;若总线是低电平,则逻辑0被写入DS18B20。读/写时段时序如下所示:
转换DS18B20代码如下所示:
- //写一个字节到DS18B20
- //dat:要写入的字节
- void DS18B20_Write_Byte(uint8_t dat)
- {
- uint8_t j;
- uint8_t testb;
- DS18B20_IO_OUT(); ///< 配置端口输出
- for (j=1;j<=8;j++)
- {
- testb=dat&0x01;
- dat=dat>>1;
- if (testb) ///< 写入1
- {
- Gpio_ClrIO(GpioPortB,GpioPin8);
- delay_us(1); ///< 拉低2us
-
- Gpio_SetIO(GpioPortB,GpioPin8);
- delay10us(5); ///< 拉高64us
- }
- else ///< 写入0
- {
- Gpio_ClrIO(GpioPortB,GpioPin8);
- delay10us(5); ///< 拉低64us
-
- Gpio_SetIO(GpioPortB,GpioPin8);
- delay_us(1); ///< 拉高2us
- }
- }
- }
-
- //开始温度转换
- void DS18B20_Start(void)
- {
- DS18B20_Rst();
- DS18B20_Check();
- DS18B20_Write_Byte(0xCC); ///< skip rom
- DS18B20_Write_Byte(0x44); ///< convert
- }
运行程序,写入0XCC(二进制:1100 1100)时序效果如下所示:
写入0X44(二进制:0100 0100)时序效果如下所示:
第5步: 写入读取DS18B20暂存器功能命令:0XCC和0XBE,代码如下所示:
- DS18B20_Write_Byte(0xCC); ///< skip rom
- DS18B20_Write_Byte(0xBE); ///< convert
运行程序,写入0XBE(二进制:1011 1110)时序效果如下所示:
第6步: 读取温度寄存器数据,这里只需要读取两个字节(16位)即可。每个读时段最小必须有60us的持续时间且独立的写时段间至少有1us的恢复时间。读时段通过主设备将总线拉低超过1us再释放总线来实现初始化,当主设备初始化完读时段后,DS18B20将会向总线发送0或者1。DS18B20通过将总线拉至高来发送逻辑1,将总线拉至低来发送逻辑0。当发送完0后,DS18B20将会释放总线,则通过上拉电阻该总线将会恢复到高电平的闲置状态。从DS18B20中输出的数据在初始化读时序后仅有15us的有效时间,因此,主设备在开始改读时段后的15us之内必须释放总线,并且对总线进行采样。读时段时序图如下所示:
读时段代码如下所示:
- //从DS18B20读取一个位
- //返回值:1/0
- uint8_t DS18B20_Read_Bit(void)
- {
- uint8_t data;
- DS18B20_IO_OUT(); ///< 配置端口输出
- Gpio_ClrIO(GpioPortB,GpioPin8);
- delay_us(1); ///< 等待2us
-
- Gpio_SetIO(GpioPortB,GpioPin8);
- DS18B20_IO_IN(); ///< 配置端口输入
- delay_us(40); ///< 等待12us
-
- if(Gpio_GetInputIO(GpioPortB,GpioPin8)==1)
- {
- data=1;
- }
- else
- {
- data=0;
- }
-
- delay10us(4); ///< 等待52us
- return data;
- }
-
- //从DS18B20读取一个字节
- //返回值:读到的数据
- uint8_t DS18B20_Read_Byte(void)
- {
- uint8_t i=0,j=0,dat=0;
- uint8_t num1,num2;
- for (i=1;i<=8;i++) ///< 一次读取一个字节,8位
- {
- j=DS18B20_Read_Bit();
- num1=j<<7;
- num2=dat>>1;
- dat=num1|num2;
- }
- return dat;
- }
-
- TL=DS18B20_Read_Byte(); ///< 读取LSB,低八位数据
- TH=DS18B20_Read_Byte(); ///< 读取MSB,高八位数据
运行程序,读取低八位数据时序效果如下所示:
读取高八位数据时序效果如下所示:
所以读到的数据为,二进制:101101010。
第7步:将十六进制数据转换为十进制形式,代码如下所示:
- if(TH>7) ///< 温度为负
- {
- TH=~TH;
- TL=~TL;
- temp_flag=0;
- }
- else ///< 温度为正
- {
- temp_flag=1;
- }
-
- tem=TH;
- tem<<=8;
- tem+=TL;
- temp=(float)tem/16.0; ///< 转换数据
-
- if(temp_flag)
- {
- return temp;
- }
-
- return -temp;
二进制:101101010按照数据手册转换为十进制362,362除以16为22.625,与此时程序串口打印效果完全一致,说明转换无误。
闲来无事为了验证温度检测的精准性(HC32L136精度较差),特使用ESP32又写了一版驱动程序,完整示例代码如下所示(可直接复制使用):
- #include <OneWire.h>
-
- int DS18B20_Pin = D3; //DS18B20信号引脚在D3上
-
- //温度芯片I/O
- OneWire ds(DS18B20_Pin); //配置数字引脚D3
-
- void setup(void) {
- Serial.begin(9600);
- Serial.println("Start!\n");
- }
-
- void loop(void) {
- float temperature = getTemp();
- Serial.println(temperature);
-
- delay(1000);
- }
-
-
- float getTemp(){
- //获取DS18B20温度数据
-
- byte data[12];
- byte addr[8];
-
- if ( !ds.search(addr)) {
- //若无传感器继续搜索
- ds.reset_search();
- return -1000;
- }
-
- if ( OneWire::crc8( addr, 7) != addr[7]) {
- Serial.println("CRC is not valid!");
- return -1000;
- }
-
- if ( addr[0] != 0x10 && addr[0] != 0x28) {
- Serial.print("Device is not recognized");
- return -1000;
- }
-
- ds.reset();
- ds.select(addr);
- ds.write(0x44,1); //开始转换
-
- byte present = ds.reset();
- ds.select(addr);
- ds.write(0xBE); //读暂存存储器
-
-
- for (int i = 0; i < 9; i++) { //读取9个字节
- data[i] = ds.read();
- }
-
- ds.reset_search();
-
- byte MSB = data[1];
- byte LSB = data[0];
-
- float tempRead = ((MSB << 8) | LSB);
- float TemperatureSum = tempRead / 16;
-
- return TemperatureSum;
-
- }
同时运行程序,HC32L136和ESP32同时获取DS18B20温度数据基本一致,时序效果如下所示:
串口输出数据效果如下所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。