STM32F103 驱动DS18B20
分享写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的需要以下步骤:
A、初始化
单总线上的所有操作均从初始化开始
所谓初始化就是发送一段特定的时序,即复位脉冲
从属器接收到这段脉冲后会拉低总线,这个拉低的动作就是应答
当你发送复位脉冲后检测到DS18B20拉低了信号,就是成功的第一步了呀。
B、发送ROM命令
一旦我们检测到DS18B20的存在,我们就可以发生ROM指令啦
当有多个DS18B20连接在同一个IO口上时,我可以通过ROM指令指定DS18B20
而只有一个DS18B20时,我们通常直接发送“跳过ROM”
C、发送功能命令
那么我们要怎么发送这些指令呢?
又要怎么读DS18B20传送回来的温度呢?
这就涉及到DS18B20的读写时序啦
下面我们来看看读写时序
如果你觉得时序图晦涩难懂,可以戳传送门DS18B20的读写时序
里面的讲解非常详细
五、驱动编写
关于读写时序,建议对照代码进行理解
DS18B20.h
- #include "stm32f1xx_hal.h"
- //IO操作
- #define DS18B20_DQ_H HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET)
- #define DS18B20_DQ_L HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET)
- #define DS18B20_DQ_ReadPin HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15)
- extern void DS18B20_DQ_DDR(uint8_t ddr);
- extern void delay_us(uint32_t nus);
- extern int DS18B20_reset(void);
- extern void DS18B20_Wbyte(uint8_t xbyte);
- extern uint8_t DS18B20_Rbit(void);
- extern uint8_t DS18B20_Rbyte(void);
- extern int ReadTemperature(void);
DS18B20.c
- #include "DS18B20.h"
- /*******************************************************************************
- 函数名:DS18B20_DQ_DDR
- 功能:配置IO输入/输出状态
- 输入:0/1 输入0配置为输入,输入1配置为输出
- 输出:
- 返回值:
- 备注:我用的是PB15,其他GPIO口需自己看手册修改相应的寄存器
- *******************************************************************************/
- void DS18B20_DQ_DDR(uint8_t ddr)
- {
- if(ddr == 1)
- {
- GPIOB->CRH&=0X1FFFFFFF;
- GPIOB->CRH|=0X10000000;
- }
- else
- {
- GPIOB->CRH&=0X8FFFFFFF;
- GPIOB->CRH|=0X80000000;
- }
- }
- //void DS18B20_DQ_DDR(uint8_t ddr)
- //{
- // GPIO_InitTypeDef GPIO_InitStruct;
- // //使能GPIO时钟
- // __HAL_RCC_GPIOB_CLK_ENABLE();
- // //配置为输出
- // if(ddr == 1)
- // {
- // 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);
- // }
- // //配置为输入
- // else
- // {
- // GPIO_InitStruct.Pin = GPIO_PIN_15;
- // GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
- // GPIO_InitStruct.Pull = GPIO_NOPULL;
- // HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- // }
- //}
- /*******************************************************************************
- 函数名:delay_us
- 功能:延时us
- 输入:
- 输出:
- 返回值:
- 备注:
- *******************************************************************************/
- void delay_us(uint32_t nus)
- {
- while (nus--)
- __nop();
- }
- /*******************************************************************************
- 函数名:DS18B20_reset
- 功能:初始化DS18B20
- 输入:
- 输出:
- 返回值:初始化成功为0,不成功为1
- 备注:
- *******************************************************************************/
- int DS18B20_reset(void)
- {
- int x = 0;
- //改变DQ引脚为输出
- DS18B20_DQ_DDR(1);
- //先置高
- DS18B20_DQ_H;
- //延时700us,使总线稳定
- delay_us(1400);
- //复位脉冲,低电位
- DS18B20_DQ_L;
- //保持至少480us,这里500us
- delay_us(1000);
- //改变DQ引脚为输入
- DS18B20_DQ_DDR(0);
- //拉高数据线,释放总线
- DS18B20_DQ_H;
- //等待15-60us,这里33us
- delay_us(60);
- //等待35us,这里33us
- delay_us(60);
- //聆听,判断有没有初始化成功(DS18B20有没有发送应答脉冲)
- x = DS18B20_DQ_ReadPin;
- //printf("DS18B20 waiting....\n");
- //等待应答脉冲出现
- //while(x);
- //printf("DS18B20 OK\n");
- //至少480us后进入接收状态,这里500us
- delay_us(1000);
- return x;
- }
- /*******************************************************************************
- 函数名:DS18B20_Wbyte
- 功能:写一个字节
- 输入:uint8_t xbyte
- 输出:
- 返回值:
- 备注:
- *******************************************************************************/
- void DS18B20_Wbyte(uint8_t xbyte)
- {
- //i:循环控制变量,x:取位运算变量
- int8_t i ,x = 0;
- //改变DQ引脚为输出
- DS18B20_DQ_DDR(1);
- //8次循环实现逐位写入
- for(i = 1; i <= 8; i++)
- {
- //先取低位
- x = xbyte & 0x01;
- //写1
- if(x)
- {
- DS18B20_DQ_H;
- //拉低总线
- DS18B20_DQ_L;
- //延时15us
- delay_us(25);
- //总线写1
- DS18B20_DQ_H;
- //延时15us
- delay_us(25);
- //保持高电平
- DS18B20_DQ_H;
- delay_us(4);
- }
- //写0
- else
- {
- DS18B20_DQ_H;
- //总线拉低
- DS18B20_DQ_L;
- //延时15us
- delay_us(25);
- //总线写0
- DS18B20_DQ_L;
- //延时15us
- delay_us(25);
- //保持高电平
- DS18B20_DQ_H;
- delay_us(4);
- }
- //xbyte右移一位
- xbyte = xbyte >> 1;
- }
- }
- /*******************************************************************************
- 函数名:DS18B20_Rbit
- 功能:从DS18B20读一个位
- 输入:
- 输出:
- 返回值:读取到的位
- 备注:
- *******************************************************************************/
- uint8_t DS18B20_Rbit(void)
- {
- //rbit是最终位数据,x是取状态变量
- uint8_t rbit = 0x00,x = 0;
- //改变DQ为输出模式
- DS18B20_DQ_DDR(1);
- DS18B20_DQ_H;
- //总线写0
- DS18B20_DQ_L;
- //延时15us以内
- delay_us(1);
- //释放总线
- DS18B20_DQ_H;
- //改变DQ为输入模式
- DS18B20_DQ_DDR(0);
- //延时大约3us
- //delay_us(7);
- //获取总线电平状态
- x = DS18B20_DQ_ReadPin;
- //如果是1,则返回0x80,否则返回0x00
- if(x)
- rbit = 0x80;
- //延时大约60us
- delay_us(130);
- return rbit;
- }
- /*******************************************************************************
- 函数名:DS18B20_Rbyte
- 功能:从DS18B20读一个字节
- 输入:
- 输出:
- 返回值:读取到的字节
- 备注:
- *******************************************************************************/
- uint8_t DS18B20_Rbyte(void)
- {
- //rbyte:最终得到的字节
- //tempbit:中间运算变量
- uint8_t rbyte = 0,i = 0, tempbit =0;
- for (i = 1; i <= 8; i++)
- {
- //读取位
- tempbit = DS18B20_Rbit();
- //右移实现高低位排序
- rbyte = rbyte >> 1;
- //或运算移入数据
- rbyte = rbyte|tempbit;
- }
- return rbyte;
- }
- int ReadTemperature(void)
- {
- //fg:符号位
- //data:温度的整数部分
- int fg;
- int data;
- //DS18B20初始化
- DS18B20_reset();
- //跳过读序列号
- DS18B20_Wbyte(0xcc);
- //启动温度转换
- DS18B20_Wbyte(0x44);
- //等待温度转换
- HAL_Delay(1);
- DS18B20_reset();
- DS18B20_Wbyte(0xcc);
- //读温度寄存器
- DS18B20_Wbyte(0xbe);
- uint8_t TempL = DS18B20_Rbyte();
- uint8_t TempH = DS18B20_Rbyte();
- //符号位为负
- if(TempH > 0x70)
- {
- TempL = ~TempL;
- TempH = ~TempH;
- fg = 0;
- }
- else
- fg = 1;
- //整数部分
- data = TempH;
- data <<= 8;
- data += TempL;
- data = (float)data*0.625;
- data = data / 10.0; //转换
- if(fg)
- return data;
- else
- return -data;
- }
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;
最后就成功读取到了正确的数据
希望能帮助到和我一样困扰的人~