• 物联网ARM开发-4协议-单总线应用温湿度传感器


    前言:STM32 虽然内部自带了温度传感器,但是因为芯片温升较大等问题,与实际温度差别较大, 所以,本章我们将向大家介绍如何通过 STM32 来读取外部数字温度传感器的温度,来得到较 为准确的环境温度。在本章中,我们将学习使用单总线技术,通过它来实现 STM32 和外部温 度传感器(DS18B20)的通信,并把从温度传感器得到的温度显示在 TFTLCD 模块上。本章分为如下几个部分:

    一、DS18B20温度传感器简介

    1、DS18B20 是由 DALLAS 半导体公司推出的一种的“一线总线”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。一线总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络, 从而为测量系统的构建引入全新概念,测量温度范围为-55~+125℃ ,精度为±0.5℃。现场温度直接以“一线总线”的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现 9~l2 位的数字值读数方式。它工作在 3~5.5V 的电压范围,采用多种封装形式,从而使系统设计灵活、方便,设定分辨率及用户设定的报警温度存储在 EEPROM 中,掉电后依然保存。

    2、DS18B20技术性能特征

    (1)独特的单总线接口方式,DS18B20在与微处理器连接时仅需要一条口线即可实        
    现微处理器与DS18B20的双向通讯。大大提高了系统的抗干扰性。
    (2)测温范围 -55℃~+125℃,精度为±0.5℃。
    (3)支持多点组网功能,多个DS18B20可以并联在唯一的三线上,最多只能并联8个,
    实现多点测温,如果数量过多,会使供电电源电压过低,从而造成信号传输的不稳定。
    (4)工作电源: 3.0~5.5V/DC (可以数据线寄生电源)。
    (5)在使用中不需要任何外围元件。
    (6)测量结果以9~12位数字量方式串行传送。

    二、DS18B20硬件设计

    1、DS18B20一共有三个引脚,分别是:

    • GND:电源地线
    • DQ:数字信号输入/输出端。
    • VDD:外接供电电源输入端。

     

    从上图可以看出,我们使用的是 STM32 PG9 来连接 U12 DQ 引脚,图中 U12 DHT11
    (数字温湿度传感器)和 DS18B20 共用的一个接口。DHT11在后面的案例中会介绍,也是温湿度传感器。

    2、DS18B20寄生电源

    DSl8B20的另一个特点是不需要再外部供电下即可工作。当总线高电平时能量由单线上拉电阻经过DQ引脚获得。高电平同时充电一个内部电容,当总线低电平时由此电容供应能量。这种供电方法被称为“寄生电源”。另外一种选择是DSl8B20由接在VDD的外部电源供电(也是我们设计的连接方式)

    三、DS18B20软件设计

    设计之前我们先阅读一下芯片手册,了解一下芯片的通信方式,控制方式,读写时序然后根据流程图规划我们的程序思路。

    1、单总线是一种半双工通信方式。

    2、DS18B20共有6种信号类型:       

    复位脉冲、应答脉冲、写0、写1、读0和读1。          
    所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前

    (1)复位脉冲

    单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少480 us,以产生复位脉冲。接着主机释放总线,4.7K的上拉电阻将单总线拉高,延时15~60 us并进入接收模式(Rx)接着DS18B20拉低总线60~240 us,以产生低电平应答脉冲

    (2)写时序

    写时序包括写0时序和写1时序。所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线。
    写0时序:主机输出低电平,延时60us,然后释放总线,延时2us
    写1时序:主机输出低电平,延时2us,然后释放总线,延时60us

    (3)读时序

    单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态
    典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us

    4、命令

     

    5、DS18B20内部构成

    主要由以下3部分组成: 64 位ROM,高速暂存器,存储器

    (1)64 位ROM存储独有的序列号

    ROM中的64位序列号是出厂前被光刻好的,它可以看作是该DS18B20的地址序列码,每个DS18B20的64位序列号均不相同。这样就可以实现一根总线上挂接多个DS18B20的目的。

    (2)高速暂存器包含:

    温度传感器
    一个字节的温度上限和温度下限报警触发器(TH和TL)
    配置寄存器允许用户设定9位,10位,11位和12位的温度分辨率,分别对应着温度的分辨率为:0.5°C,0.25°C,0.125°C,0.0625°C,默认为12位分辨率,
    (3)存储器:

    由一个高速的RAM和一个可擦除的EEPROM组成,EEPROM存储高温和低温触发器(TH和TL)以及配置寄存器的值,(就是存储低温和高温报警值以及温度分辨率)

    6、温度读取过程

    (1)主机发送复位脉冲,DS18B20发送应答脉冲,主机选择ROM命令,我们使用SKIP ROM,主机发送控制功能脉冲。

    (2)主机发送控制命令:主机发送44转换命令。这个命令开始温度转换。该任务结束。将执行温度转换,然后DS18B20将保持空闲。如果总线主根据此命令发出读时点,如果它忙于进行温度转换,DS18B20将在总线上输出0,;当温度转换完成时,它将返回1。

    (3)主机发送控制命令:主机发送BE转换命令。读取将从字节0开始,并将继续通过scratchpad直到第9个字节(字节8,CRC)被读取。如果不是所有的位置都要被读取,主机可以在任何时候发出一个重置来终止读取。

    DS18B20温度读取与计算
    DS18B20采用16位补码的形式来存储温度数据,温度是摄氏度。当温度转换命令发布后,经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第0和第1个字节。

    高字节的五个S为符号位,温度为正值时S=0,温度为负值时S=1

    剩下的11位为温度数据位,对于12位分辨率,所有位全部有效,对于11位分辨率,位0(bit0)无定义,对于10位分辨率,位0和位1无定义,对于9位分辨率,位0,位1,和位2无定义

    (4) 总结:

    DS18B20工作步骤
    DS18B20的工作步骤可以分为三步:

    • 复位DS18B20
    • 执行ROM指令
    • 执行DS18B20功能指令

    其中第二步执行ROM指令,也就是访问每个DS18B20,搜索64位序列号,读取匹配的序列号值,然后匹配对应的DS18B20,如果我们仅仅使用单个DS18B20,可以直接跳过ROM指令。而跳过ROM指令的字节是0xCC。
     

    四、DS18B20温度采集实例

    1、cubmx

    不同开发板的单总线引脚不同,原子的开发版对应是PG9

     

    2、keil

    ds18b20.h

    1. #ifndef __DS18B20_H
    2. #define __DS18B20_H
    3. #include "stm32f4xx_hal.h"
    4. //IO方向设置
    5. #define DS18B20_IO_IN() {GPIOG->MODER&=~(3<<(6*2));GPIOG->MODER|=0<<(6*2);} //PG6输入模式
    6. #define DS18B20_IO_OUT() {GPIOG->MODER&=~(3<<(6*2));GPIOG->MODER|=1<<(6*2);} //PG6输出模式
    7. IO操作函数
    8. #define DS18B20_OUT_LOW HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_RESET) //数据端口 PG6
    9. #define DS18B20_OUT_HIGH HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_SET) //数据端口 PG6
    10. #define DS18B20_DQ_IN HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6) //数据端口 PG6
    11. uint8_t DS18B20_Init(void); //初始化DS18B20
    12. short DS18B20_Get_Temp(void); //获取温度
    13. void DS18B20_Start(void); //开始温度转换
    14. void DS18B20_Write_Byte(uint8_t dat);//写入一个字节
    15. uint8_t DS18B20_Read_Byte(void); //读出一个字节
    16. uint8_t DS18B20_Read_Bit(void); //读出一个位
    17. uint8_t DS18B20_Check(void); //检测是否存在DS18B20
    18. void DS18B20_Reset(void); //复位DS18B20
    19. #endif

    ds18b20.c

    (1)初始化DS18B20_Init函数

    包含复位函数DS18B20_IReset和接收DS18B20_Check应答函数

    参考芯片手册中复位和应答的介绍:

    主机输出低电平,保持低电平时间至少480 us,以产生复位脉冲。接着主机释放总线,4.7K的上拉电阻将单总线拉高,延时15~60 us并进入接收模式(Rx)接着DS18B20拉低总线60~240 us,以产生低电平应答脉冲

    (2)因延时需要用到us,我们要写delay_us延时函数

    因为HAL_Delay()毫秒级延时,无法达到us。在中断中调用延时函数有时候卡死在这里,从封装函数中可以发现函数中有中断获取系统时钟HAL_IncTick(void),由于优先级系统给的低,所以在高优先级的中断中无法产生这个低级的中断,导致程序卡死在HAL_Delay()中
    由此建议直接不用HAL_Delay(),直接自己写延时函数

    (3)实现读取一个位,再实现读取一个字节(参考芯片手册的读时序)

    单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态
    典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us

     (4) 写时序,写CCh和44h命令

    写时序包括写0时序和写1时序。所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线。
    写0时序:主机输出低电平,延时60us,然后释放总线,延时2us
    写1时序:主机输出低电平,延时2us,然后释放总线,延时60us

    1. #include "ds18b20.h"
    2. uint32_t usctick = 0;
    3. uint32_t time_delay = 0;
    4. extern TIM_HandleTypeDef htim6;
    5. //延时nus
    6. //nus为要延时的us数.
    7. //nus:0~190887435(最大值即2^32/fac_us@fac_us=168)
    8. static uint8_t fac_us = 168; //这里主时钟为168M, 所以在1us内ticks会减168次
    9. void delay_us(uint32_t nus)
    10. {
    11. uint32_t ticks;
    12. uint32_t told,tnow,tcnt=0;
    13. uint32_t reload=SysTick->LOAD; //LOAD的值
    14. ticks=nus*fac_us; //1us需要的节拍数
    15. told=SysTick->VAL; //刚进入时的计数器值
    16. while(1)
    17. {
    18. tnow=SysTick->VAL;
    19. if(tnow!=told)
    20. {
    21. if(tnow//这里注意一下SYSTICK是一个递减的计数器就可以了.
    22. else tcnt+=reload-tnow+told;
    23. told=tnow;
    24. if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
    25. }
    26. };
    27. }
    28. //复位DS18B20
    29. void DS18B20_Reset(void)
    30. {
    31. DS18B20_IO_OUT(); //设置为输出
    32. DS18B20_OUT_LOW ; //拉低DQ
    33. delay_us(650); //拉低650us
    34. DS18B20_OUT_HIGH ; //拉高DQ
    35. delay_us(20); //20US
    36. }
    37. //等待DS18B20的回应
    38. //返回1:未检测到DS18B20的存在
    39. //返回0:存在
    40. uint8_t DS18B20_Check(void)
    41. {
    42. uint8_t retry=0;
    43. DS18B20_IO_IN(); //设置为输入
    44. //等待DS18B20拉低总线回应,如果超过200us未拉低,则认为未回应
    45. while ((DS18B20_DQ_IN == 1) && (retry<200))
    46. {
    47. retry++;
    48. delay_us(1);
    49. };
    50. if(retry>=200)return 1; //DS18B20超时未拉低总线
    51. else retry=0; //DS18B20拉低总线
    52. while ( (DS18B20_DQ_IN == 0 ) && ( retry < 240) ) //测试拉低总线的时间是否在240us内
    53. {
    54. retry++;
    55. delay_us(1);
    56. };
    57. if(retry>=240)return 1; //超过240us错误
    58. return 0; //正确回应
    59. }
    60. //从DS18B20读取一个位
    61. //返回值:1/0
    62. uint8_t DS18B20_Read_Bit(void)
    63. {
    64. uint8_t data;
    65. DS18B20_IO_OUT(); //设置为输出
    66. DS18B20_OUT_LOW ; //拉低DQ
    67. delay_us(3);
    68. DS18B20_OUT_HIGH ; //拉高DQ
    69. DS18B20_IO_IN(); //设置为输入
    70. delay_us(12);
    71. if(DS18B20_DQ_IN) data=1;
    72. else data=0;
    73. delay_us(50);
    74. return data;
    75. }
    76. //从DS18B20读取一个字节
    77. //返回值:读到的数据,先读数据的低位
    78. uint8_t DS18B20_Read_Byte(void)
    79. {
    80. uint8_t i,j,dat;
    81. dat=0;
    82. for (i=0;i<8;i++)
    83. {
    84. j=DS18B20_Read_Bit();
    85. dat=(j<
    86. }
    87. return dat;
    88. }
    89. //写一个字节到DS18B20
    90. //dat:要写入的字节
    91. void DS18B20_Write_Byte(uint8_t dat)
    92. {
    93. uint8_t j;
    94. uint8_t testb;
    95. DS18B20_IO_OUT(); //设置为输出
    96. for (j=1;j<=8;j++)
    97. {
    98. testb=dat&0x01;
    99. dat=dat>>1;
    100. if(testb) // 写1
    101. {
    102. DS18B20_OUT_LOW ; //拉低DQ
    103. delay_us(2);
    104. DS18B20_OUT_HIGH ; //拉高DQ
    105. delay_us(60);
    106. }
    107. else //写0
    108. {
    109. DS18B20_OUT_LOW ; //拉低DQ
    110. delay_us(60);
    111. DS18B20_OUT_HIGH ; //拉高DQ
    112. delay_us(2);
    113. }
    114. }
    115. }
    116. void DS18B20_Start(void)
    117. {
    118. //开始温度转换
    119. DS18B20_Reset();
    120. DS18B20_Check();
    121. DS18B20_Write_Byte(0xcc); // skip rom
    122. DS18B20_Write_Byte(0x44); // convert
    123. //开始读取温度
    124. DS18B20_Reset();
    125. DS18B20_Check();
    126. DS18B20_Write_Byte(0xcc); // skip rom
    127. DS18B20_Write_Byte(0xbe); // convert
    128. }
    129. //初始化DS18B20的IO口 DQ 同时检测DS的存在
    130. //返回1:不存在
    131. //返回0:存在
    132. uint8_t DS18B20_Init(void)
    133. {
    134. DS18B20_Reset();
    135. return DS18B20_Check();
    136. }
    137. //从ds18b20得到温度值
    138. //精度:0.1C
    139. //返回值:温度值 (-550~1250)
    140. short DS18B20_Get_Temp(void)
    141. {
    142. uint8_t temp;
    143. uint8_t TL,TH;
    144. short tem;
    145. DS18B20_Start (); //开始转换读取
    146. TL=DS18B20_Read_Byte(); // LSB
    147. TH=DS18B20_Read_Byte(); // MSB
    148. if(TH>7) //温度为负
    149. {
    150. TH=~TH;
    151. TL=~TL;
    152. temp=0; //温度为负
    153. }else temp=1; //温度为正
    154. tem=TH; //获得高八位
    155. tem<<=8;
    156. tem+=TL;//获得底八位
    157. tem=(double)tem*0.625;//转换 获得不带符号位的11位温度值
    158. if(temp)return tem; //返回温度值
    159. else return -tem;
    160. }

    main.c

    1. int fputc(int ch, FILE *p)
    2. {
    3. while(!(USART1->SR & (1<<7)));
    4. USART1->DR = ch;
    5. return ch;
    6. }
    7. /* USER CODE END 0 */
    8. /**
    9. * @brief The application entry point.
    10. *
    11. * @retval None
    12. */
    13. int main(void)
    14. {
    15. /* USER CODE BEGIN 1 */
    16. int16_t temperature;
    17. /* USER CODE END 1 */
    18. /* MCU Configuration----------------------------------------------------------*/
    19. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    20. HAL_Init();
    21. /* USER CODE BEGIN Init */
    22. /* USER CODE END Init */
    23. /* Configure the system clock */
    24. SystemClock_Config();
    25. /* USER CODE BEGIN SysInit */
    26. /* USER CODE END SysInit */
    27. /* Initialize all configured peripherals */
    28. MX_GPIO_Init();
    29. MX_USART1_UART_Init();
    30. MX_TIM6_Init();
    31. /* USER CODE BEGIN 2 */
    32. printf("this is DS18B20 test\n");
    33. if(!DS18B20_Init())
    34. {
    35. printf(" DS18B20 is here\n");
    36. }else
    37. {
    38. printf(" DS18B20 is not here\n");
    39. }
    40. /* USER CODE END 2 */
    41. /* Infinite loop */
    42. /* USER CODE BEGIN WHILE */
    43. while (1)
    44. {
    45. temperature = DS18B20_Get_Temp();
    46. if(temperature<0)
    47. {
    48. printf("-"); //显示负号
    49. temperature=-temperature; //转为正数
    50. }
    51. printf("temperature = %d.%d\n",temperature/10,temperature%10);
    52. HAL_Delay(1000);
    53. /* USER CODE END WHILE */
    54. /* USER CODE BEGIN 3 */
    55. }
    56. /* USER CODE END 3 */
    57. }

  • 相关阅读:
    Java手写最长公共子序列算法算法和最长公共子序列算法应用拓展案例
    ROS学习(26)动态参数配置
    使用Mind+部署kmodel模型至Maixduino板
    [交互]前端展示服务端获取的图片
    【GPT4O 开启多模态新时代!】
    485. 最大连续 1 的个数(javascript)485. Max Consecutive Ones
    选择合适的 DevOps 工具,从理解 DevOps 开始
    ICCV2023中super-resolution相关的文章汇总
    手机玻璃盖板为什么需要透光率检测
    IDC 中国边缘云市场最新报告解读:阿里云蝉联中国公有云市场第一
  • 原文地址:https://blog.csdn.net/m0_60718520/article/details/127593326