PIC18F66K22与XC8的Modbus RTU通信故障排查求助
我正在开发一个项目,使用XC8编译器、C语言,以PIC18F66K22作为Modbus RTU(RS485)通信的主设备。硬件采用USART2(TX=RG1、RX=RG2、R/T=RG3),硬件连接和规格已反复检查,配置看似正常,但通信未按预期工作,用串口工具监测时发现发送乱码。已查阅PIC18F66K22数据手册,仍未找到问题根源,请求协助排查。
附带代码如下:
// PIC18F66K22 Configuration Bit Settings #pragma config RETEN=OFF, INTOSCSEL=HIGH, SOSCSEL=DIG, XINST=OFF, FOSC=INTIO2, PLLCFG=ON, FCMEN=OFF, IESO=OFF // Configuration word 1 #pragma config PWRTEN=OFF, BOREN=ON, BORV=3, BORPWR=HIGH, WDTEN=SWDTDIS, WDTPS=1024 // Configuration word 2 #pragma config RTCOSC=SOSCREF, CCP2MX=PORTC, MSSPMSK=MSK5, STVREN=ON, MCLRE=OFF // Configuration word 3 #pragma config BBSIZ=BB1K // Configuration word 4 #pragma config CP0=ON, CP1=ON, CP2=ON, CP3=ON, CPB=OFF, CPD=OFF // Configuration word 5 #pragma config WRT0=OFF, WRT1=OFF, WRT2=OFF, WRT3=OFF, WRTC=ON, WRTB=OFF, WRTD=OFF // Configuration word 6 #pragma config EBRT0=OFF, EBRT1=OFF, EBRT2=OFF, EBRT3=OFF, EBRTB=OFF // Configuration word 7 #include <xc.h> //default #define _XTAL_FREQ 64000000 // 64MHz //declare used funcsions: void InitializeSerialPort(); void WriteModbusData(uint8_t* data, int length); void SendStatusRequest(uint8_t address, uint8_t* modbusData); uint16_t CalculateCRC(uint8_t* data, int length); void main(void) //start main { InitializeSerialPort(); //set UART settings. __delay_ms(100); //delay for a bit while (1) //while true { uint8_t modbusData[8]; //create an array SendStatusRequest(0x01, modbusData); //setting the array based on the MODBUS data sent in this case " 01 03 00 00 00 10 " WriteModbusData(modbusData, sizeof(modbusData)); //write the data (send). __delay_ms(100); //delay after each message & repeat the process. } } //end main void InitializeSerialPort() { // Configure pins for serial communication TRISG2 = 1; // RX pin set as input TRISG1 = 0; // TX pin set as output TRISG3 = 0; // R/T pin set as output // Set AN18 as digital I/O - data-sheet page 22 ANSEL18 = 0; // RG2 needs to be done just for 18 because the ANSEL chanel is Analog Input and the rest (17 & 19) are outputs // Disable 9-bit mode TX92 = 0; // Disable 9 bit sending. RX92 = 0; // Disable 9 Bit reception. // Enable Asynchronous serial port SYNC2 = 0; // Asynchronous mode. SPEN2 = 1; // Enable serial port pins // Prepare for transmission & reception TXEN2 = 1; // Enable transmission. CREN2 = 1; // Enable reception // High-speed mode BRGH2 = 1; //-----------------------) BAUDCON2 |= 0x08; // Calculate and set SPBRG value for 9600 BPS baud rate according to the data-sheet page 332. const unsigned int spbrg_value = (_XTAL_FREQ / (64 * 9600)) - 1; // BRG16: 16-Bit Baud Rate Register Enable bit //1 = 16-bit Baud Rate Generator – SPBRGHx and SPBRGx //0 = 8-bit Baud Rate Generator – SPBRGx only (Compatible mode), SPBRGHx value ignored PAGE 330 // BRG16 = 1 (if needed, otherwise leave it as it is) SPBRGH2 = spbrg_value & 0xFF; // Low byte SPBRG2 = (spbrg_value >> 8) & 0xFF; // High byte } void WriteModbusData(uint8_t* data, int length) { for (int i = 0; i < length; i++) { while (!TX2IF) {} //wait until transmit buffer is empty TXREG2 = data[i]; // Write data to transmit buffer } } void SendStatusRequest(uint8_t address, uint8_t* modbusData) { // Construct the Modbus status request message modbusData[0] = address; // Slave address modbusData[1] = 0x03; // Function code (Read Holding Registers) modbusData[2] = 0x00; // Starting register address (high byte) modbusData[3] = 0x00; // Starting register address (low byte) modbusData[4] = 0x00; // Quantity of registers (high byte) modbusData[5] = 0x0A; // Quantity of registers (low byte) (10) uint16_t crc = CalculateCRC(modbusData, 6); //calculate CRC for the sent data modbusData[6] = (uint8_t)(crc & 0xFF); // CRC low byte modbusData[7] = (uint8_t)(crc >> 8); // CRC high byte } uint16_t CalculateCRC(uint8_t* data, int length) //based on the CRC16 algorithm. { uint16_t crc = 0xFFFF; for (int i = 0; i < length; i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1; } return crc; }
波特率配置错误(核心问题):
代码中开启了BRGH2=1(高速模式)和BAUDCON2 |= 0x08(BRG16=1,16位波特率发生器),此时正确的波特率公式应为波特率 = FOSC/(16*(SPBRG+1)),但代码误用了8位发生器+低速模式的公式,同时寄存器字节赋值顺序也搞反了。
修正代码:// 修正波特率计算 const unsigned int spbrg_value = (_XTAL_FREQ / (16 * 9600)) - 1; // 修正寄存器赋值顺序 SPBRGH2 = (spbrg_value >> 8) & 0xFF; // 高字节 SPBRG2 = spbrg_value & 0xFF; // 低字节RS485收发控制未处理:
代码仅设置了R/T引脚为输出,但未在发送时切换到发送状态,发送完成后也未切回接收状态,会导致数据发送不完整或乱码。修改发送函数:void WriteModbusData(uint8_t* data, int length) { LATG3 = 1; // 根据硬件电路调整,高电平为发送状态 for (int i = 0; i < length; i++) { while (!TX2IF); TXREG2 = data[i]; while(!TX2TRAN); // 等待当前字节发送完成 } while(!TRMT2); // 等待移位寄存器清空,确保所有数据发送完毕 __delay_us(400); // 预留Modbus要求的帧间间隙(9600波特下至少364us) LATG3 = 0; // 切回接收状态 }UART初始化流程优化:
建议先关闭UART再配置参数,避免配置过程中出现异常状态:void InitializeSerialPort() { // 先关闭UART TXEN2 = 0; CREN2 = 0; // 原引脚配置、ANSEL配置代码保留 // 原波特率配置代码(修正后的)保留 // 最后开启发送和接收 TXEN2 = 1; CREN2 = 1; }CRC验证:
你的CRC算法是标准Modbus CRC16(多项式0xA001),字节顺序也正确,但可以用测试数据验证:输入01 03 00 00 00 0A,计算出的CRC应为0x44 0x0C,即数组第6位是0x44,第7位是0x0C,可通过打印或调试确认。时钟验证:
确认配置位FOSC=INTIO2和PLLCFG=ON是否正确生成64MHz时钟:INTOSC默认8MHz,开启PLL乘8后得到64MHz,可通过示波器测量引脚或验证延时函数的实际时长确认。
内容的提问来源于stack exchange,提问作者yahav h

