蓝桥杯单片机编程避坑指南:PCF8591多通道读取、长按检测与PWM输出的实战技巧
在蓝桥杯单片机竞赛中,PCF8591多通道AD读取、长按按键检测和PWM输出是常见的功能模块,也是容易踩坑的重灾区。本文将结合实战经验,分享这些功能实现中的关键技巧和避坑指南。
1. PCF8591多通道AD读取的数据窜扰问题与解决方案
PCF8591是一款常用的8位AD/DA转换芯片,支持4路模拟输入和1路模拟输出。在多通道读取时,经常会遇到数据窜扰的问题——读取第二个通道时得到的是第一个通道的值。
这种现象的根本原因在于PCF8591内部采样保持电路的特性。当切换通道时,前一个通道的采样值可能会残留在内部电容上,影响下一个通道的读取结果。
解决这一问题的有效方法是连续读取两次:
unsigned char AD0, AD1; AD0 = read_pcf(0); // 第一次读取通道0 AD0 = read_pcf(0); // 第二次读取通道0 AD1 = read_pcf(1); // 第一次读取通道1 AD1 = read_pcf(1); // 第二次读取通道1这种方法的原理是:
- 第一次读取时,可能得到的是前一个通道的残留值
- 第二次读取时,采样保持电路已经稳定,得到的是当前通道的真实值
在实际应用中,如果发现数据仍有异常,可以在两次读取之间加入短暂的延时:
AD0 = read_pcf(0); Delay100us(); // 适当延时 AD0 = read_pcf(0);2. 长按1秒功能实现的常见误区与可靠方案
长按检测是单片机应用中常见的功能需求,但在实现过程中容易陷入两种误区:
- 误区一:认为必须按住按键满1秒后松开才触发长按
- 误区二:仅通过延时判断长按,导致程序阻塞
推荐的长按检测方案采用定时器中断配合状态标志位实现,既准确又不会阻塞主程序:
bit is_1s = 1; // 初始为1 unsigned int count_1000ms = 0; // 定时器中断服务函数 void Timer_Isr(void) interrupt 1 { if(is_1s == 0) // 如果开始计时 { if(++count_1000ms == 1000) // 1秒到 { is_1s = 1; // 设置标志位 count_1000ms = 0; } } else { count_1000ms = 0; // 清零计数器 } } // 按键检测函数 void Key_Scan() { if(P30 == 0) // 按键按下 { Delay5ms(); // 消抖 if(P30 == 0) { is_1s = 0; // 开始计时 while(P30 == 0) // 等待按键释放 { if(is_1s == 1) // 检测到长按1秒 { // 执行长按功能 break; } } is_1s = 1; // 重置标志位 } } }这种实现方式的优势在于:
- 不阻塞主程序,系统可以同时处理其他任务
- 准确判断1秒长按,无论按键是否继续按住
- 代码结构清晰,易于维护和扩展
3. 资源紧张时的软件PWM实现技巧
在定时器资源紧张的情况下,可以使用软件延时实现PWM输出,这种方法特别适合对精度要求不高的场合。
80%占空比1kHz PWM的软件实现:
#define MOTOR_ON() ULN |= 0x20; P0 = ULN; P2 |= 0xA0; P2 &= 0xBF; P2 &= 0x1F; #define MOTOR_OFF() ULN &= 0xDF; P0 = ULN; P2 |= 0xA0; P2 &= 0xBF; P2 &= 0x1F; void Delay800us() // 12MHz时钟下的800微秒延时 { unsigned char i = 10, j = 83; do { while(--j); } while(--i); } void Delay200us() // 12MHz时钟下的200微秒延时 { unsigned char i = 3, j = 82; do { while(--j); } while(--i); } void PWM_out_80() // 80%占空比1kHz PWM { MOTOR_ON(); Delay800us(); MOTOR_OFF(); Delay200us(); }使用时需要注意:
- 这种实现方式会占用CPU时间,不适合在主循环中有大量其他任务的情况
- 延时精度受中断影响,在频繁中断的系统中可能不稳定
- 可以通过状态机的方式优化,将PWM输出分散到多个主循环周期中执行
4. 系统资源优化与任务调度策略
在蓝桥杯竞赛中,经常面临定时器资源不足的问题。以同时需要超声波测距和NE555频率测量为例:
| 功能模块 | 所需定时器资源 | 推荐分配方案 |
|---|---|---|
| 超声波测距 | 定时器0(计时模式) | 定时器0 |
| NE555频率测量 | 定时器1(计数模式) | 定时器1 |
| 数码管扫描 | 定时器中断 | 定时器2 |
| PWM输出 | 定时器或软件延时 | 软件延时 |
STC15F2系列单片机定时器2的特殊用法:
void Timer2_Init(void) // 1毫秒@12.000MHz { AUXR |= 0x04; // 定时器时钟1T模式 T2L = 0x20; // 设置定时初始值 T2H = 0xD1; // 设置定时初始值 AUXR |= 0x10; // 定时器2开始计时 IE2 |= 0x04; // 使能定时器2中断 } void Timer2_Isr(void) interrupt 12 { // 中断服务程序 }定时器2的特殊之处在于:
- 中断使能位不是ET2,而是IE2 |= 0x04
- 中断号为12,不是常规的定时器中断号
- 初始化方式与其他定时器略有不同
在资源分配时,建议:
- 将精度要求高的任务分配给硬件定时器
- 对实时性要求不高的任务使用软件定时器或状态机实现
- 合理规划中断优先级,确保关键任务及时响应