一、功能需求
用DS18B20实现环境温度的采样
用数码管显示: 显示一位小数, 例如 23.6
实现高温蜂鸣器报警: 超过28.0蜂鸣器响
二、分析
数码管显示, 蜂鸣器报警, 这些功能在视频教程里都已经讲过。此次作业,关键在于与DS18B20通讯,采集实时的环境温度到mcu。
从网上找到一个ds18b20的手册,学习下它使用的 1-wire 通讯协议的时序规则。手册中有各个通讯过程的详细时序图,按图来实现规则即可。
三、硬件SysConfig配置
通讯只需要一根数据线,根据使用的引脚配置它:

四、1-wire协议的实现
新建两个文件:DS18B20.c 和 DS18B20.h, 将与它通讯的1-wire 协议封装在模块中,方便后续使用。
因为 1-wire协议时序严格,通讯过程中的延时要很严格否则通讯失败,所以最好使用阻塞式的delay_cycles()函数,因为主频是32M,微妙与cycle相差32倍,包装一个延迟微秒的函数:
void delay_us(uint32_t us) //阻塞延迟微秒数
{
delay_cycles(us*32);
}为了简化通讯引脚的状态读取,以及置高低电平,使用宏定义:
#define DS18B20_DQ_OUT(x) ((x)?(DL_GPIO_setPins(GPIO_HC595_PORT, GPIO_HC595_DS18B20_DQ_PIN)) : (DL_GPIO_clearPins(GPIO_HC595_PORT, GPIO_HC595_DS18B20_DQ_PIN))) #define DS18B20_DQ_IN ( DL_GPIO_readPins(GPIO_HC595_PORT,GPIO_HC595_DS18B20_DQ_PIN) )
通讯针需要输出和输入状态切换,继续宏定义:
#define DS18B20_OutPut_Mode() {DL_GPIO_initDigitalOutput(GPIO_HC595_DS18B20_DQ_IOMUX);}
#define DS18B20_InPut_Mode() {DL_GPIO_initDigitalInput(GPIO_HC595_DS18B20_DQ_IOMUX);}做好以上的准备,开始真正的通讯过程开发。
(1)初始化序列:
1-wire单总线协议通讯总是以初始化序列开始,包含一个复位脉冲和一个存在脉冲,如下图
按手册上序列图实现代码如下:
/**
* @brief 复位DS18B20
* @param 无
* @retval 无
*/
static void ds18b20_reset(void)
{
DS18B20_OutPut_Mode();
DS18B20_DQ_OUT(0); /* 拉低DQ,复位 */
delay_us(750); /* 拉低750us */
DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */
delay_us(15); /* 延迟15US */
} 接着是等待存在脉冲,即回应信号。实现代码如下:
/**
* @brief 等待DS18B20的回应
* @param 无
* @retval 0, DS18B20正常
* 1, DS18B20异常/不存在
*/
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while ((DS18B20_DQ_IN>0) && (retry < 200)) /* 等待DQ变低, 等待200us */
{
retry++;
delay_us(1);
}
if (retry >= 200)
{
rval = 1;
}
else
{
retry = 0;
while (!(DS18B20_DQ_IN>0) && (retry < 240)) /* 等待DQ变高, 等待240us */
{
retry++;
delay_us(1);
}
if (retry >= 240) rval = 1;
}
return rval;
}组合一下,就是初始化序列的完整代码:
/**
* @brief 初始化DS18B20的IO口 DQ 同时检测DS18B20的存在
* @param 无
* @retval 0, 正常
* 1, 不存在/不正常
*/
uint8_t ds18b20_init(void)
{
ds18b20_reset();
return ds18b20_check();
}(2)读写序列:
接着是读写一个字节的序列。手册上的时序图:

先实现读取一个bit的时序 ,循环8次就可实现读取一个字节。注意,协议总是最低位先行。
/**
* @brief 从DS18B20读取一个位
* @param 无
* @retval 读取到的位值: 0 / 1
*/
static uint8_t ds18b20_read_bit(void)
{
uint8_t data = 0;
DS18B20_DQ_OUT(0);
delay_us(2);
DS18B20_DQ_OUT(1);
delay_us(12);
DS18B20_InPut_Mode();
if (DS18B20_DQ_IN>0)
{
data = 1;
}
delay_us(50);
return data;
}
/**
* @brief 从DS18B20读取一个字节
* @param 无
* @retval 读到的数据
*/
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */
data |= b << i; /* 填充data的每一位 */
}
return data;
}接下来是写出一个字节的时序,代码如下:
/**
* @brief 写一个字节到DS18B20
* @param data: 要写入的字节
* @retval 无
*/
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0); /* Write 1 */
delay_us(2);
DS18B20_DQ_OUT(1);
delay_us(50);
}
else
{
DS18B20_DQ_OUT(0); /* Write 0 */
delay_us(50);
DS18B20_DQ_OUT(1);
delay_us(2);
}
data >>= 1; /* 右移,获取高一位数据 */
}
}五、与DS18B20使用1-wire协议通讯,读取温度值
(1)寄存器分布

从手册中可见,温度值是存放在两个字节中,一个高字节,一个低字节。只要读取出他们的值,拼接一下就得到我们想要的温度
(2)通讯过程实现
首先通讯前要初始化,
然后因为是点对点的网络,可以简化下,只发送 skip rom指令;
发送 Convert T 操作指令,
两个指令的说明,手册上摘抄如下:


按照说明书,实现温度转换的功能如下:
/**
* @brief 开始温度转换
* @param 无
* @retval 无
*/
static void ds18b20_start(void)
{
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0x44); /* convert */
}(2)从DS18B20获得高低字节,拼接成温度值
温度转换指令发送后,采集温度后DS18B20就会返回温度值的高低字节,mcu转入接收模式,获取这两个字节。完整的获取一次温度值的过程,包装成一个完整的函数:
/**
* @brief 从ds18b20得到温度值(精度:0.1C)
* @param 无
* @retval 温度值 (-550~1250)
* @note 返回的温度值放大了10倍.
* 实际使用的时候,要除以10才是实际温度.
*/
short ds18b20_get_temperature(void)
{
uint8_t flag = 1; /* 默认温度为正数 */
uint8_t TL, TH;
short temp;
ds18b20_start(); /* ds1820 start convert */
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0xbe); /* convert */
TL = ds18b20_read_byte(); /* LSB */
TH = ds18b20_read_byte(); /* MSB */
if (TH > 7)
{/* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致:
正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1
所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余,
我们这里先暂时没有作+1的处理,这里需要留意 */
TH = ~TH;
TL = ~TL;
flag = 0; /* 温度为负 */
}
temp = TH; /* 获得高八位 */
temp <<= 8;
temp += TL; /* 获得底八位 */
/* 转换成实际温度 */
if (flag == 0)
{ /* 将温度转换成负温度,这里的+1参考前面的说明 */
temp = (double)(temp+1) * 0.625;
temp = -temp;
}
else
{
temp = (double)temp * 0.625;
}
return temp;
}六、代码完成并演示效果
从手册看,DS18B20除了测量温度外,还有高低限制报警等功能,具体不再累述。
74HC595数码管显示,还有蜂鸣器的驱动,沿用第3课的代码,设定报警温度为28.0度,编译下载测试,一切正常。
b站视频如下:
https://www.bilibili.com/video/BV1vt421c76c?p=4
我要赚赏金
