You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 05:47:05