打印
[应用相关]

HAL库实现MODEBUS RTU从机与上位机通信

[复制链接]
485|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
测试板卡: 正点原子MINISTM32(STM32F103RB)

实现思路: 位机向STM32发送连续数据,STM32串口中断一直接收,期间使用定时器控制接收时间,如果在3.5个时间字符时间内没有接收到任何数据,那么定时器就判定为一帧数据接收完毕,然后根据modbus协议处理接收到的数据就可以了。

MODBUS RTU 方式的收发都需要3.5个字符的等待时间,这个时间可以通过定时器控制,两个字符之间的间隔时间按照9600的波特率算,1s可以发9600/8=1200字节,1个字节发送的时间就是1/1200≈833微秒,3.5个字符时间就是1/1200*3.5≈2917微秒,这里我为了方便直接用了5ms,如果你测试不行的话,调小定时器的超时时间。




贴一下主要的代码片段:串口接收中断回调函数:

串口1为调试串口
串口3作为modbus的收发数据端口
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == DEBUG_UART_HANDLER.Instance)
    {
        if ((DEBUG_UART_RX_STA & 0x8000) == 0)                                                  // 接收未完毕
        {
            if (DEBUG_UART_RX_STA & 0x4000)                                                     // 已经接收到了回车符
            {
                if (DEBUG_UART_RX_BYTE == '\n')     DEBUG_UART_RX_STA |= 0x8000;                // 本次接收到的是换行符,标记接收到了换行符
                else                                DEBUG_UART_RX_STA = 0;                      // 本次接收到的不是换行符,视为接收错误,状态置为初始
            }
            else
            {
                if (DEBUG_UART_RX_BYTE == '\r')     DEBUG_UART_RX_STA |= 0x4000;                // 本次接收到的是回车符,标记接收到了回车符
                else
                {
                    DEBUG_UART_RX_BUF[DEBUG_UART_RX_STA & 0x3FFF] = DEBUG_UART_RX_BYTE;         // 将本次接收到的数据保存到接收缓存中
                    if (DEBUG_UART_RX_STA++ >= DEBUG_UART_RX_LEN)   DEBUG_UART_RX_STA = 0;      // 连续接收到的数据长度高于最大接收长度,则视为接收错误
                }
            }
        }
        HAL_UART_Receive_IT(huart, &DEBUG_UART_RX_BYTE, 1);                                     // 再次使能接收中断
    }
    else if (huart->Instance == huart3.Instance)
    {
        if (MODBUS_RX_STA < MODBUS_RX_LEN)
        {
            __HAL_TIM_SET_COUNTER(&htim4, 0);                                                                // 清除5ms定时器的计数值
            if (MODBUS_RX_STA == 0)   TIM4_Set(TIM4_ENABLE);                                // 如果接收为初始状态,则启动定时器
            MODBUS_RX_BUF[MODBUS_RX_STA++] = MODBUS_RX_BYTE;                                // 接收数据
        }
        else
        {
            MODBUS_RX_STA |= 0x8000;                                                                                // 超长,标记接收结束
        }
        HAL_UART_Receive_IT(huart, &MODBUS_RX_BYTE, 1);                                                // 再次使能接收中断
    }
}


使用特权

评论回复
沙发
paotangsan|  楼主 | 2021-8-4 15:23 | 只看该作者
MODBUS RTU数据解析,我只实现了功能码3,其他功能码请根据协议自行进行补充:



// 保持寄存器
#define REG_HOLD_SIZE        10UL
uint16_t REG_HOLD[REG_HOLD_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

// 互换两个变量的值
void swap(unsigned char* byte1, unsigned char* byte2)
{
    *byte1 ^= *byte2;
    *byte2 ^= *byte1;
    *byte1 ^= *byte2;
}

inline void modbus_slave_write(const void* buf, uint32_t len)
{
    HAL_UART_Transmit(&huart3, (uint8_t *)buf, len, 1000);
}

/**
* 串口三数据接收处理
*/
void MODBUS_RecvHandler(void)
{
    // 无数据收到
    if ((MODBUS_RX_STA & 0x8000) == 0)
        return;

    // 收到的数据有误
    if (MODBUS_RX_BUF[0] != SLAVE_ADDRESS)
        goto __exit;

    // 选择相应功能码
    switch (MODBUS_RX_BUF[1])
    {
    case CMD3:
    {
        int i      = 0;
        int nread  = 0;
        int offset = 0;
        int num    = 0;

        // CRC校验
        if (usMBCRC16(MODBUS_RX_BUF, 6) != *((uint16_t *)(MODBUS_RX_BUF + 6)))
            goto __exit;

        // 大端转小端
        swap(MODBUS_RX_BUF + 2, MODBUS_RX_BUF + 3);
        swap(MODBUS_RX_BUF + 4, MODBUS_RX_BUF + 5);

        // 得到保持寄存器偏移地址
        offset = *((uint16_t *)(MODBUS_RX_BUF + 2));

        // 偏移地址错误
        if (offset >= REG_HOLD_SIZE)        goto __exit;

        // 得到要读取的保持寄存器个数
        num = *((uint16_t *)(MODBUS_RX_BUF + 4));

        // 计算实际可读的保持寄存器个数
        nread = REG_HOLD_SIZE - offset >= num ? num : REG_HOLD_SIZE - offset;

        // 实际能够被读取的保持寄存器数量错误
        if (nread <= 0)        goto __exit;

        // 填充响应数据的字节长度
        MODBUS_RX_BUF[2] = nread * sizeof(uint16_t);

        // 填充保持寄存器的值
        for (i = 0; i != nread; i++)
        {
            *((uint16_t *)(MODBUS_RX_BUF + 3 + i * 2)) = swap_uint16(REG_HOLD[offset + i]);
        }

        // 计算CRC
        *((uint16_t *)(MODBUS_RX_BUF + 3 + nread * 2)) = usMBCRC16(MODBUS_RX_BUF, 3 + nread * 2);

        // 发送给主机
        modbus_slave_write(MODBUS_RX_BUF, 3 + nread * 2 + 2);

        // 等待3.5个字符的时间
        HAL_Delay(5);

        // 改变保持寄存器的值
        for (i = 0; i != REG_HOLD_SIZE; i++)
        {
            REG_HOLD[i]++;
        }

        // 退出
        goto __exit;
    }
    default:
        goto __exit;
    }

__exit:
    MODBUS_RX_STA = 0;                // 清空接收
    return;
}



使用特权

评论回复
板凳
paotangsan|  楼主 | 2021-8-4 15:24 | 只看该作者
在主函数中轮训调用modbus的接收处理函数:

使用特权

评论回复
地板
paotangsan|  楼主 | 2021-8-4 15:24 | 只看该作者
单片机串口3我设置为9600的波特率,无流控、无奇偶校验、8bit数据、1bit停止位,上位机打开modbus poll软件,串口设置与单片机相同,Steup->Read/Write Definition,然后从机地址为1,功能码为3,偏移地址0,读取10个保持寄存器的值:

然后收到单片机的modbus rtu的回复:

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

51

主题

4079

帖子

0

粉丝