蓝桥杯单片机综合题:多传感器时间片调度与中断时序设计

发布时间:2026/10/13 23:34:25
蓝桥杯单片机综合题:多传感器时间片调度与中断时序设计 简介面向蓝桥杯单片机竞赛备赛者的完整工程资料包围绕温度显示、超声波测距、光敏D/A显示、红外接收数据四个典型模块展开并以矩阵键盘S7、S11、S15、S19实现功能切换适合需要模块化练习和赛题复现的选手使用。压缩包共39个文件、约90KB包含7个.c源文件、7个.h头文件以及Keil工程文件.uvproj/.uvopt、编译生成的.hex烧录文件、.lst列表文件、.obj目标文件和.m51链接映射能够从源码到烧录完整还原开发环境。对应DS18B20温度采集、IIC通信、红外解码、数码管显示SMG、独立/矩阵按键扫描等底层驱动每个功能模块独立成.c/.h源码结构清晰便于对照学习或裁切复用可配合蓝桥杯开发板进行验证。已有1640余人学习下载适合需要直接获得可运行工程、快速验证模块效果并在此基础上进行二次开发的单片机学习者。1. 蓝桥杯单片机这道综合题考的其实是“时间片的切割”蓝桥杯单片机赛题里把温度显示、超声波测距、光敏D/A显示、红外接收数据显示放进同一道大题看起来是四个独立外设实际考的是同一件事一颗单片机怎么同时伺候四路采集和一路数码管刷新。温度要卡单总线时序超声波要等回波红外要靠电平宽度解协议光敏又要ADC和PWM联动任何一个环节用 while 死等另外三个模块就得跟着卡壳。这道题适合两类人一类是正在备赛蓝桥杯单片机组的学生另一类是做传感器采集小系统但总被中断和延时折磨的开发者。下面按“先立时序规则再写采集代码最后给排错套路”的顺序展开争取你照着敲完四个模块能稳定显示在同一块板上。2. 温度显示与超声波测距两条并行总线的用时策略2.1 温度传感器单总线的复位时序不能被任何中断打断温度模块最常见的器件是 DS18B20单总线通信。单总线空闲时靠上拉电阻保持高电平主机发起一次通信要先把总线拉低至少 480us 产生复位脉冲从机在 60us 到 240us 之间拉低总线作为应答。接下来的读时序和写时序每个 slot 只有 60us 到 120us中间一旦被定时器中断插一脚脉冲宽度超出器件允许范围读回来的就是 0xFF 或者 0x50 这种固定错值。我一般这么处理复位和读温度#define DQ P1^0 void delay_us(unsigned int us) { unsigned int i; while (us--) { for (i 0; i 12; i); // 12MHz 1T 单片机循环约延迟1us需实测校准 } } bit ds_reset(void) { bit presence; EA 0; // 关总中断避免数码管刷新打断复位脉冲 DQ 0; delay_us(500); // 复位脉冲480us以上 DQ 1; // 释放总线等待上拉 delay_us(70); // 等从机应答窗口 presence DQ; // 读应答0有器件1无 delay_us(400); EA 1; return presence; } void ds18b20_read(void) { unsigned char raw_l, raw_h; int temp; if (ds_reset() 1) return; // 总线上没器件直接退出 ds_write_byte(0xCC); // 跳过 ROM 匹配 ds_write_byte(0x44); // 启动温度转换 delay_ms(120); // 比赛常用9位精度96ms够用 if (ds_reset() 1) return; ds_write_byte(0xCC); ds_write_byte(0xBE); // 读暂存器 raw_l ds_read_byte(); raw_h ds_read_byte(); temp (signed char)raw_h; // 先按有符号扩展处理负温度 g_temp (temp 4) | (raw_l 4); // 忽略小数位显示整数温度 }这段代码的关键在EA 0和EA 1这对开关。复位脉冲不像普通的 GPIO 输出它要求在 480us 到 960us 之间持续拉低如果你把复位写到一半被数码管刷新中断打断低电平时间被延长器件会当成一次新的复位请求后面所有读写全部错位。关中断只包住一次复位时序读字节的每个 slot 我也同样处理。整个温度转换过程里最耗时的是delay_ms(120)这段不关中断因为器件在转换期间不需要总线通信中断随便进出。delay_ms(120)对应 9 位分辨率。DS18B20 出厂默认 12 位精度转换要 750ms赛场上一秒钟都等不起所以要在初始化时写配置寄存器把精度降到 9 位或 10 位。降精度影响的只是 0.5 摄氏度的小数位整数温度完全不受影响。如果读回来的温度一直是 0先检查是不是没降精度再看是不是每次复位都成功。2.2 超声波测距Echo 的脉冲宽度是测距的全部超声波模块的原理比 DS18B20 简单得多给 Trig 脚一个大于 10us 的高电平脉冲模块内部发出 8 个 40kHz 脉冲碰到障碍物返回Echo 脚输出一个高电平高电平持续时间就是声音往返的总时间。声速大约 340m/s距离等于时间乘以声速再除以二换算下来就是时间us除以 58 约等于厘米数。unsigned int hc_sr04_get_cm(void) { unsigned int tick; TRIG 0; delay_us(2); TRIG 1; delay_us(12); // 触发脉冲保持12us大于10us即可 TRIG 0; while (ECHO 0); // 等回波前沿 T0 0; // 超声波专用定时器清零 while (ECHO 1) { // 等回波后沿 if (T0 40000) { // 超过40ms判定超时防卡死 tick 0; break; } } tick T0; if (tick 0) return 0; // 超时返回0 return tick / 58; // us/58约等于cm }这里必须有一个独立的定时器专门给回波计时不能跟数码管的刷新定时器混用。T0 配成 16 位定时器不重装清零后让它自己跑Echo 变低立刻读出计数值。超时保护是关键如果测量范围内没有障碍物Echo 会一直保持低电平while (ECHO 0)会死等如果障碍物太远回波超过 40ms 还没回来模块已经进入下一次测量周期不保护就会卡死主循环。40ms 对应的最大测距范围大约 6.8 米对桌面级赛题完全够用。声速会随温度变化0℃ 时约 331m/s40℃ 时约 354m/s两米距离能差出 3 厘米左右。如果题目要求高精度可以拿 DS18B20 读到的温度去做声速补偿一般赛题不追究这点。2.3 两个传感器交替巡检不阻塞数码管刷新的时间片写法DS18B20 转换要几十毫秒超声波回波最长要 20 多毫秒两个模块如果各用各的阻塞延时叠在一起就是上百毫秒的空白。这段时间里数码管会暗掉按键会失灵红外帧也可能被漏掉。我习惯把两个传感器放在同一个 200ms 时间片里交替执行奇数拍读温度偶数拍测距离void main_loop(void) { static unsigned char measure_count 0; if (ms_tick - last_tick 200) { // ms_tick由定时器1每次中断加1 last_tick ms_tick; if (measure_count 1) { ds18b20_read(); // 奇数拍读温度 display_buffer[0] g_temp / 10; display_buffer[1] g_temp % 10; } else { g_dist hc_sr04_get_cm(); // 偶数拍测距离 display_buffer[2] g_dist / 10; display_buffer[3] g_dist % 10; } measure_count; } show_digit(); // 数码管动态刷新无条件执行 }温度每隔 200ms 读一次DS18B20 有足够时间完成转换不需要在代码里额外 delay 等待。超声波同样隔 200ms 测一次相邻两次测量间隔远大于模块的 60ms 测量周期数据不会重叠。这样安排之后数码管刷新函数每毫秒执行一次四个传感器各自占用自己的时间片谁也不会卡住谁。3. 光敏 D/A 显示ADC 读取与 PWM 输出的映射陷阱3.1 ADC 读光敏读到的不是“亮度”而是“分压”光敏电阻本身是一个可变电阻光照越强阻值越低。它要和一只固定电阻组成分压电路把电阻变化转换成电压变化ADC 才能真正读进来。注意接法决定了映射方向如果光敏电阻在上面、固定电阻在下面接地光照强时 ADC 输入电压低反过来接光照强时电压高。不同开发板接法不一样我自己就翻过一次车光敏值变化方向跟题目要求的相反一遮光数值反而变大。读取 ADC 的代码本身很直接unsigned int adc_read(unsigned char ch) { unsigned int val; ADC_CONTR 0x80 | 0x08 | (ch 0x07); // 启动ADC选择通道 delay_us(20); // 采样保持时间 while (!(ADC_CONTR 0x10)); // 等ADC_FLAG置1 ADC_CONTR ~0x10; // 清标志位 val (ADC_RES 8) | ADC_RESL; // 10位结果合成 return val; }ADC 转换完成后标志位必须手动清零否则下次启动时状态判断会出错。ADC_RES和ADC_RESL两个寄存器怎么拼要看 ADC 配置成左对齐还是右对齐。左对齐时ADC_RES是高 8 位ADC_RESL是低 2 位放到高两位合成公式就是(ADC_RES 8) | ADC_RESL。右对齐时结果要换成(ADC_RES 2) | (ADC_RESL 6)。蓝桥杯赛题常见设置是左对齐因为只取高 8 位时可以直接读ADC_RES。如果发现 ADC 值永远只有 0 或 1023 这种极端值先查分压电阻是否接对再查 ADC 参考电压下一步才怀疑代码。3.2 PWM 模拟 DAC占空比决定电压RC 低通滤波决定纹波赛题要求的“光敏 D/A 显示”难点在 D/A 不在 A/D。很多增强型 8051 单片机内部没有真正的 DAC 模块输出模拟电压要靠 PWM 加 RC 低通滤波。PWM 的占空比越高滤波后的平均电压越接近 VCCPWM 频率必须远高于 RC 滤波器的截止频率否则输出纹波大到万用表读数抖个不停。void pwm_set_duty(unsigned int duty) { if (duty PWM_PERIOD) duty PWM_PERIOD; // 钳位 PWM_DUTY duty; // 写比较寄存器 } void light_to_pwm(void) { unsigned int adc_val, duty; adc_val adc_read(LIGHT_CH); // 读光敏通道 // 光敏电阻上拉、固定电阻下地时光照强→ADC值小 // 所以占空比与ADC值成反比先取反再映射 duty (unsigned long)(1024 - adc_val) * PWM_PERIOD / 1024; pwm_set_duty(duty); }这里最容易出问题的是整数溢出。8 位单片机的 int 是 16 位(1024 - adc_val) * PWM_PERIOD在 1024 乘以一个比较大的周期值时已经溢出必须把第一个操作数强转成unsigned long让乘法在 32 位空间里完成。PWM 周期值怎么定取决于单片机时钟和 PWM 模块分频一般让 PWM 频率落在 4kHz 到 10kHz 之间。频率太低RC 滤波器需要更大的电容电阻响应慢频率太高PWM 翻转损耗和开关噪声上升。RC 低通滤波参数可以按这个经验值起步参数推荐取值说明PWM 频率4kHz ~ 10kHz低于 1kHz 时 RC 体积太大响应太慢滤波电阻1kΩ ~ 10kΩ电阻太小时输出带载能力差太大时压降明显滤波电容1uF ~ 10uF和电阻共同决定截止频率越大纹波越小截止频率PWM 频率的 1/10 以下例如 5kHz PWM 配 500Hz 截止频率衰减足够以 1kΩ 电阻配 10uF 电容为例截止频率约 16Hz对 5kHz 的 PWM 有很强的衰减但输出电压爬升也慢光强突变时要半秒才能稳定。想快就减小电容想平滑就加大电容看题目要求响应速度还是稳定度。3.3 D/A 显示的非线性映射赛题的“显示”到底显示什么题目里“光敏 D/A 显示”的具体要求一般分两段数码管显示一个光照等级同时 PWM 输出一个模拟电压。如果只是显示等级把 ADC 值线性映射到 0 到 99 就够了。但光敏电阻的阻值和光照强度不是线性关系暗光时阻值变化剧烈亮光时变化平缓如果直接线性映射暗处等级跳动很大亮处半天不动。要做平滑映射我通常用分段查表把 ADC 的 10 位范围砍成 16 段每段一个权重code unsigned char light_map[16] { 0, 8, 16, 24, 30, 36, 42, 48, 55, 62, 70, 78, 85, 90, 95, 99 }; duty (unsigned long)light_map[adc_val 6] * PWM_PERIOD / 100;adc_val 6把 0 到 1023 的 ADC 值压缩到 0 到 15查表得到 0 到 99 的等级再映射到 PWM 占空比。查表法比算公式直观赛场上改起来也快每个断点的数值用手一摸就知道对不对。如果题目要求“输出 0 到 5V 模拟电压”就把 PWM 周期值按 5V 对应 100% 占空比换算等级值直接乘 PWM_PERIOD 再除 100代码结构相同。4. 红外接收数据显示NEC 协议中断解析别在中断里做除法4.1 一体化接收头输出一串宽度不等的低电平脉冲红外接收头内部已经做了载波解调输出脚直接给单片机一串脉冲。NEC 协议下空闲时为高电平收到一帧数据时先来一段 9ms 低电平加 4.5ms 高电平的引导码然后跟 32 位数据每位由 560us 低电平开头后面接高电平高电平持续 1.69ms 表示逻辑 1持续 560us 表示逻辑 0。32 位数据里高 16 位是地址码和地址反码第 17 到 24 位是键值码低 8 位是键值反码。校验方法很朴素键值码加上反码等于 0xFF。接收头输出的波形是反相的解调后的脉冲边沿正好能用来触发外部中断。把接收头输出脚接到 INT0配成下降沿触发每次电平跳变进一次中断在中断里读出当前定时器计数值和上一次进中断的时刻作差就得到一个时间间隔。这个间隔落在哪个区间就对应哪一位。4.2 定时器加状态机解析不在等待脉冲而是测量脉冲解析 NEC 最忌讳的是用while (pin 0)去等电平变化。一个数据位短则 1.125ms长则 2.25ms加上引导码一帧接近 70ms如果用阻塞等待CPU 全耗在红外上温度和超声波全线崩溃。正确做法是把定时器配成自由计数每次下降沿进中断记下间隔然后用状态机判断。unsigned long ir_data 0; unsigned char ir_bit 0; unsigned char ir_state 0; bit ir_done 0; void ir_int0_isr() interrupt 0 { unsigned int gap; gap (TH0 8) | TL0; // 读出两次下降沿间隔 TH0 0; TL0 0; if (ir_state 0) { // 等待引导码 if (gap 10000 gap 16000) { // 约13.5ms引导码 ir_state 1; ir_bit 0; ir_data 0; } return; } if (gap 10000 gap 16000) { // 中途来重复帧 ir_state 1; // 重新解析 ir_bit 0; ir_data 0; return; } if (ir_bit 32) { if (gap 1900 gap 2600) // 约2.25ms → 逻辑1 ir_data | (1UL ir_bit); else if (gap 900 gap 1400) // 约1.125ms → 逻辑0 ; // 逻辑0不用置位 else { ir_state 0; // 非法间隔整帧作废 return; } ir_bit; } if (ir_bit 32) { if (((ir_data 8) 0xFF) (unsigned char)~ir_data) ir_done 1; // 数据码与反码校验通过 ir_state 0; } }阈值 10000 到 16000、1900 到 2600、900 到 1400 是按定时器 1MHz 计数频率算的对应 13.5ms、2.25ms、1.125ms 加 20% 容差。不同晶振下这些数要换算比如 12MHz 晶振配 12 分频定时器计数频率是 1MHz直接用如果是直通时钟不分频计数频率 12MHz阈值全部乘 12。改完必须实测遥控器重复按同一个键看解析出来的值是否稳定。中断服务程序里只做了比较和移位没有做除法。8051 内核做 16 位除法要几十个周期在中断里跑除法会让中断服务时间拉长引发其他时序问题。uint 的间隔用区间判断替代等值判断抗干扰能力也更好。1UL ir_bit这里必须用 unsigned long 移位直接写1 ir_bit在 ir_bit 等于 16 时结果已经错了。4.3 把接收数据映射到数码管红外数据收完要做两件事校验、显示。校验通过后才更新显示缓冲区校验失败保留上一帧数据。这样遥控器没对准或者中途被遮挡时数码管上不会跳出一堆乱码。void ir_show(void) { unsigned char key; if (!ir_done) return; ir_done 0; key ir_data 0xFF; // 取低8位键值码 display_buffer[4] key 4; // 十六进制高位 display_buffer[5] key 0x0F; // 十六进制低位 }按十六进制显示键值比十进制直观遥控器按键码本身就是十六进制定义的比如 0x45、0x46 这种。如果题目要求显示十进制用key / 10和key % 10拆两位就够。数码管上 D4、D5 两个位显示红外键值显示缓冲区统一由主循环里的show_digit()刷新中断只置标志不在中断里直接改显示缓冲区避免和主循环打架。5. 蓝桥杯单片机综合调试避坑清单现象、原因与修复5.1 温度读数固定在 0xFF 或 0x50换了传感器也一样现象是数码管温度一直显示 255 或者 80怎么重新上电都不变。最容易忽略的是复位时序被中断打断但很多人关掉了数码管刷新中断温度就正常了一开中断又坏。原因在于ds_reset()里虽然关中断但ds_write_byte和ds_read_byte的内部时序段没有关读 slot 被定时器中断从中间切开脉冲超过 120us 上限。解决办法是把读写单字节的函数也用同样的方式保护起来或者把温度、超声波、红外这几个模块的中断优先级调成比定时器刷新更高让温度时序不被数码管打断。还有一个隐蔽原因上拉电阻没接。单总线需要外部上拉到 VCC如果开发板上的跳线帽没插到位总线空闲时浮空复位时序根本跑不起来。用万用表量一下 DQ 引脚空闲电压正常应该是接近 VCC只有 0V 就是上拉缺失。5.2 超声波距离偶尔跳变 5cm 以上连续测量不稳定现象是距离显示大部分时间正常偶尔跳出一个明显偏大的值或者回波超时返回 0。原因多半是 Echo 脉冲测量期间定时器计数值被其他代码干扰。T0 如果同时被红外解析占用或者测距函数里用了while (ECHO 1)但中间开了中断中断服务程序消耗的时间被算进回波宽度里。解决方法是给超声波一个专用定时器回波测量的循环里不要执行任何可能耗时超过 100us 的操作。我还会连续测三次取中位数三个值排序去中间那个能有效滤掉偶发干扰。测距间隔保持在 50ms 以上模块内部电容放电需要时间间隔太短第二次数值会偏小。5.3 红外同一按键按两次数码管显示值不同现象是第一次按遥控器显示 0x45第二次同一按键显示 0x46 或 0x42偶尔还直接不更新。原因基本是阈值判定边界卡得太紧或者定时器计数频率换算出错。900 到 1400 这个区间对应逻辑 0如果晶振实际频率偏高1.125ms 实际计数值可能超过 1400就会落入“非法间隔”分支整帧作废。解决方法是先用实测方法确认定时器计数频率再重新算区间按住遥控器不放观察重复码的间隔计数值把测到的数值中心值正负 20% 作为判定区间。中断里也不要做除法除法会把中断服务时间拉长到几十微秒期间积压的 GPIO 跳变事件处理不及时后续间隔全部错位。5.4 PWM 输出电压和理论值偏差大万用表读数乱跳现象是光敏变化时 PWM 输出变化方向对但电压值比理论低 0.3V 以上或者万用表最后一位一直跳。先确认 PWM 频率是否在 4kHz 以上频率太低的 PWM 用万用表测量会有明显的指针摆动。再看 RC 滤波器的截止频率如果截止频率落在 PWM 频率附近纹波根本没有被滤掉。解决方法是把 RC 截止频率压到 PWM 频率的十分之一以下比如 PWM 5kHzRC 截止频率 500Hz1kΩ 加 0.33uF 就能做到。还要注意 VCC 实际值如果单片机的供电是 4.8V那 100% 占空比输出就是 4.8V 而不是 5V换算关系要按实物电压修正。没有示波器时把 PWM 输出接到 LED 上看亮度变化亮暗清晰的说明占空比在正常变化只有数值差异大才查电压换算。5.5 四个模块全部开启后数码管乱跳单模块测试都正常现象是单独测温度正常单独测超声波也正常合成一个工程后数码管数字互相串位按什么都不对。这多半是显示缓冲区被并发修改。单片机的中断可以在任何一条指令执行时插入如果主循环里正在把温度数据写入显示缓冲区红外中断刚好修改了同一片内存缓冲区里的数就是两个数据拼出来的。解决方法是每个传感器只写自己专属的缓存变量主循环统一从这些变量生成显示帧中断里的红外解析只置标志位主循环看到标志后再更新显示。跨中断共享的变量都要加volatile关键字防止编译器优化时把变量带到寄存器里导致中断改写了内存但主循环还在用旧副本。这一步做完合体测试才真正有意义。6. 把四个模块收拢进一个时间片循环用“调试页面”验证每个模块6.1 数码管当调试终端按键切换数据页面四个模块单测都过了合体之后怎么快速定位问题我的习惯是把数码管做成一个多页面调试终端预留两个按键切页面一页显示一个模块的原始数据。这样不用接串口线不用仿真器直接看着数码管判断每个模块在自己的时间片里是否正常工作。页面切换逻辑很简单void key_scan(void) { static unsigned char page 0; if (K1按下) page (page 1) % 5; // K1切换页面 switch (page) { case 0: show_temp_page(); break; // 温度整数 case 1: show_ultra_page(); break; // 超声波距离cm case 2: show_light_page(); break; // 光敏ADC原始值 case 3: show_pwm_page(); break; // 当前占空比 case 4: show_ir_page(); break; // 红外键值 } }调试页面比直接显示最终结果更能暴露问题切到光敏页时用手遮住光敏电阻ADC 原值应该向一个方向平滑变化如果数值跳变剧烈说明 ADC 采样周期太短或滤波电容偏大造成滞后切到 PWM 页时应该看到占空比数字和 ADC 值严格反向这时拿万用表量输出脚电压应该稳定在某个值附近切到红外页按住遥控器同一个键不放显示值要每次都一样。超声波页用手掌慢慢靠近模块距离值应连续减小不许跳变超过约 4 米稳定返回 0。我调这类赛题养成的习惯是主函数里不铺一层延时。每加一个阻塞式 delay就是在给下一个要调的外设埋一颗雷。先把时间片切好把调试页面做好等四路数据都在数码管上稳定跳动了再回头优化单项性能。按页面逐项排查完这道综合题就只剩一个雷要踩你自己会不会忘记注释掉调试页面把代码精简上交。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询