1. PIC单片机异步串行通信基础
PIC单片机作为嵌入式系统开发的主流选择之一,其异步串行通信功能在实际项目中扮演着重要角色。硬件UART模块的稀缺性使得软件实现方案成为工程师必备技能。异步串行通信的基本帧格式包含起始位(低电平)、5-9位数据位(LSB先发)、可选的奇偶校验位以及1-2位停止位(高电平)。以1200bps为例,每位持续时间833μs(1/1200),这个时间基准将直接影响后续软件定时的精度。
传统硬件UART的优势在于自动完成帧格式处理,而软件实现则需要开发者手动控制每个位的时序。PIC单片机的中断系统和定时器资源在这种场景下显得尤为关键。中档型号如PIC16F877A提供TMR0、TMR1等多个定时器,配合RB0/INT外部中断,为软件UART的实现提供了硬件基础。需要注意的是,不同振荡频率下(4MHz/20MHz),指令周期时间不同(1MHz时钟时1指令周期=1μs),这会直接影响定时器预分频的设置。
关键参数速查:
- 4MHz晶振时指令周期=1μs
- 1200bps时每位宽度=833μs
- 8N1格式下10位帧耗时8.33ms
2. 三倍速采样法实现细节
2.1 时序控制原理
三倍速采样法的核心在于以波特率三倍的频率检测RX引脚(如278μs间隔@1200bps)。如图2所示,将每位划分为三个等份(S0-S2),通过检测起始位下降沿触发采样序列。首次检测到低电平后,延时4个ts(4×278μs≈1.11ms)到达第一位数据的中点(Ds区域),此后每3个ts采样一次,确保始终在数据位最稳定的中间阶段读取。
在PIC16系列上实现时,通常配置TMR0定时器:
c复制// 4MHz时钟下TMR0配置示例
OPTION_REG = 0b10000101; // 1:64预分频
TMR0 = 256 - (278/4); // 278μs中断周期
2.2 状态机实现
接收过程采用有限状态机(FSM)管理,状态转移如图3所示:
- IDLE状态:检测起始位下降沿
- START状态:确认起始位有效性
- DATA状态:循环采样8位数据
- STOP状态:验证停止位
发送过程相对简单,通过定时器控制每位持续时间:
c复制void send_byte(uint8_t data) {
TX_PIN = 0; // 起始位
delay_bit_time();
for(uint8_t i=0; i<8; i++) {
TX_PIN = (data >> i) & 0x1;
delay_bit_time();
}
TX_PIN = 1; // 停止位
delay_bit_time();
}
2.3 资源占用实测
在4MHz时钟的PIC16F877A上实测:
- 中断服务程序约35个指令周期(35μs)
- 全双工通信时CPU占用率约12%
- 代码量约150指令字(Hitech-PICC编译)
注意事项:
- 中断服务中必须短小精悍,避免错过下次采样
- 多路UART实现时需为每路维护独立缓冲区
- 波特率误差应控制在±3%以内(晶体精度要求)
3. 起始位中断捕捉法进阶实现
3.1 硬件中断配置
利用RB0/INT引脚下降沿中断实现异步唤醒:
c复制// 初始化代码示例
TRISBbits.TRISB0 = 1; // RX引脚输入
OPTION_REGbits.INTEDG = 0; // 下降沿触发
INTCONbits.INTE = 1; // 使能INT中断
3.2 精确定时策略
起始位触发后,定时器设置为1.5位时间(1200bps时为1.25ms)进行中点采样:
c复制void interrupt isr() {
if(INTF) { // 起始位检测
INTF = 0;
TMR1H = 0x80; // 1.25ms定时初值
TMR1L = 0x00;
TMR1ON = 1; // 启动定时器
}
if(TMR1IF) { // 数据位采样
TMR1IF = 0;
sample_data();
TMR1H = (65536 - 833)/256; // 调整到下个中点
TMR1L = (65536 - 833)%256;
}
}
3.3 性能对比
| 指标 | 三倍速采样法 | 起始位中断法 |
|---|---|---|
| 最大波特率 | 2400bps@4MHz | 9600bps@4MHz |
| CPU占用率 | 持续12% | 间歇<5% |
| 唤醒能力 | 不支持 | 支持睡眠唤醒 |
| 引脚限制 | 任意IO | 特定中断引脚 |
4. 工程实践中的优化技巧
4.1 抗干扰设计
- 添加施密特触发器输入(如PIC的ANSEL配置)
- 三次采样表决法:取连续三次采样中的多数值
- 帧错误检测:停止位验证+超时重试机制
4.2 内存优化
使用循环缓冲区管理接收数据:
c复制#define BUF_SIZE 16
struct {
uint8_t data[BUF_SIZE];
uint8_t head, tail;
} rx_buf;
void enqueue(uint8_t byte) {
rx_buf.data[rx_buf.head] = byte;
rx_buf.head = (rx_buf.head + 1) % BUF_SIZE;
}
4.3 波特率自适应
通过测量起始位宽度动态调整:
c复制void measure_baud() {
while(RX_PIN); // 等待高电平
TMR1 = 0;
while(!RX_PIN); // 测量起始位低电平时间
baud_rate = 1000000/TMR1; // 1MHz计时单位
}
5. 典型应用案例:红外抄表系统
某电能表项目采用软件UART实现:
- 硬件配置:PIC16F1937 + 38kHz红外收发管
- 通信参数:1200bps半双工,8E1格式
- 关键实现:
c复制void send_ir_pulse() { for(uint8_t i=0; i<10; i++) { // 38kHz载波 IR_LED = 1; delay_us(13); IR_LED = 0; delay_us(13); } } - 性能指标:
- 通信距离:>5米
- 误码率:<1e-6
- 待机功耗:<10μA(睡眠模式)
调试中发现的问题及解决:
- 日光干扰导致误触发 → 添加38kHz载波调制
- 多从机冲突 → 增加CSMA/CD机制
- 长距离衰减 → 动态调整载波占空比
6. 常见问题排查指南
6.1 通信不稳定
- 检查项:电源纹波、接地环路、终端电阻
- 解决方案:添加0.1μF去耦电容,使用双绞线
6.2 波特率偏差
- 计算公式:
code复制实际波特率 = Fosc / (16*(SPBRG+1)) 误差% = |(理论-实际)/理论|×100 - 修正方法:改用更高精度晶振或调整SPBRG
6.3 数据错位
- 典型现象:起始位被误判为数据位
- 处理流程:
- 确认双方帧格式一致(数据位/停止位)
- 检查中断优先级是否被抢占
- 验证定时器重装载值计算
实际项目中,我曾遇到TMR0中断被延时导致采样偏移的问题。最终通过以下措施解决:
- 将中断优先级设为最高
- 在中断入口立即重载定时器
- 使用汇编优化关键路径代码
对于需要更高性能的场景,建议:
- 改用PIC18系列硬件UART
- 使用DMA加速数据传输
- 考虑改用SPI/I2C等同步协议
