蓝桥杯单片机备赛指南- 第十二讲:DS18B20 温度传感器
·
蓝桥杯单片机备赛指南- 第十二讲:DS18B20 温度传感器
一、 DS18B20 硬件原理基础
DS18B20 是一款常用的数字温度传感器,其核心特点是采用**单总线(OneWire)**接口,仅需一根数据线即可实现双向通信。
1. 单总线通信(OneWire)
- 硬件连接:数据线DQ 通常需要接一个4.7kΩ 上拉电阻。在蓝桥杯板子上,P1.4 引脚连接到DS18B20 的DQ 引脚。
- 通信特点:
- 单主多从:一个MCU 可以挂载多个传感器(通过64位ROM ID 区分)。
- 时序严格:所有通信都由主机(单片机)发起,对延时精度要求极高(微秒级)。
- 空闲状态:总线空闲时为高电平。
2. 核心时序
DS18B20 的操作严格遵循以下步骤:
- 初始化(Reset & Presence):主机拉低总线至少480us (复位脉冲),然后释放;等待DS18B20 拉低总线60-240us (存在脉冲)。
- 写时序(Write):
- 写0:主机拉低总线60us 以上。
- 写1:主机拉低总线1-15us,然后释放(被上拉电阻拉高)。
- 读时序(Read):主机拉低总线1us 后释放,在15us 内读取总线电平。
3. 温度转换流程(常用)
对于单个DS18B20,标准的操作流程如下:
- 初始化。
- 跳过ROM (Skip ROM, 0xCC):因为总线上只有一个设备,不需要匹配ID。
- 开始转换(Convert T, 0x44):启动温度测量。
- 延时:等待转换完成(12位精度最大需750ms,但在显示循环中通常不需要死等)。
- 初始化。
- 跳过ROM (0xCC)。
- 读取暂存器(Read Scratchpad, 0xBE):读取温度数据(低字节在前,高字节在后)。
二、 底层驱动详解( onewire.c& onewire.h)
底层驱动的核心在于微秒级延时的控制。
1. 延时函数
单总线对时间非常敏感,必须使用经过校准的延时函数。
C
// 软件延时函数,适配单总线时序
void Delay_OneWire(unsigned int t)
{
unsigned char i;
while(t--){
for(i=0;i<12;i++); // 根据晶振频率调整循环次数 (STC15 @ 12MHz)
}
}
2. 初始化函数
发送复位脉冲并检测存在脉冲。
C
// DS18B20初始化
bit Init_DS18B20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(12);
DQ = 0;
Delay_OneWire(80); // 拉低总线至少 480us
DQ = 1;
Delay_OneWire(10);
initflag = DQ; // 读取存在脉冲 (0代表存在)
Delay_OneWire(5);
return initflag;
}
3. 写字节函数( Write_DS18B20)
发送8 位数据,低位先行。
C
void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
DQ = 0; // 拉低总线,开始写时序
DQ = dat&0x01; // 写入最低位 (若为1则释放总线,由上拉电阻拉高)
Delay_OneWire(5); // 保持状态 (约 50us)
DQ = 1; // 释放总线,准备下一位
dat >>= 1;
}
Delay_OneWire(5);
}
4. 读字节函数( Read_DS18B20)
读取8 位数据,低位先出。
C
unsigned char Read_DS18B20(void)
{
unsigned char i;
unsigned char dat;
for(i=0;i<8;i++)
{
DQ = 0; // 拉低总线,启动读时序
dat >>= 1; // 准备接收下一位
DQ = 1; // 释放总线,准备读取
if(DQ) // 检测总线电平
{
dat |= 0x80; // 如果是高电平,将最高位置1 (随后会移位到正确位置)
}
Delay_OneWire(5); // 等待时隙结束
}
return dat;
}
5. 温度读取完整函数( Read_T)
这通常是在.c中封装好的高层函数,供main调用。
C
// 读取温度的完整流程
float Read_T()
{
unsigned char low, high;
unsigned int temp;
float t;
// 1. 启动转换
Init_DS18B20();
Write_DS18B20(0xCC); // 跳过ROM
Write_DS18B20(0x44); // 开始转换
// 注意:这里如果加延时会阻塞显示,通常利用主循环间隔代替延时,或者连续读取
// 蓝桥杯比赛中,为了代码简单,常在读取前先发起一次转换,读的是上一次的结果
// 2. 读取数据
Init_DS18B20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE); // 读暂存器
low = Read_DS18B20();
high = Read_DS18B20();
// 3. 数据合成
temp = (high << 8) | low;
// 4. 转换为实际温度 (DS18B20默认12位精度,分辨率0.0625)
t = temp * 0.0625;
return t;
}
三、 功能设计要求(PDF还原)
本讲任务是设计一个温度报警装置,具备温度显示、参数设置及精度调节功能。
1. 数码管显示界面
- 温度显示界面
显示当前采集的温度数据。
- 格式:
C(标识符) +温度值(保留一位小数) +C(单位)。 - 示例:
C熄灭熄灭26.7C(代表26.7℃)。 - 参数设置界面
显示当前设置的温度上下限。
- 格式:
P(标识符) +TMAX+TMIN。 - 示例:
P熄灭熄滅30熄灭02(上限30,下限02)。 - 交互:选中单元格以0.5秒/次的速度闪烁。
- 精度设置界面
设置DS18B20 的采集精度(9-12位)。
- 格式:
A(标识符) +精度等级。 - 示例:
A熄灭熄灭熄灭熄灭1(等级1)。
2. 按键模块
- S12 (界面切换):在温度显示-> 参数设置-> 精度设置之间循环。
- S13 (参数切换):在参数设置界面,切换选择TMAX 或TMIN。
- S14 (加):
- 参数界面:当前温度参数+1℃。
- 精度界面:精度等级+1。
- S15 (减):
- 参数界面:当前温度参数-1℃。
- 精度界面:精度等级-1。
3. LED 指示与亮度控制(PWM)
- 报警指示:
- L1 亮:温度> TMAX (高温)。
- L2 亮:TMIN ≤ 温度≤ TMAX (正常)。
- L3 亮:温度< TMIN (低温)。
- L4 亮:参数设置错误(如TMAX < TMIN)。
- 亮度等级(PWM调光):
- 高温状态:亮度等级1 (最暗,如占空比30%)。
- 正常状态:亮度等级2 (中等,如占空比60%)。
- 低温状态:亮度等级3 (最亮,如占空比90%)。
四、 程序设计与代码逻辑( main.c)
本程序的难点在于浮点数显示和PWM 亮度控制。
1. 全局变量管理
C
float Temperature = 0; // 存储实时温度 (浮点型)
u8 T_Limit[2] = {30, 20}; // [0]上限, [1]下限
u8 T_Limit_Set[2] = {0}; // 设置时的缓存
u8 LED_PWM = 0; // 当前目标亮度 (占空比阈值)
u8 Level = 0; // 亮度等级状态记录
2. 温度获取与显示处理( main& Seg_Proc)
由于Read_T耗时较长(几毫秒到几百毫秒),绝对不能在定时器中断中调用,必须放在主循环中。
C
// 主循环
void main()
{
// ... 初始化代码 ...
while(1)
{
// 在主循环中读取温度,避免影响中断响应
Temperature = Read_T();
Key_Proc();
Seg_Proc();
LED_Proc();
}
}
// 数码管处理
void Seg_Proc()
{
// 技巧:温度显示处理 (乘以10转为整数显示)
// 例如 26.7 -> 267,这样可以用取余操作分离出每一位
long temp_disp = Temperature * 10;
switch(Seg_Mode) {
case 0: // 温度显示
Seg_Buf[0] = 12; // 'C'
Seg_Buf[3] = temp_disp / 100; // 十位
Seg_Buf[4] = (temp_disp / 10) % 10; // 个位
Seg_Buf[5] = temp_disp % 10; // 小数一位
Seg_Point[4] = 1; // 点亮第4位的小数点 (26.7)
Seg_Buf[7] = 12; // 'C'
break;
case 1: // 参数设置
Seg_Buf[0] = 13; // 'P'
// 利用 Flash_Flag 实现闪烁
Seg_Buf[3] = (Flash_Flag && T_Limit_Set_Index==0)?10:T_Limit_Set[0]/10;
Seg_Buf[4] = (Flash_Flag && T_Limit_Set_Index==0)?10:T_Limit_Set[0]%10;
Seg_Buf[6] = (Flash_Flag && T_Limit_Set_Index==1)?10:T_Limit_Set[1]/10;
Seg_Buf[7] = (Flash_Flag && T_Limit_Set_Index==1)?10:T_Limit_Set[1]%10;
break;
}
}
3. PWM 亮度控制逻辑( LED_Proc& Timer0)
这是本讲的进阶考点。利用定时器中断产生一个高频计数器,模拟PWM 波形。
-
逻辑层(
LED_Proc) :根据温度设置LED_PWM的阈值。C
void LED_Proc(){ if(Temperature > T_Limit[0]){ // 高温 -> 亮度等级1 (假设30%亮度,即PWM<3时亮) LED_PWM = 3; LED_Buf[0] = 1; LED_Buf[1]=0; LED_Buf[2]=0; } else if(Temperature < T_Limit[1]){ // 低温 -> 亮度等级3 (假设90%亮度,即PWM<9时亮) LED_PWM = 9; LED_Buf[0]=0; LED_Buf[1]=0; LED_Buf[2] = 1; } else { // 正常 -> 亮度等级2 (假设60%亮度) LED_PWM = 6; LED_Buf[0]=0; LED_Buf[1] = 1; LED_Buf[2]=0; } // 错误检测 L4 if(T_Limit[0] < T_Limit[1]) LED_Buf[3]=1; else LED_Buf[3]=0; } -
驱动层(
Timer0_Isr):C
// 定时器中断 (假设 1ms 触发一次) void Timer0_Isr(void) interrupt 1 { static u8 pwm_count = 0; pwm_count++; if(pwm_count >= 10) pwm_count = 0; // 产生 0-9 的周期 // ... 正常的数码管扫描代码 ... if(++LED_Pos == 8) LED_Pos = 0; // LED扫描指针 // PWM 核心算法 // 逻辑:如果当前计数 < 设定阈值,则点亮;否则熄灭 // 阈值 LED_PWM 越大,点亮的时间占比越长,亮度越高 if(LED_Buf[LED_Pos] && pwm_count < LED_PWM){ // 调用底层驱动点亮 LED_Disp(LED_Pos, 1); } else { // 熄灭 (PWM 低电平期间) LED_Disp(LED_Pos, 0); } }
五、 学习小结
- 时序是生命线:DS18B20 对延时非常敏感,移植代码时务必检查
Delay_OneWire是否适配当前单片机的晶振频率(蓝桥杯STC15 为12T 模式下12MHz)。 - 非阻塞读取:
Read_T内部有延时或等待,尽量不要在定时器中断中调用,否则会干扰数码管扫描和按键消抖,造成显示闪烁或按键失灵。 - 浮点转定点:在数码管显示小数时,将
float乘以10 或100 转换为long或int进行处理,可以避免使用昂贵的sprintf函数,且逻辑更清晰。
更多推荐
所有评论(0)