CH552/CH554串口实战:从初始化到中断处理的避坑指南

📅 2026/6/30 10:27:27
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引脚波形来验证实际波特率。最让人困惑的是TI1这行。为什么初始化就要置位发送完成标志这是因为标准库的putchar()实现有讲究char putchar(char c) { while (!TI); // 等待发送完成 TI 0; // 清除标志 return (SBUF c); }如果初始TI0第一个字符会卡死在while循环。这个设计虽然巧妙但导致UART0的中断使能必须谨慎处理——开启中断前一定要先写中断服务函数否则一触发中断程序就跑飞了。2.2 UART1的简洁配置相比UART0UART1的初始化就清爽多了void UART1Init() { U1SM0 0; // 8位数据模式 U1SMOD 1; // 快速模式 U1REN 1; // 使能接收 SBAUD1 0 - FREQ_SYS/16/UART1_BUAD; }这里U1SMOD1会启用独立波特率发生器实测发现比用定时器更稳定。波特率计算直接用系统时钟除以16倍目标波特率结果取补码存入SBAUD1寄存器。有个坑点当使用24MHz主频时波特率超过1Mbps可能会不稳定这时建议切换到UART0使用Timer2做波特率发生器。3. 数据收发实战技巧3.1 查询发送的陷阱官方例程的发送函数看起来简单void CH554UART1SendByte(UINT8 SendDat) { SBUF1 SendDat; while(U1TI 0); // 等待发送完成 U1TI 0; // 清除标志 }但实际使用时发现连续发送会丢数据。后来用逻辑分析仪抓包发现是while等待期间被其他中断打断导致标志位判断失效。改进方案有两种在关键代码段关闭中断EA0后再执行发送完成后再EA1改用缓冲区中断发送这是我更推荐的方式__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对于UART1SBAUD1不能超过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支持二进制显示