1.什么是 DS18B20
温度传感器
2.封装图和线路图
VD 连接 VCC,GND 连接 GND,只剩下一个 IO 口 DQ 连接 P37
3.温度数据
- 1.DS18B20 是数字温度传感器
- 2.格式可设置为 9,10,11,12 默认为 12 格式
- 3.写入温度转换字 0x44 后传感器完成温度采集
- 4.温度数据以 16 位存储在 2 个 8 位存储单元中
- a.高 5 位 S 存储符号 0000 0 为 正度,1111 1 为负度,以补码形式存储度数
- b.写入读温度指令 0xBE 后 两个 8 位存储由低到高依次输出
4.操作过程
- a.初始化
- b.Rom 操作
- c.存储器操作指令
- d.处理数据
4.1 初始化
- a.由主机也就是单片机拉低总线引脚,持续时间为 480us 到 960us
- b.主机释放总线,即单片机拉高总线引脚,等待 15us 到 60us;
- c. DS18B20 拉低总线 60us 到 240us,即返回存在脉冲,表示传感器存在于总线上并可以接受操作指令了
- d.DS18B20 主动释放总线,总线引脚自动被电阻拉高
void InitDS18B20(void)
{
// 拉低
DS18B20 = 0;
// 持续600us
DelayT_10us(60);
// 拉高
DS18B20 = 1;
// 持续60us
DelayT_10us(6);
// 直到DS18B20高阻抗被拉高
while (DS18B20 != 1)
;
// 持续60us
DelayT_10us(6);
}
4.2 写一个字节
- a.图分两个部分,左边写 0,右边写 1
- b.左边,先看最上方, 60us<Tx<120us
- c.将总线拉低 60us-120us,DS18B20 最快 15us,典型 15us+15us,最慢 15us+15us+30us 时间内,采集到总线低电平,将 0 写入传感器
- d.右边,1us<tREC< 正无穷
- e.将总线拉低持续时间大于 1us 后立刻拉高,DS18B20 最快在 15us,典型 15us+15us,最慢在 15us+15us+30us 时间内,采集到总线高电平,将 1 写入传感器
void WrByte_18B20(unsigned char dat)
{
unsigned char flag;
for (int j = 1; j <= 8; j++)
{
// 拿到数据位
flag = dat & 0x01;
// 右移1位
dat >>= 1;
// 拉低
DS18B20 = 0;
// >1us
_nop_();
_nop_();
// 给数据位
DS18B20 = flag;
// 延时60us
DelayT_10us(60);
// 写入完成后拉高
DS18B20 = 1;
}
}
4.3 读一个字节
- a.图分为两部分,左侧为读 0
- b.读 0,主机拉低,15us 内读取总线值,此时总线处于低电平所以是读 0,然后释放总线。
- c.右侧为读 1
- d.主机拉低,延时超过 1us,15us 内读取总线值,此时总线在高电平上,所以是读 1
unsigned char RdByte_18B20(void)
{
unsigned char data, flag;
for (unsigned char j = 1; j <= 8; j++)
{
// 主机将DS18B20拉低
DS18B20 = 0;
// 延时2us
_nop_();
_nop_();
// 主机将DS18B20拉高
// 这里和温度计并无关系,是RYMCU 51单片机设计
// 读取数据时需要拉高
DS18B20 = 1;
// 延时2us
_nop_();
_nop_();
// 回到正常时序图,读取数据给flag
flag = DS18B20;
// 延时60us结束周期
DelayT_10us(6);
// 读出的值最低位在前面
data = (data >> 1) | (flag << 7)
}
return data;
}
4.4 Rom 操作指令
- a.0xCC
功能:Skip ROM( 跳过 ROM ),在单点总线系统中,此命令通过允许总线。
不给主机提供 64 位 ROM 编码而访问存储器操作来节省时间。如果在总线上
存在多于一个的从属器件而且在 Skip ROM 命令之后发出读命令,那么由于
多个从片同时发送数据,会在总线上发生数据冲突(漏极开路下拉会产生线
与的效果)。 - b.0x44
功能:Convert T(温度变换),这条命令启动一次温度转换而无需其他数据。
温度转换命令被执行,而后 DS18B20 保持等待状态。如果总线控制器在这
条命令之后跟着发出读时间隙,而 DS18B20 又忙于做时间转换的话,
DS18B20 将在总线上输出“0”,若温度转换完成,则输出“1”。如果使用寄生
电源,总线控制器必须在发出这条命令后立即起动强上拉,并保持 500ms。
4.5 存储器操作命令
- a.0xBE:Read Scratchpad(读暂存存储器)
读取将从字节 0 开始,一直进行下去,直到第 9(字节 8,CRC)字节读完。如果不
想读完所有字节,控制器可以在任何时间发出复位命令来中止读取。读取的前两
个字节为存储了温度值。
4.6 处理数据
-
a. 温度传感器初始化;
-
b. 写入字节 0xCC,跳过 ROM;
-
c. 写入字节 0x44,启动一次温度转换
-
d. 温度传感器初始化;
-
e. 写入字节 0xCC,跳过 ROM;
-
f. 写入字节 0xBE,发送读温度命令
-
g. 读取返回的前两个字节,并转化为温度值。
// 处理数据获取温度 unsigned int GetT_18B20(void) { unsigned char Temp_L, Temp_H; unsigned int Temp; InitDS18B20(); WrByte_18B20(0xCC); WrByte_18B20(0x44); InitDS18B20(); WrByte_18B20(0xCC); WrByte_18B20(0xBE); // 读一个8位 Temp_L = RdByte_18B20(); // 读第二个8位 Temp_H = RdByte_18B20(); // 两个8位组合成16位 Temp = ((unsigned int)Temp_H << 8) + Temp_L; return Temp; }
4.5 把以上函数写成.h 库供后面调用
4.5.1 1602.c
/*
* @Author: cuihaonan
* @Email: devcui@outlook.com
* @Date: 2021-03-29 22:39:41
* @LastEditTime: 2021-03-30 15:57:33
* @LastEditors: cuihaonan
* @Description: Basic description
* @FilePath: /sdcc-include/src/temperature/include/1602.c
* @LICENSE: NONE
*/
#include "../../../include/STC89xx.h"
#include "./1602.h"
SBIT(RS_1602, _P3, 6);
SBIT(RW_1602, _P3, 5);
SBIT(EN_1602, _P3, 4);
unsigned char RD_sta() //读状态函数
{
unsigned char sta;
RS_1602 = 0;
RW_1602 = 1; //进入读 1602 状态模式
EN_1602 = 1; //拉高使能信号
sta = P2; //将 1602 状态数据读取
EN_1602 = 0; //拉低使能,完成读操作
return sta; //将状态值返
}
void Ready() //空闲检测函数
{
P2 = 0xFF;
while (RD_sta() & 0x80)
; //bit7 等于 1 表示忙,一直检测到 0 为止
}
void WR_Cmd(unsigned char cmd) //写指令函数
{
Ready(); //检测 1602 是否处于空闲状态
RS_1602 = 0;
RW_1602 = 0; //进入写指令模式
P2 = cmd; //将指令数据输出
EN_1602 = 1; //拉高使能信号
EN_1602 = 0; //拉低使能,完成写操作
}
void WR_Dat(unsigned char dat) //写数据函数
{
Ready(); //检测 1602 是否处于空闲状态
RS_1602 = 1;
RW_1602 = 0; //进入写数据模式
P2 = dat; //将数据输出
EN_1602 = 1; //拉高使能信号
EN_1602 = 0; //拉低使能,完成写操作
}
void Init_1602() //1602 初始化函
{
WR_Cmd(0x38); //设置 16x2 显示,5x7 点阵,8 位数据接口
WR_Cmd(0x0C); //开显示,关闭光标
WR_Cmd(0x06); //读或写完一个字符后,地址指针、光标均加 1
WR_Cmd(0x01); //数据指针清零、所示显示清零
}
void Disp_1602_str(unsigned char row, unsigned char column, char *str)
{
unsigned char addr;
addr = (row - 1) * 0x40 + (column - 1); //组合成地址
WR_Cmd(0x80 + addr); //写地址命令
while (*str) //判断 str 字符串是否已结束
{
WR_Dat(*str++); //将 str 字符串数据依次写入
}
}
4.5.2 1602.h
/*
* @Author: cuihaonan
* @Email: devcui@outlook.com
* @Date: 2021-03-29 22:34:24
* @LastEditTime: 2021-03-30 15:57:25
* @LastEditors: cuihaonan
* @Description: Basic description
* @FilePath: /sdcc-include/src/temperature/include/1602.h
* @LICENSE: NONE
*/
#ifndef __1602_H__
#define __1602_H__
extern unsigned char RD_sta();
extern void Ready();
extern void WR_Cmd(unsigned char cmd);
extern void WR_Dat(unsigned char dat);
extern void Init_1602();
extern void Disp_1602_str(unsigned char row, unsigned char column, char *str);
#endif
4.5.3 DS18B20.c
/*
* @Author: cuihaonan
* @Email: devcui@outlook.com
* @Date: 2021-03-30 15:52:42
* @LastEditTime: 2021-03-31 22:35:09
* @LastEditors: cuihaonan
* @Description: Basic description
* @FilePath: /sdcc-include/src/temperature/include/DS18B20.c
* @LICENSE: NONE
*/
#include "./DS18B20.h"
#include "../../../include/STC89xx.h"
#include "../../../include/mcs51/compiler.h"
SBIT(DS18B20, _P3, 7);
void DelayT_10us(unsigned char count)
{
while (count--)
{
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
NOP();
}
}
void InitDS18B20(void)
{
// 拉低
DS18B20 = 0;
// 持续600us
DelayT_10us(50);
// 拉高
DS18B20 = 1;
// 持续60us
DelayT_10us(6);
// 直到DS18B20高阻抗被拉高
while (DS18B20 != 1)
;
// 持续600us
DelayT_10us(50);
}
void WrByte_18B20(unsigned char dat)
{
unsigned char flag;
for (int j = 1; j <= 8; j++)
{
// 拿到数据位
flag = dat & 0x01;
// 右移1位
dat >>= 1;
// 拉低
DS18B20 = 0;
// >1us
NOP();
NOP();
// 给数据位
DS18B20 = flag;
// 延时60us
DelayT_10us(6);
// 写入完成后拉高
DS18B20 = 1;
}
}
unsigned char RdByte_18B20(void)
{
unsigned char data, flag;
for (unsigned char j = 1; j <= 8; j++)
{
// 主机将DS18B20拉低
DS18B20 = 0;
// 延时2us
NOP();
NOP();
// 主机将DS18B20拉高
// 这里和温度计并无关系,是RYMCU 51单片机设计
// 读取数据时需要拉高
DS18B20 = 1;
// 延时2us
NOP();
NOP();
// 回到正常时序图,读取数据给flag
flag = DS18B20;
// 延时60us结束周期
DelayT_10us(6);
// 读出的值最低位在前面
data = (data >> 1) | (flag << 7);
}
return data;
}
// 处理数据获取温度
unsigned int GetT_18B20(void)
{
unsigned char Temp_L, Temp_H;
unsigned int Temp;
InitDS18B20();
WrByte_18B20(0xCC);
WrByte_18B20(0x44);
InitDS18B20();
WrByte_18B20(0xCC);
WrByte_18B20(0xBE);
// 读一个8位
Temp_L = RdByte_18B20();
// 读第二个8位
Temp_H = RdByte_18B20();
// 两个8位组合成16位
Temp = ((unsigned int)Temp_H << 8) + Temp_L;
return Temp;
}
4.5.4 DS18B20.h
/*
* @Author: cuihaonan
* @Email: devcui@outlook.com
* @Date: 2021-03-30 15:52:36
* @LastEditTime: 2021-03-31 10:13:05
* @LastEditors: cuihaonan
* @Description: Basic description
* @FilePath: /sdcc-include/src/temperature/include/DS18B20.h
* @LICENSE: NONE
*/
#ifndef __DS18B20_H__
#define __DS18B20_H__
// 模拟10us延时
extern void DelayT_10us(unsigned char count);
// 初始化
extern void InitDS18B20(void);
// 写入1字节
extern void WrByte_18B20(unsigned char dat);
// 读取1字节
extern unsigned char RdByte_18B20(void);
// 处理数据获取温度
extern unsigned int GetT_18B20(void);
#endif
5 结合液晶显示器应用
/*
* @Author: cuihaonan
* @Email: devcui@outlook.com
* @Date: 2021-03-29 22:30:39
* @LastEditTime: 2021-03-31 22:31:35
* @LastEditors: cuihaonan
* @Description: 单片机每秒采集一次温 度值,并将温度值显示到液晶显示模块上。用定时器中断来时实现 1s 的定时
* @FilePath: /sdcc-include/src/temperature/temperature.c
* @LICENSE: NONE
*/
#include "./include/1602.h"
#include "./include/DS18B20.h"
#include "../../include/STC89xx.h"
#include "../../include/mcs51/lint.h"
// 晶振频率
#define FOSC 11059200
// 定时器初始值计算
#define T_1ms (65536 - FOSC / 12 / 1000)
unsigned int Temp;
unsigned char str[10] = {0};
unsigned int count = 0;
void main()
{
// 定时器模式
TMOD = 0x01;
// 装载初始值
TL0 = T_1ms;
TH0 = T_1ms >> 8;
// 启动定时
TR0 = 1;
// 定时器中断
ET0 = 1;
// 总中断
EA = 1;
// 1602初始化
Init_1602();
// 第一行第三列开始显示
Disp_1602_str(1, 3, "temperature");
while (1)
{
// 1s进行一次温度采集显示
if (count >= 1000)
{
// 先关闭中断,防止定时器中断影响温度传感器的读写
EA = 0;
// 重新计时
count = 0;
// 采集16位温度信息
Temp = GetT_18B20();
// 假如拿到的16位为 0000 0111 1101 0000 >> 4 = 0000 0111 1101 = 125
// 125/10 = 12
str[0] = (Temp >> 4) / 10 + '0'; // 右移4位,获得温度整数部分
// 125%10 = 5
str[1] = (Temp >> 4) % 10 + '0';
str[2] = '.';
// 0000 0111 1101 1000 >>3 = 0000 0111 1101 1 = 125.5
// 125.5%10 = 5.5
// 有余数直接按0.5算
if ((Temp >> 3) % 10)
{
str[3] = '5';
}
// 否则按0算
else
{
str[3] = '0';
}
str[4] = '0';
Disp_1602_str(2, 3, str);
// 处理完毕开启中断
EA = 1;
}
}
}
// 定时器进入中断后
void timer0() __interrupt(1)
{
// 重装初始值
TL0 = T_1ms;
TH0 = T_1ms >> 8;
count++;
}
6.过程中我出现了什么问题?
由于使用的不是专业单片机程序编辑器,所以涉及到编译需要手动 sdcc
编译以后在使用 stcgal
刷程序。
当写到 第 14 章 DS18B20 模块时,需要封装 1602
和 DS18B20
的 C 语言头文件,封装完成后无法使用 sdcc
进行编译的问题。
6.1 原因是什么?
sdcc 无法使用链接去链接头文件进行编译,所以尝试了一次编译多个文件,也无法编译,所以最终一个个编译出来
6.2 如何解决?
- 1.使用 sdcc -c 将头文件全部编译到 lib 目录下,生成对应的 rel 文件
- 2.使用 sdcc 将全部 rel 文件拼接 生成 ihx 文件
如图所示,执行最后一条链接命令时,sdcc 生成了 lk 文件,里面有链接信息,并输出了全部的 ihx
最后使用 stcgal
直接刷程序到板子里
代码在 https://github.com/devcui/sdcc-include