蓝桥杯单片机第15届省赛2真题深度解析:基于STC15的温度与DAC控制系统(附满分代码详解)
本文详细解析了基于蓝桥杯开发板的嵌入式系统代码实现。主要内容包括:1)底层驱动模块(精确延时函数、74HC573锁存器控制、数码管动态扫描显示);2)外设操作(DS18B20温度传感器读取、PCF8591的DAC输出控制);3)按键扫描与状态切换逻辑(支持短按/长按检测和多功能复用);4)主循环与中断的协同设计。代码采用前后台系统架构,实现了温度监测、DAC输出、数码管显示等功能,重点展示了硬件时
·
首先,本代码是基于小蜜蜂老师的模板进行编写的,希望对大家有所帮助。
4T满分截图

部分题目展示:
下面直接进行代码解析
// ================= 1. 头文件引用 =================
#include <STC15F2K60S2.H> // STC15系列单片机的核心寄存器定义文件
#include <onewire.H> // 单总线协议驱动 (通常用于 DS18B20 温度传感器)
#include <intrins.H> // 内部函数库,包含 _nop_() 等空操作指令,用于精确延时
#include <iic.H> // I2C 总线协议驱动 (通常用于 PCF8591 DAC 或 AT24C02)
// ================= 2. 数码管段码表 (共阳极) =================
// 0-9, A, b, C, d, E, F (共16个字符)
// 0xC0 对应 '0', 0xF9 对应 '1' ...
unsigned char code SMG_NODOT[] = {
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
// 带小数点的段码表 (0-9)
// 原理:将 SMG_NODOT 的值与 0x80 (小数点位) 进行“或”运算
// 例如:0x40 = 0xC0 | 0x80 (这里数据看起来像是单独定义的,或者是特定硬件接法)
// 注意:通常共阳极小数点是 0x80,这里 0x40 可能是硬件接在 dp 位不同,或者是代码特有逻辑
unsigned char code SMG_DOT[] = {
0x40, 0x79, 0x24, 0x30, 0x19, 0x12, 0x02, 0x78, 0x00, 0x10
};
// ================= 3. 全局变量定义 (系统状态核心) =================
// --- 模式控制变量 ---
unsigned char mode = 1; // 主显示模式:1=温度界面, 2=DAC界面, 3=误差界面
unsigned char mode_temp = 0; // 温度子模式:0=自动模式, 1=手动模式
// --- 数据处理变量 ---
float volt; // 存储计算出的电压值 (用于 DAC 输出)
float temp; // 存储 DS18B20 读取的原始温度值
// --- LED 闪烁与计时控制 (用于定时器中断) ---
char js = 2; // 通用计时/闪烁控制变量 (控制闪烁频率)
char jo = 0; // 奇偶计数器 (用于交替闪烁逻辑)
// --- 温度相关状态机变量 ---
char temp_smg; // 最终显示在数码管上的温度值 (可能包含修正)
char temp_last; // 上一次采样的温度值 (用于计算温差)
char temp_err_led; // 温度变化差值 (用于控制 LED 报警闪烁)
char temp_count; // 闪烁持续时间计数器
char temp_flag = 0; // 温度状态标志 (0=无变化, 1=升温, 2=降温)
char temp_flag_lx = 0; // 温度连续变化标志 (用于秒计数逻辑)
// --- 误差与修正 ---
char temp_err = 0; // 用户设定的误差修正值 (通过按键调整)
char temp_err_real; // 实际应用的误差值
// --- DAC 与 继电器控制 ---
unsigned char dac_dat = 100; // DAC 输出数值 (0-255),默认初始值 100
unsigned char jdq_mode = 0; // 继电器锁状态:0=解锁(可操作), 1=上锁(不可操作)
// --- 硬件状态输出缓存 ---
unsigned char led_state = 0xff; // LED 状态缓存 (0xff 表示全灭,共阳极)
unsigned char jdq_state = 0x00; // 继电器控制缓存 (0x00 可能表示吸合或断开,视电路而定)
// --- 时间控制 ---
char second_r = 0; // 秒计数器 (右/临时)
char second_l = 0; // 秒计数器 (左/超时控制)
// --- 按键处理 ---
unsigned char key_flag; // 按键按下标志位 (用于中断计数)
unsigned char key_js; // 按键长按计时器 (用于区分短按/长按)
一.底层驱动
1.延时函数
// 1ms 延时函数 (基于12MHz晶振)
void Delay1ms(void) {
unsigned char data i, j;
i = 12; j = 169;
do { while (--j); } while (--i);
}
// 200us 延时函数
void Delay200us(void) {
unsigned char data i, j;
i = 3; j = 82;
do { while (--j); } while (--i);
}
// 50us 延时函数 (用于按键消抖)
void Delay50us(void) {
unsigned char data i;
_nop_(); _nop_(); // 空操作指令,微调时间
i = 147;
while (--i);
}
注释解释:蓝桥杯开发板通常使用12MHz晶振。这些函数通过嵌套循环消耗CPU时间来实现精确延时。Delay1ms用于数码管动态扫描消隐;Delay50us专门用于按键按下时的硬件消抖。
2. 74HC573 锁存器控制
// 选择锁存器通道
void HC573(unsigned char channel) {
switch(channel) {
case 4: P2=(P2&0X1F)|0X80; break; // L1-L8 LED & 继电器
case 5: P2=(P2&0X1F)|0Xa0; break; // BUZZ & RELAY (蜂鸣器/继电器使能)
case 6: P2=(P2&0X1F)|0Xc0; break; // SMG_BIT (数码管位选)
case 7: P2=(P2&0X1F)|0Xe0; break; // SMG_IO (数码管段选)
}
}
- 注释解释:这是蓝桥杯开发板最核心的逻辑。
P2口的高3位(P2.7-P2.5)用来选择控制哪个外设。 - 关键技巧:
P2 & 0x1F(00011111) 是为了保留 P2 低5位的值不变,只修改高3位。如果不这样做,可能会意外改变正在运行的外设状态。
3.数码管显示操作
// 初始化系统状态
void init_system() {
HC573(4); P0=0Xff; // 关闭所有LED
HC573(5); P0=0x00; // 关闭蜂鸣器和继电器
}
// 设置数码管某一位显示什么
void set_smg(unsigned char pos, unsigned char value) {
HC573(6); P0=0X01<<pos; // 选择位选 (pos位)
HC573(7); P0=value; // 输出段选码 (显示内容)
Delay1ms(); // 保持1ms (动态扫描)
HC573(6); P0=0X01<<pos; // 消隐:再次选中该位
HC573(7); P0=0xff; // 段选全灭 (防止拖影)
}
// 全部数码管熄灭
void smg_xy() {
HC573(6); P0=0xff; // 位选全0 (不选中任何位)
HC573(7); P0=0xff; // 段选全1 (共阳极,1为灭)
}
- 注释解释:
set_smg函数实现了动态扫描的核心。先选中某一位(位选),输出数据(段选),延时保持,最后必须进行“消隐”操作(先关闭段选再关闭位选,或像代码中那样在关闭位选前将段选全灭),否则会出现串位显示。
二.外设
1.DS18B20 温度读取
void ds18b20()
{
unsigned char MSB, LSB; // 定义高8位和低8位变量
unsigned int dat; // 定义合并后的16位数据变量
// --- 步骤 1: 启动温度转换 ---
init_ds18b20(); // 1. 复位 DS18B20 (发送复位脉冲)
SMG_SHOW(); // 【关键技巧】: 在等待总线稳定或转换开始时,刷新数码管,防止熄灭
Write_DS18B20(0xcc); // 2. 跳过 ROM 匹配指令 (因为总线上只有一个传感器)
Write_DS18B20(0x44); // 3. 发送温度转换指令 (开始进行 A/D 转换)
// --- 步骤 2: 模拟延时等待 ---
// 官方驱动通常需要延时 750ms 等待转换完成。
// 这里没有使用死板的 Delay(750ms),而是通过多次调用 SMG_SHOW() 来消耗时间。
// 这样既满足了时序要求,又维持了人机交互界面的刷新。
SMG_SHOW(); // 消耗时间片 1
init_ds18b20(); // 4. 再次复位 DS18B20,准备读取
SMG_SHOW(); // 消耗时间片 2
SMG_SHOW(); // 消耗时间片 3 (此时 DS18B20 内部转换已完成)
// --- 步骤 3: 读取温度数据 ---
Write_DS18B20(0xcc); // 5. 再次跳过 ROM
Write_DS18B20(0xbe); // 6. 发送读取暂存器指令 (Read Scratchpad)
LSB = Read_DS18B20(); // 7. 读取低 8 位 (小数部分在低4位)
MSB = Read_DS18B20(); // 8. 读取高 8 位 (符号位在高5位)
// --- 步骤 4: 数据合并与处理 ---
dat = MSB;
dat = (MSB << 8) | LSB; // 将高8位左移8位,与低8位进行“或”运算,合并成16位数据
// 判断温度正负 (检查高5位是否全为0)
// 0xf800 = 1111 1000 0000 0000
// 如果高5位是0,说明是正温度
if ((dat & (0xf800)) == 0x0000)
{
temp = dat * 0.0625; // 正温度直接乘以分辨率 0.0625
}
// 注意:代码中省略了负温度的处理逻辑,可能默认只处理正温或通过其他方式处理
// --- 步骤 5: 更新全局状态 ---
temp_last = temp_smg; // 保存上一次的显示温度 (用于计算温差)
temp_smg = temp + temp_err_real; // 计算最终显示温度 (原始温度 + 用户修正值)
temp_err_led = temp_smg - temp_last; // 计算本次温度变化量 (用于驱动 LED 闪烁逻辑)
}
- 注释解释:
- 代码中穿插了
SMG_SHOW(),这是为了在等待DS18B20转换时(转换需要几百毫秒)刷新数码管,防止数码管熄灭。 temp_err_led记录了温度的变化量,这是为了在中断里判断温度是升高了还是降低了,从而控制LED闪烁。
- 代码中穿插了
2.DAC 输出控制 (PCF8591)
// ================= 底层驱动:I2C 通信协议 =================
// 功能:向 PCF8591 发送一个字节的数据,控制 DAC 输出电压
void dac_output(unsigned char dat)
{
I2CStart(); // 1. 发送 I2C 起始信号
I2CSendByte(0x90); // 2. 发送设备地址 (PCF8591 写地址)
// 0x90 = 1001 0000 (A2-A0接地,LSB=0表示写)
I2CWaitAck(); // 3. 等待从机应答
I2CSendByte(0x43); // 4. 发送控制字节
// 0x43 = 0100 0011
// Bit6=1 (使能DAC), Bit0-1=11 (选择通道3)
I2CWaitAck(); // 5. 等待从机应答
I2CSendByte(dat); // 6. 发送数据 (0-255)
// 这个值决定了输出电压的大小
I2CWaitAck(); // 7. 等待从机应答
I2CStop(); // 8. 发送 I2C 停止信号,释放总线
}
// ================= 业务逻辑:DAC 策略控制 =================
// 功能:根据模式决定输出什么电压
void dac_chuli()
{
unsigned char dat; // 定义 DAC 的数字量 (0-255)
// 模式 0:自动模式 (温度控制电压)
if(mode_temp == 0)
{
// 分段函数逻辑
if(temp_smg <= 10)
{
volt = 2; // 温度 <= 10°C,固定输出 2V
}
else if(temp_smg > 10 && temp_smg < 40)
{
// 10°C < 温度 < 40°C,线性增长
// 斜率 k = (5V - 2V) / (40 - 10) = 3 / 30 = 0.1 V/°C
volt = 2 + 0.1 * (temp_smg - 10);
}
else if(temp_smg >= 40)
{
volt = 5; // 温度 >= 40°C,固定输出 5V (饱和)
}
// 将电压转换为 DAC 数字量
// 公式:D = (V_out / V_ref) * 255
// 这里 V_ref = 5V,所以 dat = volt * 51
dat = volt * (255 / 5.0);
dac_output(dat); // 调用底层驱动输出
}
// 模式 1:手动模式 (用户控制电压)
else if(mode_temp == 1)
{
dac_output(dac_dat); // 直接输出用户设定的值 (dac_dat 由按键控制)
}
}
注释解释:dac_chuli 是一个典型的“策略模式”函数。它根据 mode_temp 的值决定是走自动算法(温度查表)还是直接输出用户设定的手动值。
3.数码管显示逻辑
void SMG_SHOW()
{
// 根据全局变量 mode 的值,决定显示哪个界面
switch(mode)
{
// ================= 模式 1:温度显示界面 =================
case 1:
// 1. 显示单位/标识符
// 在第0位数码管显示 'C' (假设 SMG_NODOT[12] 是 'C' 的段码)
set_smg(0, SMG_NODOT[12]);
// 2. 温度数值显示 (动态位数处理)
// 情况A: 温度 < 10 (个位数)
if(temp_smg <= 9)
{
// 只显示个位 (第7位),高位自动熄灭 (消隐)
set_smg(7, SMG_NODOT[temp_smg % 10]);
}
// 情况B: 10 <= 温度 <= 999 (十位或百位)
else if(temp_smg <= 999)
{
// 显示十位 (第6位) 和 个位 (第7位)
// 注意:这里代码似乎省略了百位的显示逻辑,或者题目要求只显示后两位
set_smg(6, SMG_NODOT[temp_smg / 10]); // 取十位
set_smg(7, SMG_NODOT[temp_smg % 10]); // 取个位
}
break;
// ================= 模式 2:DAC 数值显示界面 =================
case 2:
// 1. 显示单位/标识符
// 在第0位数码管显示 'H' (假设 SMG_NODOT[10] 是 'H' 的段码,代表Hex或High)
set_smg(0, SMG_NODOT[10]);
// 2. DAC数值显示 (0-255)
// 情况A: 个位数 (0-9)
if(dac_dat <= 9)
{
set_smg(7, SMG_NODOT[dac_dat % 10]);
}
// 情况B: 十位数 (10-99)
else if(dac_dat <= 99)
{
set_smg(6, SMG_NODOT[dac_dat / 10]); // 十位
set_smg(7, SMG_NODOT[dac_dat % 10]); // 个位
}
// 情况C: 百位数 (100-255)
else if(dac_dat <= 999)
{
set_smg(5, SMG_NODOT[dac_dat / 100]); // 百位
set_smg(6, SMG_NODOT[(dac_dat / 10) % 10]); // 十位
set_smg(7, SMG_NODOT[dac_dat % 10]); // 个位
}
break;
// ================= 模式 3:误差/偏差显示界面 =================
case 3:
// 1. 显示单位/标识符
// 在第0位显示自定义符号 (0x8c),可能是 '-' 或 'E' (Error)
set_smg(0, 0x8c);
// 2. 误差数值显示 (带符号显示)
// 情况A: 正误差或零 (>= 0)
if(temp_err >= 0)
{
// 直接显示数值
set_smg(7, SMG_NODOT[temp_err]);
}
// 情况B: 负误差 (< 0)
else if(temp_err < 0)
{
// 先显示负号 '-' (0xbf 是共阳极 '-' 的段码)
set_smg(6, 0xbf);
// 再显示数值的绝对值 (-temp_err)
set_smg(7, SMG_NODOT[-temp_err]);
}
break;
}
// 3. 消隐处理
// 关闭所有位选,防止上一轮显示的残影(鬼影)
smg_xy();
}
- 注释解释:这个函数非常繁琐但重要。它根据全局变量
mode(模式)的不同,调用set_smg在数码管的不同位置显示不同的数组内容。注意处理了多位数的拆分(/10,%10)。
4.按键扫描
void key_scan()
{
// ================= 检测 S12 (模式切换键) =================
// 技巧:行反转法扫描矩阵按键
// 0x08 = 0000 1000, ~0x08 = 1111 0111
// 将 P3.3 拉低,如果按键按下,P3.5 也会被拉低
P3 = ~0X08;
if(P35 == 0) // 检测 P3.5 是否为低电平
{
Delay50us(); // 软件消抖
if(P35 == 0)
{
// 功能:切换主显示模式 (1->2->3->1)
mode++;
if(mode == 4)
{
mode = 1;
// 当切回模式1时,将用户设定的误差值写入实际误差变量
temp_err_real = temp_err;
}
// 等待松手 (死循环),期间保持系统刷新,防止死机
while(P35 == 0)
{
dac_chuli(); // 保持 DAC 输出稳定
SMG_SHOW(); // 保持数码管显示
}
}
}
// ================= 检测 S16 (加/开关键) =================
// 注意:这里直接检测 P34,说明 S16 和 S17 可能是独立按键,或者 P3 口已配置好
if(P34 == 0)
{
Delay50us();
if(P34 == 0)
{
// 功能复用:根据 mode 决定按键功能
if(mode == 1) // 模式1:控制继电器
{
if(jdq_mode == 0) // 只有在解锁状态下才能操作
jdq_state |= 0x10; // 继电器吸合 (P0.4置0)
}
else if(mode == 2) // 模式2:DAC 数值增加
{
dac_dat += 5; // 步进为 5
if(dac_dat >= 255) dac_dat = 255; // 限幅
}
else if(mode == 3) // 模式3:误差值增加
{
temp_err++;
if(temp_err >= 9) temp_err = 9; // 限幅
}
while(P34 == 0)
{
dac_chuli();
SMG_SHOW();
}
}
}
// ================= 检测 S13 (功能/长按键) =================
P3 = ~0X04; // 0000 0100 -> 1111 1011 (将 P3.2 拉低)
if(P35 == 0) // 检测 P3.5
{
Delay50us();
if(P35 == 0)
{
// 核心逻辑:长按检测
// 在按住按键期间,不断置位 key_flag,并刷新显示
while(P35 == 0)
{
dac_chuli();
SMG_SHOW();
key_flag = 1; // 通知定时器中断:按键被按住了
}
// 松手后进行判断
// key_js 是在定时器中断中累加的 (每50ms加1)
// 30 * 50ms = 1500ms = 1.5秒
if(key_js <= 30) // 短按 (小于1.5秒)
{
// 切换 自动/手动 模式
if(mode_temp == 0) mode_temp = 1;
else if(mode_temp == 1) mode_temp = 0;
key_js = 0; // 清零计数器
key_flag = 0;
}
else // 长按 (大于1.5秒)
{
// 切换 继电器锁 状态 (解锁 <-> 上锁)
if(jdq_mode == 0) jdq_mode = 1;
else if(jdq_mode == 1) jdq_mode = 0;
key_flag = 0;
key_js = 0;
}
}
}
// ================= 检测 S17 (减/关关键) =================
if(P34 == 0)
{
Delay50us();
if(P34 == 0)
{
// 功能复用:与 S16 逻辑相反
if(mode == 1) // 模式1:控制继电器
{
if(jdq_mode == 0)
jdq_state &= ~(0x10); // 继电器断开 (P0.4置1)
}
else if(mode == 2) // 模式2:DAC 数值减小
{
dac_dat -= 5;
if(dac_dat <= 0) dac_dat = 0; // 限幅
}
else if(mode == 3) // 模式3:误差值减小
{
temp_err--;
if(temp_err <= -9) temp_err = -9; // 限幅
}
while(P34 == 0)
{
dac_chuli();
SMG_SHOW();
}
}
}
}
- 注释解释:按键逻辑是代码中最复杂的部分。
- 短按/长按检测:
S13按键通过key_flag和key_js(在中断中计数)来判断用户是短按(切换自动/手动)还是长按(切换上锁/解锁)。这是通过在while(按键按下)循环中不重置计数器实现的。 - 功能复用:同一个按键(S16/S17)在不同
mode下有不同的功能(控制继电器、调节DAC、调节误差)。
- 短按/长按检测:
三.主循环与中断
1.定时器0 中断
void init_timer0()
{
// 1. 设置工作模式
// TMOD = 0x01 -> 0000 0001
// 低4位控制 T0: GATE=0, C/T=0 (定时), M1=0, M0=1 (模式1: 16位定时器)
TMOD = 0X01;
// 2. 装载初值 (50ms @ 12MHz)
// 65536 - 50000 = 15536
TH0 = (65536 - 50000) / 256; // 高8位
TL0 = (65536 - 50000) % 256; // 低8位
// 3. 开启中断
ET0 = 1; // 允许定时器0中断
EA = 1; // 允许总中断
// 4. 启动定时器
TR0 = 1; // 开始计数
}
void timer0() interrupt 1
{
// 1. 定时器重载 (产生50ms基准时钟)
// 晶振12MHz,机器周期1us。65536-50000 = 15536,即50ms溢出一次
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 2. 按键长按计数器 (配合S13功能)
// 如果按键扫描中检测到按键按下(key_flag=1),这里每50ms加1
// 约计数30次(1.5秒)判定为长按,用于区分S13的短按(切模式)和长按(上锁)
if (key_flag == 1)
{
key_js++;
}
// 3. 自动/手动模式指示 (LED1)
// mode_temp: 0=自动, 1=手动
// 逻辑:自动时LED1灭(0),手动时LED1亮(1)
if (mode_temp == 0)
{
led_state &= (~0x01); // LED1 灭
}
else
{
led_state |= 0x01; // LED1 亮
}
// 4. 温度变化报警闪烁逻辑 (LED2, LED3)
// js 是一个全局计数器,用于控制闪烁频率
if (js > 0) js--;
else if (js == 0)
{
// LED2 闪烁逻辑 (温度升高/维持)
// temp_err_led > 0 表示温度在升高
if ((temp_err_led >= 1 || temp_flag == 1) && temp_flag != 2)
{
temp_flag = 1; // 标记状态为“升高”
temp_count++; // 计数器累加
led_state &= (~0x02); // LED2 亮 (共阳极,0为亮)
// 闪烁40次 (约2秒) 后熄灭并重置
if (temp_count >= 40)
{
led_state |= 0x02; // LED2 灭
temp_count = 0;
temp_flag = 0; // 重置状态
}
}
// LED3 闪烁逻辑 (温度降低)
// temp_err_led < 0 表示温度在降低
else if (temp_err_led <= -1 || temp_flag == 2)
{
temp_flag = 2; // 标记状态为“降低”
temp_count++;
led_state &= (~0x04); // LED3 亮
if (temp_count >= 40)
{
led_state |= 0x04; // LED3 灭
temp_count = 0;
temp_flag = 0;
}
}
// 5. 秒计数与特殊闪烁 (LED4)
// 只要温度在变化(temp_err_led非0),就启动秒计数
if ((temp_err_led >= 1 || temp_err_led <= -1) || temp_flag_lx == 1)
{
temp_flag_lx = 1;
second_r++; // 计数器r
second_l++; // 计数器l
// 这里实现了一个“慢速闪烁”逻辑
// 每4次中断(约200ms)触发一次状态翻转
if ((second_r - 1) % 4 == 0)
{
jo++; // 翻转计数器
if (jo % 2 != 0) led_state &= (~0x08); // 奇数次:亮
if (jo % 2 == 0) led_state |= 0x08; // 偶数次:灭
}
// 60秒超时保护
if (second_l >= 60)
{
led_state |= 0x08; // 强制熄灭LED4
temp_flag_lx = 0;
second_l = 0;
second_r = 0;
jo = 0;
}
}
}
// 6. 锁状态指示 (LED8)
// jdq_mode: 0=解锁, 1=上锁
// 逻辑:解锁时灭,上锁时亮
if (jdq_mode == 0)
{
led_state &= (~0x80); // LED8 灭
}
else if (jdq_mode == 1)
{
led_state |= 0x80; // LED8 亮
}
}
- 注释解释:中断服务程序(ISR)不应该做太耗时的操作。这里的逻辑主要是更新变量(如
key_js计数,second_r计时),具体的硬件操作(P0赋值)是在main循环中完成的。
2.main主函数
void main() {
init_system(); // 初始化硬件
init_timer0(); // 开启定时器
while(1) {
dac_chuli(); // 1. 处理DAC输出 (实时性要求高)
ds18b20(); // 2. 读取温度 (包含延时,放在循环中)
key_scan(); // 3. 扫描按键
SMG_SHOW(); // 4. 刷新数码管
// 5. 输出 LED 和 继电器 状态
HC573(4); P0=led_state; // LED状态由中断计算得出
HC573(5); P0=jdq_state; // 继电器状态由按键设置
}
}
- 注释解释:主循环采用了前后台系统(Super Loop)架构。
- 顺序很重要:先处理数据(DAC、温度),再处理交互(按键、显示),最后输出硬件状态。
- 分工明确:中断负责“感知”时间流逝和按键长短,主循环负责“执行”具体的业务逻辑。
四.总结
我这篇代码虽然有些地方(写法比较“野”,但它完美展示了蓝桥杯考察的核心,拿到比赛上是完全没问题的:
- 硬件基础:锁存器、I2C、单总线时序。
- 算法逻辑:浮点数转整型显示、电压映射、温度区间判断。
- 状态机:通过
mode和mode_temp控制程序流程。 - 抗干扰:按键消抖、数码管消隐。
更多推荐



所有评论(0)