【个人经验分享】一个完整的Modbus RTU 主机库

一个完整的Modbus RTU 主机库

  • 一个完整的Modbus RTU 主机库

    • 1.1 Modbus RTU 超时机制

    • 1.2 Modbus 的错误机制

  • 2. 协议栈代码解析

    • 2.1 核心数据结构

    • 2.2 主要功能码支持

    • 2.3 工作流程

  • 3. 移植指南

  • 4. Modbus主机库使用示例

    • 4.1 基础配置和初始化

    • 2. 使用示例代码

  • 5. 完整代码

1.1 Modbus RTU 超时机制

  • “Modbus RTU没有固定的帧头帧尾,依靠3.5个字符时间的静默间隔来区分数据帧” 这句话描述了Modbus RTU协议中的一个关键技术特点。

  • • 工作原理示意

    帧1: [字符1][字符2][字符3]...[字符N]    3.5字符静默    帧2: [字符1][字符2]...
          ↑帧内间隔≤1.5字符    ↑帧结束检测点    ↑静默≥3.5字符    ↑新帧开始
    
  • • 不同波特率下3.5字符时间

    波特率 每字符时间 3.5字符时间
    2400 bps 4.17 ms 14.6 ms
    4800 bps 2.08 ms 7.29 ms
    9600 bps 1.04 ms 3.65 ms
    19200 bps 0.52 ms 1.82 ms
    38400 bps 0.26 ms 0.91 ms

这一机制在代码中使用硬件定时器完成


/* 硬件定时器 TIM2 START 最小1us时间精度  使用 恩富莱 的代码*/
/* 保存 TIM定时中断到后执行的回调函数指针 */
static void (*s_TIM_CallBack1)(void);
static void (*s_TIM_CallBack2)(void);
static void (*s_TIM_CallBack3)(void);
static void (*s_TIM_CallBack4)(void);

/**
 * @brief  初始化硬件定时器
 * 
 */
void bsp_InitHardTimer(void)
{
    TIM_HandleTypeDef  TimHandle = {0};
    uint32_t usPeriod;
    uint16_t usPrescaler;
    uint32_t uiTIMxCLK;
    
    __HAL_RCC_TIM2_CLK_ENABLE();
    
    uiTIMxCLK = SystemCoreClock;

    usPrescaler = uiTIMxCLK / 1000000 - 1;    /* 分频比 = 1 */
    

    usPeriod = 0xFFFF;
    

    /* 
       设置分频为usPrescaler后,那么定时器计数器计1次就是1us
       而参数usPeriod的值是决定了最大计数:
       usPeriod = 0xFFFF 表示最大0xFFFF微秒。
       usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微秒。
    */
    TimHandle.Instance = TIM2;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    
    if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
    {
        // Error_Handler(__FILE__, __LINE__);
        DEBUG_PRINTF(LEVEL_ERROR, "HAL_TIM_Base_Init error\n");
    }

    /* 配置定时器中断,给CC捕获比较中断使用 */
    {
        HAL_NVIC_SetPriority(TIM2_IRQn, 0, 2);
        HAL_NVIC_EnableIRQ(TIM2_IRQn);    
    }
    
    /* 启动定时器 */
    HAL_TIM_Base_Start(&TimHandle);
}

/**
 * @brief                   启动硬件定时器
 *                          定时时间到后执行回调函数。可以同时启动4个定时器通道,互不干扰。
 *                          是16位定时器,最大定时时间为65535us,即65.535ms
 * 
 * @param _CC               定时器通道  1~4
 * @param _uiTimeOut        超时时间, 单位 1us. 对于16位定时器,最大 65.5ms; 对于32位定时器,最大 4294秒
 * @param _pCallBack        定时时间到后,被执行的函数
 */
void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack)
{
    uint32_t cnt_now;
    uint32_t cnt_tar;
    TIM_TypeDef* TIMx = TIM2;
    
    /* 无需补偿延迟,实测精度正负1us */
    
    cnt_now = TIMx->CNT; 
    cnt_tar = cnt_now + _uiTimeOut;            /* 计算捕获的计数器值 */
    if (_CC == 1)
    {
        s_TIM_CallBack1 = (void (*)(void))_pCallBack;

        TIMx->CCR1 = cnt_tar;                 /* 设置捕获比较计数器CC1 */
        TIMx->SR = (uint16_t)~TIM_IT_CC1;   /* 清除CC1中断标志 */
        TIMx->DIER |= TIM_IT_CC1;            /* 使能CC1中断 */

    }
    else if (_CC == 2)
    {
        s_TIM_CallBack2 = (void (*)(void))_pCallBack;

        TIMx->CCR2 = cnt_tar;                /* 设置捕获比较计数器CC2 */
        TIMx->SR = (uint16_t)~TIM_IT_CC2;    /* 清除CC2中断标志 */
        TIMx->DIER |= TIM_IT_CC2;            /* 使能CC2中断 */
    }
    else if (_CC == 3)
    {
        s_TIM_CallBack3 = (void (*)(void))_pCallBack;

        TIMx->CCR3 = cnt_tar;                /* 设置捕获比较计数器CC3 */
        TIMx->SR = (uint16_t)~TIM_IT_CC3;    /* 清除CC3中断标志 */
        TIMx->DIER |= TIM_IT_CC3;            /* 使能CC3中断 */
    }
    else if (_CC == 4)
    {
        s_TIM_CallBack4 = (void (*)(void))_pCallBack;

        TIMx->CCR4 = cnt_tar;                /* 设置捕获比较计数器CC4 */
        TIMx->SR = (uint16_t)~TIM_IT_CC4;    /* 清除CC4中断标志 */
        TIMx->DIER |= TIM_IT_CC4;            /* 使能CC4中断 */
    }
    else
    {
        return;
    }
}

/**
 * @brief                       TIM2中断处理函数
 * 
 */
void TIM2_IRQHandler(void)
{
    uint16_t itstatus = 0x0, itenable = 0x0;
    TIM_TypeDef* TIMx = TIM2;
    
    
      itstatus = TIMx->SR & TIM_IT_CC1;
    itenable = TIMx->DIER & TIM_IT_CC1;
    
    if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
    {
        TIMx->SR = (uint16_t)~TIM_IT_CC1;
        TIMx->DIER &= (uint16_t)~TIM_IT_CC1;        /* 禁能CC1中断 */    
        // DEBUG_PRINTF(LEVEL_DEBUG, "TIM2->CCR1: %d\n", TIMx->CCR1);
        /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
        s_TIM_CallBack1();
    }

    itstatus = TIMx->SR & TIM_IT_CC2;
    itenable = TIMx->DIER & TIM_IT_CC2;
    if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
    {
        TIMx->SR = (uint16_t)~TIM_IT_CC2;
        TIMx->DIER &= (uint16_t)~TIM_IT_CC2;        /* 禁能CC2中断 */    

        /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
        s_TIM_CallBack2();
    }

    itstatus = TIMx->SR & TIM_IT_CC3;
    itenable = TIMx->DIER & TIM_IT_CC3;
    if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
    {
        TIMx->SR = (uint16_t)~TIM_IT_CC3;
        TIMx->DIER &= (uint16_t)~TIM_IT_CC3;        /* 禁能CC2中断 */    

        /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
        s_TIM_CallBack3();
    }

    itstatus = TIMx->SR & TIM_IT_CC4;
    itenable = TIMx->DIER & TIM_IT_CC4;
    if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
    {
        TIMx->SR = (uint16_t)~TIM_IT_CC4;
        TIMx->DIER &= (uint16_t)~TIM_IT_CC4;        /* 禁能CC4中断 */    

        /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
        s_TIM_CallBack4();
    }    
}

1.2 Modbus 的错误机制

  • • 错误码如下:我需要在代码中完善这些

    错误码(Hex) 名称 说明
    01 Illegal Function 从站不支持请求的功能码(如设备不支持 0x05 写线圈)。
    02 Illegal Data Address 请求的寄存器/线圈地址无效(如超出设备支持的范围)。
    03 Illegal Data Value 请求的数据值非法(如写入的寄存器值超出允许范围)。
    04 Slave Device Failure 从站设备执行请求时发生内部错误(如硬件故障)。
    05 Acknowledge 从站已接收请求,但需要较长时间处理(通常用于编程命令)。
    06 Slave Device Busy 从站正忙,无法处理当前请求(需重试)。
    08 Memory Parity Error 从站检测到存储器奇偶校验错误(罕见,通常需硬件修复)。
    0A Gateway Path Unavailable 网关路径不可用(仅适用于 Modbus 网关设备)。
    0B Gateway Target Failed 网关目标设备无响应(仅适用于 Modbus 网关设备)。

2. 协议栈代码解析

2.1 核心数据结构

typedef struct {
    uint8_t RxBuf[H_RX_BUF_SIZE];    // 接收缓冲区
    uint8_t TxBuf[H_TX_BUF_SIZE];    // 发送缓冲区
    uint8_t RxCount;                  // 接收计数
    uint8_t TxCount;                  // 发送计数
    
    // 寄存器地址存储(用于匹配响应)
    uint16_t Reg01H, Reg02H, Reg03H, Reg04H;
    uint8_t RegNum;                   // 请求的寄存器数量
    
    // 应答标志位
    uint8_t fAck01H, fAck02H, fAck03H, fAck04H;
    uint8_t fAck05H, fAck06H, fAck10H;
    
    int16_t *RegBuf;                  // 存储读取到的寄存器数据
    uint8_t address;                  // 当前操作的从机地址
} MODH_T;

2.2 主要功能码支持

功能码 名称 描述
0x01 读线圈状态 读取1-n个线圈的ON/OFF状态
0x02 读离散输入 读取1-n个离散输入的ON/OFF状态
0x03 读保持寄存器 读取1-n个保持寄存器的值
0x04 读输入寄存器 读取1-n个输入寄存器的值
0x05 写单个线圈 强制单个线圈为ON/OFF
0x06 写单个寄存器 写入单个保持寄存器的值
0x10 写多个寄存器 写入多个保持寄存器的值

2.3 工作流程

  1. 1. 数据发送流程

    // 以03H功能码为例
    void MODH_Send03H(uint8_t _addr, uint16_t _reg, uint16_t _num, int16_t *_buf)
    {
        // 1. 填充发送缓冲区
        g_tModH.TxBuf[0] = _addr;        // 从站地址
        g_tModH.TxBuf[1] = 0x03;        // 功能码
        g_tModH.TxBuf[2] = _reg >> 8;   // 寄存器地址高字节
        g_tModH.TxBuf[3] = _reg;        // 寄存器地址低字节
        g_tModH.TxBuf[4] = _num >> 8;   // 寄存器数量高字节
        g_tModH.TxBuf[5] = _num;        // 寄存器数量低字节
        
        // 2. 计算并添加CRC校验
        crc = CRC16_Modbus(g_tModH.TxBuf, g_tModH.TxCount);
        g_tModH.TxBuf[g_tModH.TxCount++] = crc >> 8;
        g_tModH.TxBuf[g_tModH.TxCount++] = crc;
        
        // 3. 通过串口发送数据
        MODH_SendPacket(g_tModH.TxBuf, g_tModH.TxCount);
        
        // 4. 保存上下文信息用于后续解析
        g_tModH.fAck03H = 0;            // 清空应答标志
        g_tModH.RegNum = _num;          // 保存寄存器数量
        g_tModH.Reg03H = _reg;          // 保存寄存器起始地址
        g_tModH.RegBuf = _buf;          // 保存数据缓冲区指针
    }
    
  2. 2. RTU 模式帧同步:依靠3.5个字符时间的静默间隔来区分数据帧

    void MODH_ReciveNew(uint8_t _data)
    {
        // 1. 根据波特率获取超时时间(3.5字符时间)
        // 9600bps → 4.0ms
        // 19200bps → 2.0ms
        // 38400bps及以上 → 1.75ms
        
        // 2. 启动硬件定时器
        bsp_StartHardTimer(2, timeout, MODH_RxTimeOut);
        
        // 3. 存储接收到的字节
        if (g_tModH.RxCount < H_RX_BUF_SIZE) {
            g_tModH.RxBuf[g_tModH.RxCount++] = _data;
        }
    }
    
    static void MODH_RxTimeOut(void)
    {
        // 定时器超时,表示接收到完整帧
        g_modh_timeout = 1;
    }
    
  3. 3. 数据解析流程

    void MODH_Poll(void)
    {
        if (g_modh_timeout == 0) return;  // 未收到完整帧
        
        // 1. 基本长度检查(至少4字节)
        if (g_tModH.RxCount < 4) goto err_ret;
        
        // 2. CRC校验
        crc1 = CRC16_Modbus(g_tModH.RxBuf, g_tModH.RxCount);
        if (crc1 != 0) goto err_ret;  // CRC错误
        
        // 3. 应用层协议解析
        MODH_AnalyzeApp();
        
    err_ret:
        g_tModH.RxCount = 0;  // 重置接收计数器
    }
    
  4. 4. 应用层响应解析

    static void MODH_Read_03H(void)
    {
        // 1. 获取数据长度
        bytes = g_tModH.RxBuf[2];  // 第3字节是数据字节数
        
        // 2. 验证数据长度匹配
        if (bytes == (g_tModH.RegNum * 2)) {
            // 3. 解析数据(大端序转换)
            p = &g_tModH.RxBuf[3];
            for (uint8_t i = 0; i < g_tModH.RegNum; i++) {
                g_tModH.RegBuf[i] = BEBufToUint16(p);  // 大端序转整型
                p += 2;
            }
            // 4. 设置应答标志
            g_tModH.fAck03H = 1;
        }
    }
    

3. 移植指南

  1. 1. 移植串口,将创建串口,并且打开串口中断,在中断中将数据压入

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    
      if (huart->Instance == USART2)
      {
        HAL_UART_Receive_IT(&huart2, &g_usart2Rx, 1);
        MODH_ReciveNew(g_usart2Rx);
      }
    }
    
  2. 2. 移植硬件定时器,完善如下硬件定时器的函数,更改为适应单片机的架构。

    
    /* 硬件定时器 TIM2 START 最小1us时间精度  使用 恩富莱 的代码*/
    /* 保存 TIM定时中断到后执行的回调函数指针 */
    static void (*s_TIM_CallBack1)(void);
    static void (*s_TIM_CallBack2)(void);
    static void (*s_TIM_CallBack3)(void);
    static void (*s_TIM_CallBack4)(void);
    
    /**
     * @brief  初始化硬件定时器
     * 
     */
    void bsp_InitHardTimer(void)
    {
        TIM_HandleTypeDef  TimHandle = {0};
        uint32_t usPeriod;
        uint16_t usPrescaler;
        uint32_t uiTIMxCLK;
        
        __HAL_RCC_TIM2_CLK_ENABLE();
        
        uiTIMxCLK = SystemCoreClock;
    
        usPrescaler = uiTIMxCLK / 1000000 - 1;    /* 分频比 = 1 */
        
    
        usPeriod = 0xFFFF;
        
    
        /* 
           设置分频为usPrescaler后,那么定时器计数器计1次就是1us
           而参数usPeriod的值是决定了最大计数:
           usPeriod = 0xFFFF 表示最大0xFFFF微秒。
           usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微秒。
        */
        TimHandle.Instance = TIM2;
        TimHandle.Init.Prescaler         = usPrescaler;
        TimHandle.Init.Period            = usPeriod;
        TimHandle.Init.ClockDivision     = 0;
        TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
        TimHandle.Init.RepetitionCounter = 0;
        TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
        
        if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
        {
            // Error_Handler(__FILE__, __LINE__);
            DEBUG_PRINTF(LEVEL_ERROR, "HAL_TIM_Base_Init error\n");
        }
    
        /* 配置定时器中断,给CC捕获比较中断使用 */
        {
            HAL_NVIC_SetPriority(TIM2_IRQn, 0, 2);
            HAL_NVIC_EnableIRQ(TIM2_IRQn);    
        }
        
        /* 启动定时器 */
        HAL_TIM_Base_Start(&TimHandle);
    }
    
    /**
     * @brief                   启动硬件定时器
     *                          定时时间到后执行回调函数。可以同时启动4个定时器通道,互不干扰。
     *                          是16位定时器,最大定时时间为65535us,即65.535ms
     * 
     * @param _CC               定时器通道  1~4
     * @param _uiTimeOut        超时时间, 单位 1us. 对于16位定时器,最大 65.5ms; 对于32位定时器,最大 4294秒
     * @param _pCallBack        定时时间到后,被执行的函数
     */
    void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack)
    {
        uint32_t cnt_now;
        uint32_t cnt_tar;
        TIM_TypeDef* TIMx = TIM2;
        
        /* 无需补偿延迟,实测精度正负1us */
        
        cnt_now = TIMx->CNT; 
        cnt_tar = cnt_now + _uiTimeOut;            /* 计算捕获的计数器值 */
        if (_CC == 1)
        {
            s_TIM_CallBack1 = (void (*)(void))_pCallBack;
    
            TIMx->CCR1 = cnt_tar;                 /* 设置捕获比较计数器CC1 */
            TIMx->SR = (uint16_t)~TIM_IT_CC1;   /* 清除CC1中断标志 */
            TIMx->DIER |= TIM_IT_CC1;            /* 使能CC1中断 */
    
        }
        else if (_CC == 2)
        {
            s_TIM_CallBack2 = (void (*)(void))_pCallBack;
    
            TIMx->CCR2 = cnt_tar;                /* 设置捕获比较计数器CC2 */
            TIMx->SR = (uint16_t)~TIM_IT_CC2;    /* 清除CC2中断标志 */
            TIMx->DIER |= TIM_IT_CC2;            /* 使能CC2中断 */
        }
        else if (_CC == 3)
        {
            s_TIM_CallBack3 = (void (*)(void))_pCallBack;
    
            TIMx->CCR3 = cnt_tar;                /* 设置捕获比较计数器CC3 */
            TIMx->SR = (uint16_t)~TIM_IT_CC3;    /* 清除CC3中断标志 */
            TIMx->DIER |= TIM_IT_CC3;            /* 使能CC3中断 */
        }
        else if (_CC == 4)
        {
            s_TIM_CallBack4 = (void (*)(void))_pCallBack;
    
            TIMx->CCR4 = cnt_tar;                /* 设置捕获比较计数器CC4 */
            TIMx->SR = (uint16_t)~TIM_IT_CC4;    /* 清除CC4中断标志 */
            TIMx->DIER |= TIM_IT_CC4;            /* 使能CC4中断 */
        }
        else
        {
            return;
        }
    }
    
    /**
     * @brief                       TIM2中断处理函数
     * 
     */
    void TIM2_IRQHandler(void)
    {
        uint16_t itstatus = 0x0, itenable = 0x0;
        TIM_TypeDef* TIMx = TIM2;
        
        
          itstatus = TIMx->SR & TIM_IT_CC1;
        itenable = TIMx->DIER & TIM_IT_CC1;
        
        if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
        {
            TIMx->SR = (uint16_t)~TIM_IT_CC1;
            TIMx->DIER &= (uint16_t)~TIM_IT_CC1;        /* 禁能CC1中断 */    
            // DEBUG_PRINTF(LEVEL_DEBUG, "TIM2->CCR1: %d\n", TIMx->CCR1);
            /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
            s_TIM_CallBack1();
        }
    
        itstatus = TIMx->SR & TIM_IT_CC2;
        itenable = TIMx->DIER & TIM_IT_CC2;
        if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
        {
            TIMx->SR = (uint16_t)~TIM_IT_CC2;
            TIMx->DIER &= (uint16_t)~TIM_IT_CC2;        /* 禁能CC2中断 */    
    
            /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
            s_TIM_CallBack2();
        }
    
        itstatus = TIMx->SR & TIM_IT_CC3;
        itenable = TIMx->DIER & TIM_IT_CC3;
        if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
        {
            TIMx->SR = (uint16_t)~TIM_IT_CC3;
            TIMx->DIER &= (uint16_t)~TIM_IT_CC3;        /* 禁能CC2中断 */    
    
            /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
            s_TIM_CallBack3();
        }
    
        itstatus = TIMx->SR & TIM_IT_CC4;
        itenable = TIMx->DIER & TIM_IT_CC4;
        if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
        {
            TIMx->SR = (uint16_t)~TIM_IT_CC4;
            TIMx->DIER &= (uint16_t)~TIM_IT_CC4;        /* 禁能CC4中断 */    
    
            /* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
            s_TIM_CallBack4();
        }    
    }
    
  1. 3. 在 main 函数中周期调用 MODH_Poll();

      while (1)
      {
        BSP_Timer_Process();     
        MODH_Poll();      /* 空闲处理 */ 
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
    
  2. 4. 移植完成。

4. Modbus主机库使用示例

4.1 基础配置和初始化

/* 1. 包含头文件 */
#include "modbus_host.h"
#include "usart.h"

/* 2. 定义设备地址(根据实际设备设置) */
#define PLC_ADDRESS        0x01      // PLC从站地址
#define TEMP_SENSOR_ADDR   0x02      // 温度传感器地址
#define IO_MODULE_ADDR     0x03      // IO模块地址

/* 3. 定义寄存器映射(根据设备手册) */
// 保持寄存器地址
#define REG_MOTOR_SPEED    0x1000    // 电机速度设定
#define REG_TEMPERATURE    0x1001    // 温度设定值
#define REG_PRESSURE       0x1002    // 压力设定值
#define REG_FLOW_RATE      0x1003    // 流量设定值

// 输入寄存器地址
#define REG_ACTUAL_TEMP    0x0000    // 实际温度
#define REG_ACTUAL_PRESS   0x0001    // 实际压力
#define REG_ACTUAL_FLOW    0x0002    // 实际流量

// 线圈寄存器地址
#define RELAY_1            0x0000    // 继电器1
#define RELAY_2            0x0001    // 继电器2
#define RELAY_3            0x0002    // 继电器3
#define RELAY_4            0x0003    // 继电器4

// 离散输入地址
#define SWITCH_1           0x0000    // 开关1状态
#define SWITCH_2           0x0001    // 开关2状态

/* 4. 定义数据缓冲区 */
int16_t holding_registers[10];       // 保持寄存器数据
int16_t input_registers[10];         // 输入寄存器数据
int16_t coil_states[10];             // 线圈状态数据
int16_t discrete_inputs[10];         // 离散输入数据

/* 5. 初始化Modbus(通常在main函数中调用) */
void Modbus_Init(void)
{
    // 初始化串口(波特率9600,8位数据,无校验,1位停止位)
    USART2_Init(9600);
    
    // 初始化硬件定时器用于Modbus超时检测
    bsp_InitTimer();
    
    // 其他必要的初始化代码...
}

2. 使用示例代码

/* 工业控制系统Modbus通信示例 */
void IndustrialControl_Example(void)
{
    uint8_t retry_count = 0;
    uint8_t success = 0;
    
    /* 示例1: 读取温度传感器的当前温度(功能码04H) */
    int16_t temperature_data[1] = {0};
    
    printf("=== 读取温度传感器数据 ===\n");
    
    // 方法1: 直接调用(带超时和重试)
    if (MODH_ReadParam_04H(TEMP_SENSOR_ADDR, REG_ACTUAL_TEMP, 1, temperature_data) == 1)
    {
        printf("当前温度: %.1f°C\n", temperature_data[0] / 10.0);
    }
    else
    {
        printf("温度读取失败,请检查设备连接\n");
    }
    
    /* 示例2: 读取多个模拟量输入(功能码04H) */
    int16_t analog_inputs[3] = {0};  // 温度、压力、流量
    
    printf("\n=== 读取所有模拟量输入 ===\n");
    
    // 手动实现带重试机制的读取
    for (retry_count = 0; retry_count < 3; retry_count++)
    {
        success = MODH_ReadParam_04H(PLC_ADDRESS, REG_ACTUAL_TEMP, 3, analog_inputs);
        if (success == 1)
        {
            printf("读取成功(第%d次重试):\n", retry_count + 1);
            printf("  温度: %.1f°C\n", analog_inputs[0] / 10.0);
            printf("  压力: %.1f kPa\n", analog_inputs[1] / 100.0);
            printf("  流量: %.1f L/min\n", analog_inputs[2] / 10.0);
            break;
        }
        else
        {
            printf("第%d次读取失败,等待500ms后重试...\n", retry_count + 1);
            Delay_ms(500);  // 等待500ms后重试
        }
    }
    
    if (success == 0)
    {
        printf("模拟量读取失败,进入安全模式\n");
        Emergency_Shutdown();
    }
    
    /* 示例3: 写入电机速度设定值(功能码06H) */
    printf("\n=== 设置电机速度 ===\n");
    
    int16_t motor_speed = 1500;  // 1500 RPM
    
    if (MODH_WriteParam_06H(PLC_ADDRESS, REG_MOTOR_SPEED, motor_speed) == 1)
    {
        printf("电机速度已设置为: %d RPM\n", motor_speed);
        
        // 验证写入是否成功
        int16_t verify_speed = 0;
        if (MODH_ReadParam_03H(PLC_ADDRESS, REG_MOTOR_SPEED, 1, &verify_speed) == 1)
        {
            if (verify_speed == motor_speed)
            {
                printf("验证成功: 设定值与实际值一致\n");
            }
            else
            {
                printf("警告: 设定值(%d)与实际值(%d)不符\n", 
                       motor_speed, verify_speed);
            }
        }
    }
    else
    {
        printf("电机速度设置失败\n");
    }
    
    /* 示例4: 批量写入多个参数(功能码10H) */
    printf("\n=== 批量设置工艺参数 ===\n");
    
    int16_t process_params[3] = {500, 2000, 100};  // 温度500,压力2000,流量100
    
    if (MODH_WriteParam_10H(PLC_ADDRESS, REG_TEMPERATURE, 3, process_params) == 1)
    {
        printf("工艺参数批量设置成功\n");
        
        // 读取验证
        int16_t verify_params[3] = {0};
        if (MODH_ReadParam_03H(PLC_ADDRESS, REG_TEMPERATURE, 3, verify_params) == 1)
        {
            for (int i = 0; i < 3; i++)
            {
                if (process_params[i] == verify_params[i])
                {
                    printf("  参数%d: 设置成功\n", i+1);
                }
                else
                {
                    printf("  参数%d: 设置失败(期望:%d,实际:%d)\n", 
                           i+1, process_params[i], verify_params[i]);
                }
            }
        }
    }
    
    /* 示例5: 读取和写入线圈(功能码01H和05H) */
    printf("\n=== 控制继电器状态 ===\n");
    
    // 读取当前所有继电器状态
    int16_t relay_status[2] = {0};  // 每个int16可存储16个继电器的状态
    
    if (MODH_ReadParam_01H(IO_MODULE_ADDR, RELAY_1, 16, relay_status) == 1)
    {
        printf("当前继电器状态:\n");
        for (int i = 0; i < 16; i++)
        {
            int byte_index = i / 8;
            int bit_index = i % 8;
            int state = (relay_status[byte_index] >> bit_index) & 0x01;
            printf("  继电器%d: %s\n", i+1, state ? "ON" : "OFF");
        }
    }
    
    // 控制单个继电器(打开继电器1)
    if (MODH_WriteParam_05H(IO_MODULE_ADDR, RELAY_1, MODH_ON) == 1)
    {
        printf("继电器1已打开\n");
    }
    
    // 读取开关状态(功能码02H)
    printf("\n=== 读取开关状态 ===\n");
    
    int16_t switch_states[1] = {0};
    if (MODH_ReadParam_02H(IO_MODULE_ADDR, SWITCH_1, 8, switch_states) == 1)
    {
        for (int i = 0; i < 8; i++)
        {
            int state = (switch_states[0] >> i) & 0x01;
            printf("  开关%d: %s\n", i+1, state ? "闭合" : "断开");
        }
    }
}

/* 示例6: 多设备轮询监控 */
void MultiDevice_Monitoring(void)
{
    typedef struct {
        uint8_t address;
        char* name;
        uint16_t reg_address;
        int16_t last_value;
        uint8_t error_count;
    } DeviceInfo;
    
    // 设备列表
    DeviceInfo devices[] = {
        {0x01, "主控制器", 0x1000, 0, 0},
        {0x02, "温度传感器", 0x0000, 0, 0},
        {0x03, "压力传感器", 0x0000, 0, 0},
        {0x04, "流量计", 0x0000, 0, 0},
    };
    
    int16_t current_value;
    
    printf("=== 多设备监控轮询 ===\n");
    
    for (int i = 0; i < sizeof(devices)/sizeof(devices[0]); i++)
    {
        if (MODH_ReadParam_03H(devices[i].address, devices[i].reg_address, 1, &current_value) == 1)
        {
            devices[i].error_count = 0;  // 清除错误计数
            
            if (current_value != devices[i].last_value)
            {
                printf("[%s] 值变化: %d -> %d\n", 
                       devices[i].name, devices[i].last_value, current_value);
                devices[i].last_value = current_value;
            }
        }
        else
        {
            devices[i].error_count++;
            printf("[%s] 通信错误,错误计数: %d\n", 
                   devices[i].name, devices[i].error_count);
            
            if (devices[i].error_count >= 3)
            {
                printf("[%s] 设备故障,触发报警\n", devices[i].name);
                // 触发报警处理
            }
        }
        
        Delay_ms(100);  // 每个设备间隔100ms
    }
}

/* 示例7: 数据记录器 */
void DataLogger_Example(void)
{
    typedef struct {
        uint32_t timestamp;
        int16_t temperature;
        int16_t pressure;
        int16_t flow_rate;
        uint8_t device_status;
    } LogEntry;
    
    LogEntry log_data;
    int16_t sensor_data[3];
    
    // 获取时间戳
    log_data.timestamp = GetSystemTick();
    
    // 读取所有传感器数据
    if (MODH_ReadParam_04H(PLC_ADDRESS, 0x0000, 3, sensor_data) == 1)
    {
        log_data.temperature = sensor_data[0];
        log_data.pressure = sensor_data[1];
        log_data.flow_rate = sensor_data[2];
        
        // 读取设备状态
        int16_t status_bits;
        if (MODH_ReadParam_01H(PLC_ADDRESS, 0x0000, 1, &status_bits) == 1)
        {
            log_data.device_status = (uint8_t)status_bits;
        }
        
        // 保存到日志(模拟)
        SaveToLog(&log_data);
        
        printf("数据记录完成:\n");
        printf("  时间: %lu\n", log_data.timestamp);
        printf("  温度: %d\n", log_data.temperature);
        printf("  压力: %d\n", log_data.pressure);
        printf("  流量: %d\n", log_data.flow_rate);
        printf("  状态: 0x%02X\n", log_data.device_status);
    }
}

/* 示例8: 错误处理和恢复 */
void Modbus_ErrorHandling_Example(void)
{
    uint8_t i;
    int16_t data[4];
    
    printf("=== Modbus通信错误处理示例 ===\n");
    
    // 尝试读取数据,最多重试3次
    for (i = 0; i < 3; i++)
    {
        uint8_t result = MODH_ReadParam_03H(0x01, 0x1000, 4, data);
        
        switch (result)
        {
            case 1:  // 成功
                printf("第%d次尝试: 读取成功\n", i+1);
                Process_Data(data);
                return;  // 成功则退出
            
            case 0:  // 超时
                printf("第%d次尝试: 通信超时\n", i+1);
                if (i < 2)  // 不是最后一次尝试
                {
                    printf("  等待%d秒后重试...\n", i+1);
                    Delay_ms((i+1) * 1000);  // 指数退避
                    
                    // 尝试重新初始化串口
                    USART2_Reinit();
                }
                break;
                
            default:  // 其他错误
                printf("第%d次尝试: 错误代码 %d\n", i+1, result);
                break;
        }
    }
    
    // 所有重试都失败
    printf("通信失败,切换到备用设备\n");
    SwitchToBackupDevice();
}

/* 示例9: 设备配置和校准 */
void Device_Configuration_Example(void)
{
    printf("=== 设备配置流程 ===\n");
    
    /* 步骤1: 读取设备信息 */
    int16_t device_info[4];
    if (MODH_ReadParam_03H(0x01, 0x9990, 4, device_info) == 1)
    {
        printf("设备信息:\n");
        printf("  型号: 0x%04X\n", device_info[0]);
        printf("  版本: %d.%d\n", device_info[1] >> 8, device_info[1] & 0xFF);
        printf("  序列号: %d\n", (device_info[2] << 16) | device_info[3]);
    }
    
    /* 步骤2: 进入校准模式 */
    printf("\n进入校准模式...\n");
    if (MODH_WriteParam_06H(0x01, 0x8000, 0x5555) == 1)
    {
        printf("校准模式已激活\n");
        Delay_ms(1000);  // 等待设备准备
        
        /* 步骤3: 设置零点校准 */
        printf("执行零点校准...\n");
        if (MODH_WriteParam_06H(0x01, 0x8001, 0x0001) == 1)
        {
            printf("零点校准完成\n");
        }
        
        /* 步骤4: 设置满量程校准值 */
        printf("设置满量程值...\n");
        if (MODH_WriteParam_06H(0x01, 0x8002, 1000) == 1)
        {
            printf("满量程设置完成\n");
        }
        
        /* 步骤5: 退出校准模式 */
        printf("退出校准模式...\n");
        if (MODH_WriteParam_06H(0x01, 0x8000, 0xAAAA) == 1)
        {
            printf("校准完成,设备返回正常模式\n");
        }
    }
}

/* 主循环中的Modbus任务调度 */
void Modbus_Task_Scheduler(void)
{
    static uint32_t last_read_time = 0;
    static uint8_t poll_phase = 0;
    uint32_t current_time = GetTimerMs();
    
    // 每100ms执行一次Modbus轮询
    if (bsp_CheckRunTime(last_read_time) > 100)
    {
        last_read_time = current_time;
        
        // 分阶段轮询不同设备
        switch (poll_phase)
        {
            case 0:
                // 阶段0: 读取快速变化的数据(如开关状态)
                Read_Switch_States();
                break;
                
            case 1:
                // 阶段1: 读取模拟量数据(如温度、压力)
                Read_Analog_Values();
                break;
                
            case 2:
                // 阶段2: 写入控制命令
                Write_Control_Commands();
                break;
                
            case 3:
                // 阶段3: 设备状态检查
                Check_Device_Status();
                break;
        }
        
        poll_phase = (poll_phase + 1) % 4;  // 4个阶段循环
    }
    
    // 必须调用Modbus轮询函数处理接收
    MODH_Poll();
}

/* 示例10: 实用函数封装 */
// 读取温度值(带单位转换)
float Read_Temperature_Celsius(uint8_t device_addr)
{
    int16_t raw_data;
    
    if (MODH_ReadParam_04H(device_addr, REG_ACTUAL_TEMP, 1, &raw_data) == 1)
    {
        // 假设原始数据是0.1°C单位
        return raw_data / 10.0;
    }
    
    return -999.9;  // 错误值
}

// 控制继电器(带状态反馈)
uint8_t Control_Relay(uint8_t device_addr, uint16_t relay_num, uint8_t state)
{
    uint16_t value = (state ? MODH_ON : MODH_OFF);
    
    if (MODH_WriteParam_05H(device_addr, relay_num, value) == 1)
    {
        // 等待并验证状态
        Delay_ms(50);
        
        int16_t verify_state;
        if (MODH_ReadParam_01H(device_addr, relay_num, 1, &verify_state) == 1)
        {
            uint8_t actual_state = (verify_state == MODH_ON ? 1 : 0);
            if (actual_state == state)
            {
                return 1;  // 成功
            }
        }
    }
    
    return 0;  // 失败
}

/* 主函数中的使用示例 */
int main(void)
{
    // 系统初始化
    System_Init();
    Modbus_Init();
    
    printf("Modbus主机通信系统启动\n");
    printf("系统时间: %s\n", GetSystemTime());
    
    // 设备自检
    printf("\n=== 设备自检 ===\n");
    Device_SelfTest();
    
    // 主循环
    while (1)
    {
        // 任务1: 监控数据(每秒一次)
        static uint32_t last_monitor = 0;
        if (bsp_CheckRunTime(last_monitor) > 1000)
        {
            last_monitor = GetTimerMs();
            IndustrialControl_Example();
        }
        
        // 任务2: 多设备轮询(每5秒一次)
        static uint32_t last_poll = 0;
        if (bsp_CheckRunTime(last_poll) > 5000)
        {
            last_poll = GetTimerMs();
            MultiDevice_Monitoring();
        }
        
        // 任务3: 数据记录(每10秒一次)
        static uint32_t last_log = 0;
        if (bsp_CheckRunTime(last_log) > 10000)
        {
            last_log = GetTimerMs();
            DataLogger_Example();
        }
        
        // 必须不断调用Modbus轮询
        MODH_Poll();
        
        // 系统空闲处理
        BSP_Idle();
    }
}