蓝桥杯单片机备赛指南- 第十二讲:DS18B20 温度传感器

一、 DS18B20 硬件原理基础

DS18B20 是一款常用的数字温度传感器,其核心特点是采用**单总线(OneWire)**接口,仅需一根数据线即可实现双向通信。

1. 单总线通信(OneWire)

  • 硬件连接:数据线DQ 通常需要接一个4.7kΩ 上拉电阻。在蓝桥杯板子上,P1.4 引脚连接到DS18B20 的DQ 引脚。
  • 通信特点:
    • 单主多从:一个MCU 可以挂载多个传感器(通过64位ROM ID 区分)。
    • 时序严格:所有通信都由主机(单片机)发起,对延时精度要求极高(微秒级)。
    • 空闲状态:总线空闲时为高电平。

2. 核心时序

DS18B20 的操作严格遵循以下步骤:

  1. 初始化(Reset & Presence):主机拉低总线至少480us (复位脉冲),然后释放;等待DS18B20 拉低总线60-240us (存在脉冲)。
  2. 写时序(Write):
    • 写0:主机拉低总线60us 以上。
    • 写1:主机拉低总线1-15us,然后释放(被上拉电阻拉高)。
  3. 读时序(Read):主机拉低总线1us 后释放,在15us 内读取总线电平。

3. 温度转换流程(常用)

对于单个DS18B20,标准的操作流程如下:

  1. 初始化。
  2. 跳过ROM (Skip ROM, 0xCC):因为总线上只有一个设备,不需要匹配ID。
  3. 开始转换(Convert T, 0x44):启动温度测量。
  4. 延时:等待转换完成(12位精度最大需750ms,但在显示循环中通常不需要死等)。
  5. 初始化。
  6. 跳过ROM (0xCC)。
  7. 读取暂存器(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. 数码管显示界面

  1. 温度显示界面

显示当前采集的温度数据。

  • 格式:C(标识符) + 温度值(保留一位小数) + C(单位)。
  • 示例:C 熄灭 熄灭 2 6 . 7 C(代表26.7℃)。
  • 参数设置界面

显示当前设置的温度上下限。

  • 格式:P(标识符) + TMAX+ TMIN。
  • 示例:P 熄灭 熄滅 3 0 熄灭 0 2(上限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);
        }
    }
    

五、 学习小结

  1. 时序是生命线:DS18B20 对延时非常敏感,移植代码时务必检查Delay_OneWire是否适配当前单片机的晶振频率(蓝桥杯STC15 为12T 模式下12MHz)。
  2. 非阻塞读取:Read_T内部有延时或等待,尽量不要在定时器中断中调用,否则会干扰数码管扫描和按键消抖,造成显示闪烁或按键失灵。
  3. 浮点转定点:在数码管显示小数时,将float乘以10 或100 转换为long或int进行处理,可以避免使用昂贵的sprintf函数,且逻辑更清晰。
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐