CH552/CH554串口实战:从初始化到中断处理的避坑指南
1. CH552/CH554串口开发入门
第一次用CH552的串口功能时,我对着官方例程照猫画虎,结果数据死活发不出去。后来才发现,这个看着像传统51单片机的芯片,在串口配置上有不少隐藏细节。CH552系列最大的优势就是内置USB和双串口,成本才几块钱,特别适合做通信转接设备。但要用好它的串口,得先搞清楚几个关键点:
硬件连接是第一个坑。UART0默认引脚在P3.0(RX)和P3.1(TX),UART1则在P1.6(RX)和P1.7(TX)。有次我死活收不到数据,查了半天才发现评估板的P1.6脚被LED电路占用了。建议先用万用表确认引脚连通性,特别是自己画板子的时候。和CH340这类USB转串口芯片对接时,记得TX接RX交叉连接,共地更是基本操作——我就曾因为没共地导致数据乱码,折腾了一下午。
开发环境搭建也有讲究。官网的CH554EVT.ZIP包里有两个关键文件夹:UART0和UART1。新手容易犯的错是直接复制整个文件夹到工程,结果发现重复定义。正确做法是只添加需要的C文件,比如用UART1就只引入UART1目录下的文件。我习惯在Keil里新建组来管理,避免文件混杂。编译时如果报错"bT1_M1未定义",记得检查是否包含了CH554头文件,这个在例程里容易遗漏。
2. 串口初始化深度解析
2.1 UART0的特殊初始化
官方例程里的mInitSTDIO()函数暗藏玄机。它用Timer1做波特率发生器,这个和传统51单片机一致,但有个细节很关键:PCON |= SMOD这行代码开启了双倍波特率模式。我在115200波特率下测试时,发现实际速率总是差一半,就是因为漏了这个设置。计算波特率的公式看起来复杂:
x = 10 * FREQ_SYS / UART0_BUAD / 16;其实拆解开来很简单:FREQ_SYS是系统时钟(默认12MHz),UART0_BUAD是目标波特率,16是固定分频系数。那个乘以10再四舍五入的操作,是为了提高计算精度。实际调试时,可以用示波器测量TX引脚波形来验证实际波特率。
最让人困惑的是TI=1这行。为什么初始化就要置位发送完成标志?这是因为标准库的putchar()实现有讲究:
char putchar(char c) { while (!TI); // 等待发送完成 TI = 0; // 清除标志 return (SBUF = c); }如果初始TI=0,第一个字符会卡死在while循环。这个设计虽然巧妙,但导致UART0的中断使能必须谨慎处理——开启中断前一定要先写中断服务函数,否则一触发中断程序就跑飞了。
2.2 UART1的简洁配置
相比UART0,UART1的初始化就清爽多了:
void UART1Init() { U1SM0 = 0; // 8位数据模式 U1SMOD = 1; // 快速模式 U1REN = 1; // 使能接收 SBAUD1 = 0 - FREQ_SYS/16/UART1_BUAD; }这里U1SMOD=1会启用独立波特率发生器,实测发现比用定时器更稳定。波特率计算直接用系统时钟除以16倍目标波特率,结果取补码存入SBAUD1寄存器。有个坑点:当使用24MHz主频时,波特率超过1Mbps可能会不稳定,这时建议切换到UART0使用Timer2做波特率发生器。
3. 数据收发实战技巧
3.1 查询发送的陷阱
官方例程的发送函数看起来简单:
void CH554UART1SendByte(UINT8 SendDat) { SBUF1 = SendDat; while(U1TI ==0); // 等待发送完成 U1TI = 0; // 清除标志 }但实际使用时发现连续发送会丢数据。后来用逻辑分析仪抓包,发现是while等待期间被其他中断打断,导致标志位判断失效。改进方案有两种:
- 在关键代码段关闭中断:EA=0后再执行发送,完成后再EA=1
- 改用缓冲区+中断发送,这是我更推荐的方式:
__xdata UINT8 txBuffer[64]; UINT8 txIndex = 0; void UART1_ISR() interrupt INT_NO_UART1 { if (U1TI) { U1TI = 0; if (txIndex > 0) { SBUF1 = txBuffer[--txIndex]; } } }3.2 中断接收的最佳实践
查询法接收数据就像用勺子舀海水——既低效又容易遗漏。CH554的中断接收要特别注意三点:
- 中断服务函数要尽可能短,我见过有人在中断里做字符串解析,结果9600波特率下都丢数据
- 使用双缓冲机制:一个缓冲用于中断快速存储,另一个供主循环处理
- 记得清除RI标志,否则会反复进入中断
这是我优化后的中断接收代码框架:
#define BUF_SIZE 128 __xdata UINT8 rxBuffer[BUF_SIZE]; volatile UINT8 rxHead = 0, rxTail = 0; void UART0_ISR() interrupt INT_NO_UART0 { if (RI) { RI = 0; rxBuffer[rxHead++] = SBUF; if (rxHead >= BUF_SIZE) rxHead = 0; } } UINT8 UART0_Available() { return (rxHead != rxTail); } UINT8 UART0_Read() { if (rxHead == rxTail) return 0; UINT8 data = rxBuffer[rxTail++]; if (rxTail >= BUF_SIZE) rxTail = 0; return data; }4. 典型问题排查指南
4.1 波特率异常排查
遇到波特率不准时,按这个步骤检查:
- 用示波器测量单个位的持续时间,计算实际波特率
- 确认FREQ_SYS定义是否正确,使用外部晶振时要修改时钟配置
- 检查波特率计算公式是否溢出,特别是当主频较高时
- 对于UART0,确保(256-TH1)≥3
- 对于UART1,SBAUD1不能超过0xFF
- 尝试降低波特率测试,比如先试9600看是否正常
4.2 数据丢失分析
数据丢失通常有三大原因:
接收方问题:用逻辑分析仪同时抓TX和RX线,如果发送端波形正常但接收端没反应,检查:
- 共地是否良好
- 引脚配置是否正确(比如复用功能未开启)
- 电压电平是否匹配(CH552是3.3V电平)
软件处理不及时:
// 错误示例:在主循环处理大量耗时操作 while(1) { process_data(); // 耗时50ms if (UART0_Available()) { // 在115200波特率下,50ms可能丢失57字节 } }中断冲突:当多个中断同时发生时,如果串口中断优先级低,可能导致数据丢失。解决方法:
- 调整IP寄存器提升串口中断优先级
- 在中断服务函数开始时备份SBUF数据
4.3 硬件设计注意事项
画PCB时要特别注意:
- 串口走线远离高频信号线,避免干扰
- 超过10cm的传输距离建议加120Ω终端电阻
- 工业环境使用时要加TVS二极管防护
- 如果要用RS485,建议选用带自动方向控制的芯片如MAX13487
调试时必备工具链:
- 逻辑分析仪(Saleae便宜好用)
- USB转串口工具(CH340G就行)
- 终端软件(推荐Tera Term,支持二进制显示)
