分享写DS18B20驱动代码时遇到的坑…为何读取到的数据全为1?
另,作为经验总结贴,希望能帮助到和我一样困扰的人

目录

一、前言

二、环境与准备(开发环境、硬件准备、DS18B20内部结构)

三、硬件连接(寄生接法、正常供电)

四、DS18B20的“1Wire”协议(初始化、发送ROM命令、发送功能命令)

五、驱动源代码

六、问题总结

一、前言

   最近在做一个基于机智云平台的智能花盆,选购的传感器里包含了这款DS18B20。

  正是这一个类似三极管的东西花了我几天的时间,最后看了一天示波器才找到驱动的错误…血泪史啊!

      

 

二、环境与准备

 开发环境:STM32CubeMx、keil5

硬件准备:STM32F103C8T6最小系统、4.7K的电阻、DS18B20

 

在此之前我们先来看看DS18B20的内部结构

 

A、64位光刻ROM

即每个DS18B20的身份证号码,如果你只用到了一个DS181B20,你可以不关注它。

 

B、高速寄存器

 

 

三、硬件连接

根据手册,DS18B20的硬件接法很简单,分为以下两种:

需要注意的是不管哪一种接法DQ上一定要接个上拉电阻

1.寄生接法

 

 DS18B20_GND——————>STM32F103_GND

DS18B20_VCC——————>STM32F103_GND

DS18B20_DQ——————>STM32F103_PB15

 

DQ引脚可接任意IO口

关于寄生方式,需要注意以下几点:

A、DS18B20的寄生方式是在DQ引脚为高电平时“窃取”电源,同时将部分能量存储在内部的电容里。

所以,上拉电阻!!一定要接上!!

B、为了使DS18B20准确完成温度转换,当温度转换发生时,IO口必须提供足够大的功率。

DS18B20的工作电流高达1mA,5K的上拉电阻使得IO口没有足够的驱动能力。

如果多个DS18B20在同一个IO上而且同时进行温度的变换时,这个问题将特别尖锐。

 

 

 

2.正常供电

 

 DS18B20_GND——————>STM32F103_GND

DS18B20_VCC——————>STM32F103_VCC

DS18B20_DQ——————>STM32F103_PB15

 

 

四、DS18B20的“1Wire”协议

 先放个传送门…

 

哎呀,放错了…是下面的…

安利,里面的讲解真的很详细!

 

单总线通信

DS18B20的数据格式及转换

DS18B20的读写时序

 

经过单线访问DS18B20的需要以下步骤:

A、初始化

单总线上的所有操作均从初始化开始

所谓初始化就是发送一段特定的时序,即复位脉冲

从属器接收到这段脉冲后会拉低总线,这个拉低的动作就是应答

当你发送复位脉冲后检测到DS18B20拉低了信号,就是成功的第一步了呀。

 

 

B、发送ROM命令

一旦我们检测到DS18B20的存在,我们就可以发生ROM指令啦

当有多个DS18B20连接在同一个IO口上时,我可以通过ROM指令指定DS18B20

而只有一个DS18B20时,我们通常直接发送“跳过ROM”

 

 

 

C、发送功能命令

 

 

 

 那么我们要怎么发送这些指令呢?

又要怎么读DS18B20传送回来的温度呢?

这就涉及到DS18B20的读写时序啦

下面我们来看看读写时序

 

如果你觉得时序图晦涩难懂,可以戳传送门DS18B20的读写时序

里面的讲解非常详细

 

五、驱动编写

关于读写时序,建议对照代码进行理解

 

DS18B20.h

  1. #include "stm32f1xx_hal.h"
  2.  
  3. //IO操作
  4. #define DS18B20_DQ_H HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET)
  5. #define DS18B20_DQ_L HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET)
  6. #define DS18B20_DQ_ReadPin HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)
  7.  
  8. extern void DS18B20_DQ_DDR(uint8_t ddr);
  9. extern void delay_us(uint32_t nus);
  10. extern int DS18B20_reset(void);
  11. extern void DS18B20_Wbyte(uint8_t xbyte);
  12. extern uint8_t DS18B20_Rbit(void);
  13. extern uint8_t DS18B20_Rbyte(void);
  14. extern int ReadTemperature(void);

 

DS18B20.c

 

  1. #include "DS18B20.h"
  2.  
  3. /*******************************************************************************
  4. 函数名:DS18B20_DQ_DDR
  5. 功能:配置IO输入/输出状态
  6. 输入:0/1 输入0配置为输入,输入1配置为输出
  7. 输出:
  8. 返回值:
  9. 备注:我用的是PB15,其他GPIO口需自己看手册修改相应的寄存器
  10. *******************************************************************************/
  11. void DS18B20_DQ_DDR(uint8_t ddr)
  12. {
  13. if(ddr == 1)
  14. {
  15. GPIOB->CRH&=0X1FFFFFFF;
  16. GPIOB->CRH|=0X10000000;
  17. }
  18. else
  19. {
  20. GPIOB->CRH&=0X8FFFFFFF;
  21. GPIOB->CRH|=0X80000000;
  22. }
  23. }
  24. //void DS18B20_DQ_DDR(uint8_t ddr)
  25. //{
  26. // GPIO_InitTypeDef GPIO_InitStruct;
  27. // //使能GPIO时钟
  28. // __HAL_RCC_GPIOB_CLK_ENABLE();
  29. // //配置为输出
  30. // if(ddr == 1)
  31. // {
  32. // GPIO_InitStruct.Pin = GPIO_PIN_15;
  33. // GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  34. // GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  35. // HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  36. // }
  37. // //配置为输入
  38. // else
  39. // {
  40. // GPIO_InitStruct.Pin = GPIO_PIN_15;
  41. // GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  42. // GPIO_InitStruct.Pull = GPIO_NOPULL;
  43. // HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  44. // }
  45. //}
  46. /*******************************************************************************
  47. 函数名:delay_us
  48. 功能:延时us
  49. 输入:
  50. 输出:
  51. 返回值:
  52. 备注:
  53. *******************************************************************************/
  54. void delay_us(uint32_t nus)
  55. {
  56. while (nus--)
  57. __nop();
  58. }
  59. /*******************************************************************************
  60. 函数名:DS18B20_reset
  61. 功能:初始化DS18B20
  62. 输入:
  63. 输出:
  64. 返回值:初始化成功为0,不成功为1
  65. 备注:
  66. *******************************************************************************/
  67. int DS18B20_reset(void)
  68. {
  69. int x = 0;
  70. //改变DQ引脚为输出
  71. DS18B20_DQ_DDR(1);
  72. //先置高
  73. DS18B20_DQ_H;
  74. //延时700us,使总线稳定
  75. delay_us(1400);
  76. //复位脉冲,低电位
  77. DS18B20_DQ_L;
  78. //保持至少480us,这里500us
  79. delay_us(1000);
  80. //改变DQ引脚为输入
  81. DS18B20_DQ_DDR(0);
  82. //拉高数据线,释放总线
  83. DS18B20_DQ_H;
  84. //等待15-60us,这里33us
  85. delay_us(60);
  86. //等待35us,这里33us
  87. delay_us(60);
  88. //聆听,判断有没有初始化成功(DS18B20有没有发送应答脉冲)
  89. x = DS18B20_DQ_ReadPin;
  90. //printf("DS18B20 waiting....\n");
  91. //等待应答脉冲出现
  92. //while(x);
  93. //printf("DS18B20 OK\n");
  94. //至少480us后进入接收状态,这里500us
  95. delay_us(1000);
  96. return x;
  97. }
  98. /*******************************************************************************
  99. 函数名:DS18B20_Wbyte
  100. 功能:写一个字节
  101. 输入:uint8_t xbyte
  102. 输出:
  103. 返回值:
  104. 备注:
  105. *******************************************************************************/
  106. void DS18B20_Wbyte(uint8_t xbyte)
  107. {
  108. //i:循环控制变量,x:取位运算变量
  109. int8_t i ,x = 0;
  110. //改变DQ引脚为输出
  111. DS18B20_DQ_DDR(1);
  112. //8次循环实现逐位写入
  113. for(i = 1; i <= 8; i++)
  114. {
  115. //先取低位
  116. x = xbyte & 0x01;
  117. //写1
  118. if(x)
  119. {
  120. DS18B20_DQ_H;
  121. //拉低总线
  122. DS18B20_DQ_L;
  123. //延时15us
  124. delay_us(25);
  125. //总线写1
  126. DS18B20_DQ_H;
  127. //延时15us
  128. delay_us(25);
  129. //保持高电平
  130. DS18B20_DQ_H;
  131. delay_us(4);
  132. }
  133. //写0
  134. else
  135. {
  136. DS18B20_DQ_H;
  137. //总线拉低
  138. DS18B20_DQ_L;
  139. //延时15us
  140. delay_us(25);
  141. //总线写0
  142. DS18B20_DQ_L;
  143. //延时15us
  144. delay_us(25);
  145. //保持高电平
  146. DS18B20_DQ_H;
  147. delay_us(4);
  148. }
  149. //xbyte右移一位
  150. xbyte = xbyte >> 1;
  151. }
  152. }
  153. /*******************************************************************************
  154. 函数名:DS18B20_Rbit
  155. 功能:从DS18B20读一个位
  156. 输入:
  157. 输出:
  158. 返回值:读取到的位
  159. 备注:
  160. *******************************************************************************/
  161. uint8_t DS18B20_Rbit(void)
  162. {
  163. //rbit是最终位数据,x是取状态变量
  164. uint8_t rbit = 0x00,x = 0;
  165. //改变DQ为输出模式
  166. DS18B20_DQ_DDR(1);
  167. DS18B20_DQ_H;
  168. //总线写0
  169. DS18B20_DQ_L;
  170. //延时15us以内
  171. delay_us(1);
  172. //释放总线
  173. DS18B20_DQ_H;
  174. //改变DQ为输入模式
  175. DS18B20_DQ_DDR(0);
  176. //延时大约3us
  177. //delay_us(7);
  178. //获取总线电平状态
  179. x = DS18B20_DQ_ReadPin;
  180. //如果是1,则返回0x80,否则返回0x00
  181. if(x)
  182. rbit = 0x80;
  183. //延时大约60us
  184. delay_us(130);
  185. return rbit;
  186. }
  187. /*******************************************************************************
  188. 函数名:DS18B20_Rbyte
  189. 功能:从DS18B20读一个字节
  190. 输入:
  191. 输出:
  192. 返回值:读取到的字节
  193. 备注:
  194. *******************************************************************************/
  195. uint8_t DS18B20_Rbyte(void)
  196. {
  197. //rbyte:最终得到的字节
  198. //tempbit:中间运算变量
  199. uint8_t rbyte = 0,i = 0, tempbit =0;
  200. for (i = 1; i <= 8; i++)
  201. {
  202. //读取位
  203. tempbit = DS18B20_Rbit();
  204. //右移实现高低位排序
  205. rbyte = rbyte >> 1;
  206. //或运算移入数据
  207. rbyte = rbyte|tempbit;
  208. }
  209. return rbyte;
  210. }
  211. int ReadTemperature(void)
  212. {
  213. //fg:符号位
  214. //data:温度的整数部分
  215. int fg;
  216. int data;
  217. //DS18B20初始化
  218. DS18B20_reset();
  219. //跳过读序列号
  220. DS18B20_Wbyte(0xcc);
  221. //启动温度转换
  222. DS18B20_Wbyte(0x44);
  223. //等待温度转换
  224. HAL_Delay(1);
  225. DS18B20_reset();
  226. DS18B20_Wbyte(0xcc);
  227. //读温度寄存器
  228. DS18B20_Wbyte(0xbe);
  229. uint8_t TempL = DS18B20_Rbyte();
  230. uint8_t TempH = DS18B20_Rbyte();
  231. //符号位为负
  232. if(TempH > 0x70)
  233. {
  234. TempL = ~TempL;
  235. TempH = ~TempH;
  236. fg = 0;
  237. }
  238. else
  239. fg = 1;
  240. //整数部分
  241. data = TempH;
  242. data <<= 8;
  243. data += TempL;
  244. data = (float)data*0.625;
  245. data = data / 10.0; //转换
  246. if(fg)
  247. return data;
  248. else
  249. return -data;
  250. }

 

ps:如果你需要移植该代码,只需要更改以下内容:

1.头文件里的IO操作

2.重写改变IO模式的DS18B20_DQ_DDR()函数

3.延时函数可以不用改,但是由于每个板子的时钟是不同的

你需要测出实际的延时时间,按备注更改延时时间

 

六、问题总结

其实写驱动用不了多长的时间

问题是我在测驱动的时候遇到了很多问题,其中一个困扰最久的问题就是,读取到的数据全为1。

网上也看到有人问这样的问题,最大的可能是时序不对

所以开始一直改delay_us函数却没注意到其他函数也是占用时间的…

我们知道读取的时间过长,IO口是会被上拉电阻拉高的

而在我的DS18B20_Rbit函数内中

 

 

由于是用STM32Cube+Keil开发,开始时DS18B20_DQ_DDR()的构建

是参照GPIO初始化时的操作去改变IO口的模式

我在网上也看到很多人是这样子写的

 

 GPIO_InitTypeDef GPIO_InitStruct;

//使能GPIO时钟

__HAL_RCC_GPIOB_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_15;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

 

最后我发现这样的操作居然花了我43us!!

于是我改用配置寄存器的方法去配置GPIO的输入\ 输出模式

 

 GPIOB->CRH&=0X1FFFFFFF;

GPIOB->CRH|=0X10000000;

 

最后就成功读取到了正确的数据

希望能帮助到和我一样困扰的人~

 

 

版权声明:本文为JYU-hsy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/JYU-hsy/p/9857804.html