OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 33|回复: 0

《STM32H562开发指南 V1.0 》第三十七章 DS18B20数字温度传感器实验

[复制链接]

1413

主题

1429

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
6029
金钱
6029
注册时间
2019-5-8
在线时间
1717 小时
发表于 1 小时前 | 显示全部楼层 |阅读模式
第三十七章 DS18B20数字温度传感器实验

1)实验平台:正点原子Mini STM32H562开发板

2)章节摘自【正点原子】STM32H562开发指南 V1.0

3)购买链接: https://detail.tmall.com/item.htm?id=753606659612

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h562_mini.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:414052738


2.jpg

3.png

本章,我们将介绍STM32如何读取外部温度传感器的温度,来得到较为准确的环境温度。我们将学习单总线技术,通过它来实现STM32和外部温度传感器DS18B20的通信,并把从温度传感器得到的温度显示在LCD上。
本章分为如下几个小节:
37.1 DS18B20及其时序简介
37.2 硬件设计
37.3 程序设计
37.4 下载验证


37.1 DS18B20及其时序简介

37.1.1 DS18B20简介

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

第三十七章 DS18B20数字温度传感器实验557.png
图37.1.1.1 DS18B20内部结构图

ROM中的64位序列号是出厂前被标记好的,它可以看作使该DS18B20的地址序列码,每个DS18B20的64位序列号均不相同。64位ROM的排列是:前8位是产品家族码,接着48位是DS18B20的序列号,最后8位是前面56位的循环冗余校验码(CRC=X8+X5+X4+1)。ROM作用是使每一个DS18B20都各不相同,这样设计可以允许一根总线上挂载多个DS18B20模块同时工作且不会引起冲突。

37.1.2 DS18B20时序简介
所有单总线器件要求采用严格的信号时序,以保证数据的完整性。DS18B20共有6种信号类型:复位脉冲、应答脉冲、写0、写1、读0和读1。所有这些信号,除了应答脉冲以外,都是由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。这里我们简单介绍这几个信号的时序。
1)复位脉冲和应答脉冲


第三十七章 DS18B20数字温度传感器实验945.png
图37.1.2.1 复位脉冲和应答脉冲时序图

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


第三十七章 DS18B20数字温度传感器实验1112.png
图37.1.2.2 写时序图

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


第三十七章 DS18B20数字温度传感器实验1269.png
图37.1.2.3 读时序图

单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间。每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态。典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。
在了解单总线时序之后,我们来看一下DS18B20的典型温度读取过程,DS18B20的典型温度读取过程为:复位→发SKIP ROM(0xCC)→发开始转换命令(0x44)→延时→复位→发送SKIP ROM命令(0xCC)→发送存储器命令(0xBE)→连续读取两个字节数据(即温度)→结束。


37.2 硬件设计

1. 例程功能
本实验开机的时候先检测是否有DS18B20存在,如果没有,则提示错误。只有在检测到DS18B20之后才开始读取温度并显示在LCD上,如果发现了DS18B20,则程序每隔100ms左右读取一次数据,并把温度显示在LCD上。 LED0闪烁用于提示程序正在运行。

2. 硬件资源
1)LED灯
       LED0 – PB4
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)DS18B20温度传感器(接在PC13上)

3. 原理图
DS18B20接口与STM32的连接关系,如下图所示:


第三十七章 DS18B20数字温度传感器实验1973.png
图37.2.1 DS18B20接口与STM32的连接电路图

从上图可以看出,我们使用的是STM32的PC13来连接U4的DQ引脚,图中U4为DHT11(数字温湿度传感器)和DS18B20共用的一个接口,DHT11我们将在下一章介绍。
DS18B20只用到U4的3个引脚(U4的1、2和3脚),将DS18B20传感器插入到这个上面就可以通过STM32来读取DS18B20的温度了。连接示意图如图37.2.2所示:


第三十七章 DS18B20数字温度传感器实验2181.png
图37.2.2 DS18B20连接示意图

从上图可以看出,DS18B20的平面部分(有字的那面)应该朝内,而曲面部分朝外。然后插入如图所示的三个孔内。

37.3 程序设计
DS18B20实验中使用的是单总线协议,用到的是HAL中GPIO相关函数,前面也有介绍到,这里就不做展开了。下面介绍一下如何驱动DS18B20。
DS18B20配置步骤
1)使能DS18B20数据线对应的GPIO时钟。
本实验中DS18B20的数据线引脚是PC13,因此需要先使能GPIOC的时钟,代码如下:

  1. __HAL_RCC_GPIOC_CLK_ENABLE();                /* PC口时钟使能 */
复制代码
2)设置对应GPIO工作模式(开漏输出)
本实验GPIO使用开漏输出模式,通过函数HAL_GPIO_Init设置实现。
3)参考单总线协议,编写信号函数(复位脉冲、应答脉冲、写0/1、读0/1)
复位脉冲:主机发出低电平,保持低电平时间至少480us。
应答脉冲:DS18B20拉低总线60~240us,以产生低电平应答信号。
写1信号:主机输出低电平,延时2us,然后释放总线,延时60us。
写0信号:主机输出低电平,延时60us,然后释放总线,延时2us。
读0/1信号:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。
4)编写DS18B20的读和写函数
基于写1bit数据和读1bit数据的基础上,编写DS18B20写1字节和读1字节函数。
5)编写DS18B20获取温度函数
参考DS18B20典型温度读取过程,编写获取温度函数。


37.3.1 程序流程图

第三十七章 DS18B20数字温度传感器实验2920.png
图37.3.1.1 DS18B20实验程序流程图

37.3.2 程序解析

1.DS18B20驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DS18B20驱动源码包括两个文件:ds18b20.c和ds18b20.h。
首先我们先看一下ds18b20.h头文件里面的内容,其定义如下:

  1. /* DS18B20引脚 定义 */
  2. #define DS18B20_DQ_GPIO_PORT                GPIOC
  3. #define DS18B20_DQ_GPIO_PIN                 GPIO_PIN_13
  4. #define DS18B20_DQ_GPIO_CLK_ENABLE()         do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

  5. /* IO操作函数 */
  6. #define DS18B20_DQ_OUT(x)   do{ x ? \
  7.                                 HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT,
  8. DS18B20_DQ_GPIO_PIN, GPIO_PIN_SET) : \
  9.                                 HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT,
  10. DS18B20_DQ_GPIO_PIN, GPIO_PIN_RESET); \
  11.                             }while(0)                                                       /* 数据端口输出 */
  12. #define DS18B20_DQ_IN     HAL_GPIO_ReadPin(DS18B20_DQ_GPIO_PORT,
  13. DS18B20_DQ_GPIO_PIN)     /* 数据端口输入 */
复制代码
在ds18b20.h的操作跟IIC实验代码很类似,主要对用到GPIO口进行宏定义,以及宏定义IO操作函数,方便时序函数调用。
下面我们直接介绍ds18b20.c的程序,首先介绍的是DS18B20传感器的初始化函数,其定义如下:

  1. /**
  2. * @brief              初始化DS18B20的IO口 DQ 同时检测DS18B20的存在
  3. * [url=home.php?mod=space&uid=271674]@param[/url]               无
  4. * @retval              0, 正常
  5. *                       1, 不存在/不正常
  6. */
  7. uint8_t ds18b20_init(void)
  8. {
  9.     GPIO_InitTypeDef gpio_init_struct;
  10.    
  11.     DS18B20_DQ_GPIO_CLK_ENABLE();   /* 开启DQ引脚时钟 */

  12.     gpio_init_struct.Pin = DS18B20_DQ_GPIO_PIN;
  13.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;                  /* 开漏输出 */
  14.     gpio_init_struct.Pull = GPIO_PULLUP;                            /* 上拉 */
  15. gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;          /* 高速 */
  16. /* 初始化DS18B20_DQ引脚 */
  17.     HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &gpio_init_struct);
  18. /* DS18B20_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了,
  19. 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
  20.     ds18b20_reset();
  21.     return ds18b20_check();
  22. }
复制代码
在ds18b20的初始化函数中,主要对用到的GPIO口进行初始化,同时在函数最后调用复位函数和自检函数,这两个函数在后面会解释到。
下面介绍的是复位DS18B20函数和等待DS18B20的回应函数,它们的定义如下:

  1. /**
  2. * @brief              复位DS18B20
  3. * @param              data: 要写入的数据
  4. * @retval              无
  5. */
  6. static void ds18b20_reset(void)
  7. {
  8.     DS18B20_DQ_OUT(0);  /* 拉低DQ,复位 */
  9.     delay_us(750);       /* 拉低750us */
  10.     DS18B20_DQ_OUT(1);  /* DQ=1, 释放复位 */
  11.     delay_us(15);        /* 延迟15US */
  12. }

  13. /**
  14. * @brief             等待DS18B20的回应
  15. * @param              无
  16. * @retval              0, DS18B20正常
  17. *                       1, DS18B20异常/不存在
  18. */
  19. uint8_t ds18b20_check(void)
  20. {
  21.     uint8_t retry = 0;
  22.     uint8_t rval = 0;

  23.     while (DS18B20_DQ_IN && retry < 200)    /* 等待DQ变低, 等待200us */
  24.     {
  25.         retry++;
  26.         delay_us(1);
  27. }

  28.     if (retry >= 200)
  29.     {
  30.         rval = 1;
  31.     }
  32.     else
  33.     {
  34.         retry = 0;
  35.         while (!DS18B20_DQ_IN && retry < 240)   /* 等待DQ变高, 等待240us */
  36.         {
  37.             retry++;
  38.             delay_us(1);
  39.         }
  40.         if (retry >= 240) rval = 1;
  41.     }
  42.     return rval;
  43. }
复制代码
以上两个函数分别代表着前面所说的复位脉冲与应答信号,大家可以对比前面的时序图进行理解。由于复位脉冲比较简单,所以这里不做展开。现在看一下应答信号函数,函数主要是对于DS18B20传感器的回应信号进行检测,对此判断其是否存在。函数的实现也是依据时序图进行逻辑判断,例如当主机发送了复位信号之后,按照时序,DS18B20会拉低数据线60~240us,同时主机接收最小时间为480us,我们就依据这两个硬性条件进行判断,首先需要设置一个时限等待DS18B20响应,后面也设置一个时限等待DS18B20释放数据线拉高,满足这两个条件即DS18B20成功响应。
下面介绍的是写函数,其定义如下:

  1. /**
  2. * @brief            写一个字节到DS18B20
  3. * @param              data: 要写入的字节
  4. * @retval            无
  5. */
  6. static void ds18b20_write_byte(uint8_t data)
  7. {
  8.     uint8_t j;
  9.     for (j = 1; j <= 8; j++)
  10.     {
  11.         if (data & 0x01)
  12.         {
  13.             DS18B20_DQ_OUT(0);  /*  Write 1 */
  14.             delay_us(2);
  15.             DS18B20_DQ_OUT(1);
  16.             delay_us(60);
  17.         }
  18.         else
  19.         {
  20.             DS18B20_DQ_OUT(0);  /*  Write 0 */
  21.             delay_us(60);
  22.             DS18B20_DQ_OUT(1);
  23.             delay_us(2);
  24.         }
  25.         data >>= 1;             /* 右移,获取高一位数据 */
  26.     }
  27. }
复制代码
通过形参决定是写1还是写0,按照前面对写时序的分析,我们可以很清晰知道写函数的逻辑处理。
有写函数肯定就有读函数,下面介绍的是读函数,其定义如下:

  1. /**
  2. * @brief             从DS18B20读取一个位
  3. * @param            无
  4. * @retval           读取到的位值: 0 / 1
  5. */
  6. static uint8_t ds18b20_read_bit(void)
  7. {
  8.     uint8_t data = 0;
  9.     DS18B20_DQ_OUT(0);
  10.     delay_us(2);
  11.     DS18B20_DQ_OUT(1);
  12.     delay_us(12);

  13.     if (DS18B20_DQ_IN)
  14.     {
  15.         data = 1;
  16.     }
  17.     delay_us(50);
  18.     return data;
  19. }

  20. /**
  21. * @brief             从DS18B20读取一个字节
  22. * @param             无
  23. * @retval            读到的数据
  24. */
  25. static uint8_t ds18b20_read_byte(void)
  26. {
  27.     uint8_t i, b, data = 0;
  28.     for (i = 0; i < 8; i++)
  29.     {
  30.         b = ds18b20_read_bit();        /* DS18B20先输出低位数据 ,高位数据后输出 */
  31.         data |= b << i;                /* 填充data的每一位 */
  32.     }
  33.     return data;
  34. }
复制代码
在这里ds18b20_read_bit函数从DS18B20处读取1位数据,在前面已经对读时序也进行了详细的分析,所以这里也不展开解释了。
下面介绍读取温度函数,其定义如下:

  1. /**
  2. * @brief             开始温度转换
  3. * @param             无
  4. * @retval             无
  5. */
  6. static void ds18b20_start(void)
  7. {
  8.     ds18b20_reset();
  9.     ds18b20_check();
  10.     ds18b20_write_byte(0xcc);   /*  skip rom */
  11.     ds18b20_write_byte(0x44);   /*  convert */
  12. }
  13. /**
  14. * @brief             从ds18b20得到温度值(精度:0.1C)
  15. * @param             无
  16. * @retval            温度值 (-550~1250)
  17. *   [url=home.php?mod=space&uid=60778]@note[/url]            返回的温度值放大了10倍.
  18. *                    实际使用的时候,要除以10才是实际温度.
  19. */
  20. short ds18b20_get_temperature(void)
  21. {
  22.     uint8_t flag = 1;            /* 默认温度为正数 */
  23.     uint8_t TL, TH;
  24.     short temp;
  25.    
  26.     ds18b20_start();             /*  ds1820 start convert */
  27.     ds18b20_reset();
  28.     ds18b20_check();
  29.     ds18b20_write_byte(0xcc);   /*  skip rom */
  30.     ds18b20_write_byte(0xbe);   /*  convert */
  31.     TL = ds18b20_read_byte();   /*  LSB */
  32. TH = ds18b20_read_byte();   /*  MSB */

  33.     if (TH > 7)
  34.     {        /* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致:
  35.                 正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1
  36.                 所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余,
  37.                 我们这里先暂时没有作+1的处理,这里需要留意  */
  38.         TH = ~TH;
  39.         TL = ~TL;
  40.         flag = 0;   /* 温度为负 */
  41. }

  42.     temp = TH;      /* 获得高八位 */
  43.     temp <<= 8;
  44. temp += TL;     /* 获得底八位 */

  45.     /* 转换成实际温度 */
  46.     if (flag == 0)
  47.     {   /* 将温度转换成负温度,这里的+1参考前面的说明 */
  48.         temp = (double)(temp+1) * 0.625;
  49.         temp = -temp;   
  50. }
  51. else
  52. {
  53.         temp = (double)temp * 0.625;
  54. }     
  55. return temp;
  56. }
复制代码
在这里简单介绍一下上面用到的RAM指令:
跳过ROM(0xCC),该指令只适合总线只有一个节点,它通过允许总线上的主机不提供64位ROM序列号而直接访问RAM,节省了操作时间。
温度转换(0x44),启动DS18B20进行温度转换,结果存入内部RAM。
读暂存器(0xBE),读暂存器9个字节内容,该指令从RAM的第一个字节(字节0)开始读取,直到九个字节(字节8,CRC值)被读出为止。如果不需要读出所有字节的内容,那么主机可以在任何时候发出复位信号以中止读操作。

2. main.c代码
在main.c里面编写如下代码:

  1. int main(void)
  2. {
  3.     uint8_t t = 0;
  4. short temperature;

  5.     HAL_Init();                                            /* HAL库初始化 */
  6.     HAL_ICACHE_Enable();                                  /* 使能指令缓存 */
  7.     sys_stm32_clock_init(250, 4, 2, 2);                 /* 系统时钟初始化, 250Mhz */
  8.     delay_init(250);                                       /* 延时初始化 */
  9.     usart_init(115200);                                   /* 串口初始化 */
  10.     led_init();                                                 /* LED初始化 */
  11.     lcd_init();                                            /* LCD初始化 */

  12.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  13.     lcd_show_string(30, 70, 200, 16, 16, "DS18B20 TEST", RED);
  14. lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

  15.     while (ds18b20_init())              /* DS18B20初始化 */
  16.     {
  17.         lcd_show_string(30, 110, 200, 16, 16, "DS18B20 Error", RED);
  18.         delay_ms(200);
  19.         lcd_fill(30, 110, 239, 130 + 16, WHITE);
  20.         delay_ms(200);
  21. }

  22.     lcd_show_string(30, 110, 200, 16, 16, "DS18B20 OK", RED);
  23. lcd_show_string(30, 130, 200, 16, 16, "Temp:   . C", BLUE);

  24.     while (1)
  25.     {
  26.         if (t % 10 == 0)   /* 每100ms读取一次 */
  27.         {
  28.             temperature = ds18b20_get_temperature();
  29.             if (temperature < 0)
  30.             {
  31.                 lcd_show_char(30 + 40, 130, '-', 16, 0, BLUE);  /* 显示负号 */
  32.                 temperature = -temperature; /* 转为正数 */
  33.             }
  34.             else
  35.             {
  36.                 lcd_show_char(30 + 40, 130, ' ', 16, 0, BLUE);  /* 去掉负号 */
  37.             }
  38.             /* 显示正数部分 */
  39.             lcd_show_num(30 + 40 + 8, 130, temperature / 10, 2, 16, BLUE);  
  40. /* 显示小数部分 */
  41.             lcd_show_num(30 + 40 + 32, 130, temperature % 10, 1, 16, BLUE);
  42.         }

  43.         delay_ms(10);
  44.         t++;

  45.         if (t == 20)
  46.         {
  47.             t = 0;
  48.             LED0_TOGGLE();  /* LED0闪烁 */
  49.         }
  50.     }
  51. }
复制代码
主函数代码比较简单,一系列硬件初始化后,在循环中调用ds18b20_get_temperature函数获取温度值,然后显示在LCD上。

37.4 下载验证
假定DS18B20传感器已经接上去正确的位置,将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示当前的温度值的内容如图37.4.1所示:

第三十七章 DS18B20数字温度传感器实验10894.png
图37.4.1 程序运行效果图

该程序还可以读取并显示负温度值,具备零下温度条件可以测试一下。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-9-28 11:12

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表