目录
一、前言
二、环境与准备(开发环境、硬件准备、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
#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;
最后就成功读取到了正确的数据
希望能帮助到和我一样困扰的人~