简介:这是一个面向PIC18F45K22微控制器的UART串口通信示例工程,用于演示基于RS485协议的字符串收发,适合学习MPLAB X IDE开发流程和嵌入式串行通信的工程师与学生。压缩包共18个文件,体积约25KB,包含C源码、Hex固件、Makefile构建脚本、MPLAB X工程配置及属性文件等,主程序与编译输出文件可帮助用户直接查看代码逻辑并烧录验证。已有169人学习下载。资源核心价值在于通过一个可运行的UART_TEST工程,直观呈现UART初始化、波特率配置、RS485方向控制及收发字符串的完整代码路径;结合nbproject目录下的项目和Makefile配置,还能了解MPLAB X下PIC18系列项目的组织方式,适合作为RS485多机通信开发的入门参考。
1. 用MPLAB X把PIC18F45K22的UART_TEST跑起来,先理清串口调试链路
UART_TEST.zip这个命名把目标写得很直白:一个针对PIC18F45K22的UART收发测试固件包。配合mplab x,它就是在MPLAB X里打开、编译、烧录并验证串口通断的最小工程。PIC18F45K22是Microchip 8位产品线里带增强型EUSART外设的型号,相比老PIC18F4520多了发送缓冲、PPS引脚重映射和自动波特率检测;MPLAB X从v5.30开始,也能用MCC把这套初始化代码直接生成出来。
目标读者有两类:从Arduino或STM32转过来、初次接触MPLAB X的人,想知道工程建在哪、为什么串口助手全是乱码;以及在PIC上写过项目、想确认EUSART寄存器顺序的工程师。下面从寄存器结构与波特率计算讲起,落到建工程步骤和收发代码,最后收在调试验证上。
2. PIC18F45K22的EUSART寄存器与波特率计算:写UART代码前的必修课
2.1 增强型EUSART与普通UART的差异在哪里
PIC18F45K22的串口模块在Microchip内部标识是EUSART,与早期型号的USART模块相比,增强点集中在三处:发送端多一级缓冲,可以让CPU提前准备下一字节写入,减少字节间隙;接收端支持硬件自动波特率检测,利用同步字符的跳变沿估算波特率,适合批量板卡测试;另外增加了面向LIN总线协议的功能,比如Break字符检测和帧错误标志FERR的硬件管理。
落到UART_TEST这个场景,最需要记住的是引脚选项。PIC18F45K22默认把EUSART放在RC6/TX与RC7/RX,如果使用PPS重映射,也能把这两个信号搬到其他端口。重映射需要同时配置两个方向的寄存器:输入方向选择寄存器和输出方向引脚选择寄存器,只配一个方向就会出现发送有波形、接收没数据的问题。
| 功能 | 默认引脚 | PPS重映射示例 | 重映射时需要确认的寄存器 |
|---|---|---|---|
| TX/CK | RC6 | RB7 | 输出方向选择寄存器(RPOR) |
| RX/DT | RC7 | RB5 | 输入方向选择寄存器(RPINR) |
| 电平标准 | 3.3V/5V TTL | 与默认一致 | 无需额外配置 |
PPS重映射在带外设引脚选择功能的型号上要小心,因为重映射后原引脚的模拟输入和数字IO方向会自动切换,问题常常出现在“配置了PPS但没关闭原引脚功能”的情况。
2.2 波特率计算公式与SPBRG配置速查
异步模式下的波特率公式由BRGH位决定分频方式:
当BRGH = 0(低速):波特率 = FOSC /(64 ×(SPBRGH:SPBRG + 1))
当BRGH = 1(高速):波特率 = FOSC /(16 ×(SPBRGH:SPBRG + 1))
反推写入值:SPBRGH:SPBRG = FOSC /(16 × 目标波特率)- 1,这里BRGH取1。SPBRGH和SPBRG合起来是一个16位值。假设FOSC为8MHz,目标9600波特率,则计数 = 8000000 / 153600 - 1 = 51.08,取整后SPBRG = 51、SPBRGH = 0,实际波特率9615.38,误差只有0.16%。
下表按FOSC = 8MHz、BRGH = 1给出常用设置值:
| 目标波特率 | SPBRG写入值 | 实际波特率 | 误差 | 是否推荐 |
|---|---|---|---|---|
| 1200 | 416 | 1199.04 | -0.08% | 推荐 |
| 2400 | 207 | 2403.85 | 0.16% | 推荐 |
| 4800 | 103 | 4807.69 | 0.16% | 推荐 |
| 9600 | 51 | 9615.38 | 0.16% | 推荐 |
| 19200 | 25 | 19230.77 | 0.16% | 推荐 |
| 38400 | 12 | 38461.54 | 0.16% | 推荐 |
| 57600 | 8 | 55555.56 | -3.55% | 不推荐 |
| 115200 | - | 100000 | -13.2% | 不可用 |
可以看出,PIC18F45K22在8MHz系统时钟下,稳定波特率上限到38400。要跑57600或115200,需要把FOSC提到16MHz以上,或者接受误差后在上位机侧按实际波特率重新配置。多数工程师的做法是改用外部晶振或打开内部PLL提高系统时钟。
提示:上表基于FOSC = 8MHz且BRGH = 1。波特率误差超过2%时,长帧数据容易出现字节错位,回环测试能明显看到这个问题。
2.3 振荡器频率忘记改,串口输出全是乱码
PIC18F45K22波特率公式里的FOSC,指系统时钟频率,不是指令周期。系统时钟最终来源由配置位和OSCCON寄存器共同决定。很多UART_TEST工程出现“发出去一开始是乱的,后面完全不对”,就是因为初始化代码里没有显式设置内部振荡器频率。
常见做法是main函数开头先完成振荡器切换:
void osc_init(void) { OSCCONbits.IRCF = 0b111; // 内部振荡器输出8MHz OSCCONbits.SCS = 0b00; // 时钟源由配置位决定 while (!OSCCONbits.HFIOFS); // 等待高频振荡器稳定 }参数说明:IRCF是OSCCON的bit6~bit4,0b111对应8MHz;SCS的0b00表示系统时钟来源跟随配置字,不在这里强行切换;HFIOFS是高频内部振荡器稳定标志,刚上电或改变IRCF后需要等它置位,否则接着初始化UART时,波特率发生器的时钟可能还没切换完成。
如果在MPLAB X里使用图形化配置位窗口,内部振荡器频率选项会直接写进配置字。手动代码里坚持用OSCCONbits配合HFIOFS等待,比直接写OSCCON整个寄存器更不容易出错,因为不用记住每个保留位的复位值。
3. 在MPLAB X中搭PIC18F45K22最小UART工程:寄存器初始化和MCC两条路线
3.1 手动初始化EUSART的推荐寄存器序列
在MPLAB X里新建工程后,先确认Project Properties中选中的是PIC18F45K22,编译器是XC8。然后写初始化函数,UART_TEST里最常用的EUSART初始化序列如下:
void uart_init(unsigned long baud) { unsigned int spbrg_value; unsigned char dummy; spbrg_value = (unsigned int)(8000000UL / (16UL * baud) - 1); TXSTA = 0x24; // TXEN=1, BRGH=1, 异步8位 RCSTA = 0x90; // SPEN=1, CREN=1 SPBRGH = (unsigned char)(spbrg_value >> 8); SPBRG = (unsigned char)(spbrg_value & 0xFF); dummy = RCREG; // 清掉残留数据 (void)dummy; RCSTAbits.CREN = 0; // 先关接收 RCSTAbits.CREN = 1; // 重开,清OERR PIE1bits.RCIE = 0; // 中断接收时再置1 }代码说明:TXSTA的bit2即BRGH位,置1后按16分频,误差比64分频小一个数量级。RCSTA的bit7是SPEN,相当于EUSART总开关;bit4是CREN,连续接收使能,清零再置位可以清除溢出错误OERR。SPBRGH和SPBRG必须分开写入高、低字节,顺序无所谓,但要在TXSTA配置之前完成,避免波特率发生器按旧值先跑起来。
手动写初始化的好处是每个位都看得见,对照数据手册容易排查。缺点是换时钟或换波特率时需要重新计算。我一般把FOSC定义成宏,把初始化函数封装成接收baud参数的形式,这样改时钟频率时只动一处代码。
3.2 用MCC配置EUSART的步骤与生成代码的裁剪
MPLAB X的MCC使用教程一直是热门搜索词。在v5.30中,通过Tools → Embedded → MPLAB Code Configurator可以启动MCC。对UART_TEST这类工程,配置路径如下:
- 在Device Resources中双击EUSART,将其加入工程。
- 在EUSART设置页勾选Enable Receiver和Enable Transmitter,Baud Rate选9600。
- 在Pin模块中确认TX = RC6、RX = RC7,与目标板接线一致。
- 在System Module中把时钟源设为内部振荡器,并明确选择8MHz。
- 点击Generate生成代码。
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Receiver | Enable | 生成接收相关代码 |
| Transmitter | Enable | 生成发送相关代码 |
| Baud Rate | 9600 | 与上位机串口助手严格一致 |
| Stop Bits | 1 | 标准异步帧格式 |
| Interrupt Driven | Disable | 先跑通轮询模式,再开中断 |
MCC生成的代码里,我一般保留uart_initialize之类的初始化接口,不直接用它的读写函数。原因是MCC的Read/Write带有缓冲区管理和超时逻辑,对UART_TEST这种想精确控制收发行为的场景,反而增加排查难度。把初始化代码和自己的收发函数搭配使用,是最稳妥的落地方式。
3.3 编译器路径与烧录器选择的三个注意点
从Microchip官网下载MPLAB X v5.30安装后,还需要单独安装与版本匹配的XC8编译器。装完后在Tools → Options → Embedded → Build Tools里指定编译器目录,否则工程会报Compiler not found。
烧录器方面,PICkit 3和ICD4都可以通过ICSP口烧录PIC18F45K22。在Project Properties的Hardware Tool里选择对应调试器,然后确认MCLR Enabled勾选,因为UART_TEST调试期间依赖MCLR复位。
烧录时报Device ID mismatch,先量VDD是否正常。PICkit 3对目标板供电很敏感,如果开发板用外部5V电源,USB转串口模块同时接3.3V,两套供电之间有电位差就会烧不进。把两个模块共地,或者拔掉外部电源改用PICkit 3供电,通常马上解决。
4. UART收发实战:让PIC18F45K22的测试数据进入串口助手
4.1 轮询方式发送字符串:为什么不建议只查TXIF
发送一个字符串的代码很简单,内部有一个细节容易踩:判断发送完成的标准要选对。
void uart_send_byte(unsigned char data) { while (!TXSTAbits.TRMT); // 等待移位寄存器空 TXREG = data; } void uart_send_string(const char *str) { while (*str) { uart_send_byte((unsigned char)*str++); } }代码说明:TXSTAbits.TRMT是只读的移位寄存器空标志,表示TSR已经完成发送,下一字节可以安全写入TXREG。如果改用TXIF,虽然TXIF代表TXREG为空,但TSR可能仍在发送上一字节,此时连写两个字节会覆盖正在发送的数据,出现丢字节或错位。用TRMT判断能保证每个字节真正发送结束再写下一字节,这是UART_TEST里最稳的发送方式。
| 收发方式 | 适用场景 | 需要注意的点 |
|---|---|---|
| 轮询发送 | 日志输出、测试指令 | 连续发送勿用TXIF判断 |
| 中断接收+回显 | 短帧交互、回环验证 | 回显会阻塞中断,长帧不适用 |
| 中断接收+环形缓冲区 | 协议数据、命令帧 | 缓冲区满时选择丢弃或覆盖 |
轮询发送会让CPU一直等在循环里,在集中发日志时问题不大。如果主循环还要处理按键或ADC采样,就需要把发送放到调度里,或者改用中断。
4.2 中断方式接收:读取RCREG前的标志处理
实时性要求高时用中断接收。PIC18的中断入口统一在0x08地址,ISR中通过PIR1的RCIF判断是否为EUSART接收中断。
void __interrupt() isr(void) { unsigned char rx_data; if (PIR1bits.RCIF && PIE1bits.RCIE) { if (RCSTAbits.OERR) { RCSTAbits.CREN = 0; // 溢出后先关接收 RCSTAbits.CREN = 1; // 重开,清除OERR } if (!RCSTAbits.FERR) { rx_data = RCREG; // 正常数据 uart_send_byte(rx_data); // 回显一个字节 } else { unsigned char dummy = RCREG; // 帧错误也读走数据 (void)dummy; } } }逻辑说明:RCIF置位表示RCREG已经有可用数据;OERR处理必须在读RCREG之前完成,否则接收器一直停摆;FERR是帧错误标志,接收到错误停止位时置位,此时读出的数据虽然没有意义,但仍然要读走,避免留在缓冲器里影响后续判断。注意main中需要打开INTCON的GIE和PEIE,以及PIE1的RCIE。
这段ISR里调用了uart_send_byte回显,在波特率9600下不会丢数据。如果波特率更高或回显帧较长,不要在中断里等待发送,而是把数据交给缓冲区。
4.3 一个环形缓冲区:把中断接收的数据转交主循环
中断接收的数据最终要给主循环用,环形缓冲区是最简单的结构。它解决两个问题:ISR执行时间短,数据不会丢;主循环有数据时拿走,没有数据时继续干别的。
#define UART_RX_BUF_SIZE 64 volatile unsigned char uart_rx_buf[UART_RX_BUF_SIZE]; volatile unsigned int uart_rx_head = 0; volatile unsigned int uart_rx_tail = 0; void uart_rx_put(unsigned char data) { unsigned int next = (uart_rx_head + 1) % UART_RX_BUF_SIZE; if (next != uart_rx_tail) { // 缓冲区不满 uart_rx_buf[uart_rx_head] = data; uart_rx_head = next; } } unsigned char uart_rx_get(void) { unsigned char data; data = uart_rx_buf[uart_rx_tail]; uart_rx_tail = (uart_rx_tail + 1) % UART_RX_BUF_SIZE; return data; }代码说明:head指向下一个写入位置,tail指向下一个读取位置,两者相等时缓冲区为空。代码刻意用next == tail作为满条件,tail永远慢head一步,满和空可以区分。get操作只改tail,put只改head,中断和主循环各写各的索引,不会有竞争问题。在ISR中把RCREG的数据交给uart_rx_put,主循环里检查head != tail后调用uart_rx_get即可。
5. UART_TEST联调的排错顺序:PIC18F45K22的时钟、引脚与回环验证
5.1 时钟与引脚复用:先排除模拟功能和配置位干扰
串口助手完全收不到数据时,第一步量TX引脚有没有波形。没有波形,按三个点查:系统时钟有没有起来、TXSTA的TXEN是否置1、RC6是否被配置成模拟功能或第二功能。
PIC18F45K22的模拟选择寄存器ANCON0和ANCON1决定多个引脚是数字还是模拟状态。如果工程之前开过ADC但没有关闭对应通道,RX/TX又恰好重映射到带模拟功能的引脚,接收就会失效。保险的写法是在初始化开头执行:
ANCON0 = 0xFF; // 关闭所有模拟输入 ANCON1 = 0xFF; // AN8~AN15全部设为数字功能注意,这条命令会把与ADC、比较器相关的引脚全部切回数字功能。如果工程里同时要用ADC,就不要全写0xFF,按数据手册只关用不到的通道位。
5.2 用逻辑分析仪验证波特率偏差
TX有波形但PC端收乱码,用逻辑分析仪抓TX波形,比反复猜参数更高效。发送固定值0x55,二进制是01010101,示波器上应看到占空比稳定的方波。测量一个bit的宽度,9600波特率对应的周期是104微秒左右;如果测出112微秒,说明实际波特率偏低,优先检查FOSC是不是真的8MHz,再考虑SPBRG取整误差和内部振荡器漂移。
5.3 回环测试:把PIC18F45K22的发送直接接到接收
排错最后一步推荐回环测试,把TX引脚直接用杜邦线短接到RX引脚,运行下面这段代码:
void loopback_test(void) { const unsigned char test_data[] = "UART_TEST_OK\r\n"; unsigned int i; unsigned char received; for (i = 0; i < sizeof(test_data) - 1; i++) { uart_send_byte(test_data[i]); while (!PIR1bits.RCIF); // 等待接收完成 received = RCREG; if (received != test_data[i]) { LATBbits.LATB0 = 1; // 不匹配时点亮LED while (1); } } }逻辑说明:逐字节发送、逐字节接收并比较。接收侧用RCIF等待并读取RCREG,任何一个字节不匹配就停在死循环并点灯,失败位置可以直接看到。回环测试全部通过说明EUSART的发送路径、接收路径和接线都正常,剩下的问题只可能是外部电平转换或USB转串口模块。这套顺序同样适用于从UART_TEST迁移到实际产品代码后的串口联调,把test_data换成带设备地址和校验字节的帧格式,回环测试就能直接验证协议层的字节序。
本文还有配套的精品资源,点击获取