STM32F103x系列RS485、MODBUS学习资料,RS485通信历程(标准库、HAL库);应用层协议MODBUS协议,包含自己编写的MODBUS协议,移植开源FreeModbus协议;移植带操作系统的资料(FreeRTOS、RT_Thread),源码都测试过可以使用,工程规范、逻辑清楚、可以快速移植的自己的板子,学习MODBUS通信协

RS485和MODBUS在工业控制领域几乎是黄金搭档。手里有STM32F103这类经典MCU的兄弟,想低成本实现稳定通信,这篇实战笔记直接上干货。

一、先搞定硬件层:RS485驱动

RS485是半双工通信,核心是控制DE引脚切换收发状态。以标准库为例,初始化代码里要特别注意方向控制逻辑:

// DE引脚配置(PA1控制收发)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// USART2配置(波特率9600,8N1)
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStruct);
USART_Cmd(USART2, ENABLE);

发送数据前拉高DE,接收前拉低,这是基本操作。HAL库用户直接用HALUARTTransmit()函数,但要注意在发送前后加DE控制:

// HAL库发送封装
void RS485_Send(uint8_t *pData, uint16_t len) {
    HAL_GPIO_WritePin(DE_GPIO_Port, DE_Pin, GPIO_PIN_SET); // 切发送
    HAL_UART_Transmit(&huart2, pData, len, 1000);
    while(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC) == RESET); // 等发送完成
    HAL_GPIO_WritePin(DE_GPIO_Port, DE_Pin, GPIO_PIN_RESET); // 切接收
}

二、MODBUS协议层实现

STM32F103x系列RS485、MODBUS学习资料,RS485通信历程(标准库、HAL库);应用层协议MODBUS协议,包含自己编写的MODBUS协议,移植开源FreeModbus协议;移植带操作系统的资料(FreeRTOS、RT_Thread),源码都测试过可以使用,工程规范、逻辑清楚、可以快速移植的自己的板子,学习MODBUS通信协

自己撸协议适合理解底层逻辑。比如处理03功能码(读保持寄存器)的核心函数:

// 自定义MODBUS处理示例
void Modbus_Process(uint8_t *rxBuf, uint8_t *txBuf) {
    uint16_t crc;
    if(rxBuf[0] != DEVICE_ADDR) return; // 地址校验
    
    switch(rxBuf[1]) { // 功能码分支
        case 0x03: // 读寄存器
            uint16_t startAddr = (rxBuf[2] << 8) | rxBuf[3];
            uint16_t regCount = (rxBuf[4] << 8) | rxBuf[5];
            txBuf[0] = rxBuf[0];
            txBuf[1] = 0x03;
            txBuf[2] = regCount * 2;
            for(int i=0; i<regCount; i++) {
                txBuf[3+i*2] = regTable[startAddr+i] >> 8;
                txBuf[4+i*2] = regTable[startAddr+i] & 0xFF;
            }
            crc = CRC16(txBuf, 3 + regCount*2);
            txBuf[3 + regCount*2] = crc >> 8;
            txBuf[4 + regCount*2] = crc & 0xFF;
            break;
        // 其他功能码...
    }
}

但生产环境建议用开源方案。FreeModbus移植时重点关注portserial.cporttimer.c,替换里面的硬件操作函数。比如修改串口发送函数:

// FreeModbus适配层示例
BOOL xMBPortSerialPutByte(CHAR ucByte) {
    USART_SendData(USART2, ucByte);
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    return TRUE;
}

三、上操作系统怎么玩

FreeRTOS下开个任务轮询MODBUS事件:

// FreeRTOS任务函数
void ModbusTask(void *pvParameters) {
    eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE);
    eMBEnable();
    for(;;) {
        eMBPoll(); // 主循环处理报文
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

RT-Thread用户更省事,用ENV工具直接装FreeModbus软件包,menuconfig里勾选对应选项,修改Kconfig里的硬件参数就能跑起来。

四、快速移植技巧

不管用哪种方案,移植时重点关注这几个文件:

  1. mbconfig.h —— 协议功能裁剪
  2. portserial.c —— 串口收发实现
  3. porttimer.c —— 定时器配置(T35超时)

硬件差异主要改这几个宏:

#define MB_PORT_TXD_ENABLE()  HAL_GPIO_WritePin(DE_GPIO, DE_PIN, 1) // 发送使能
#define MB_PORT_RXD_ENABLE()  HAL_GPIO_WritePin(DE_GPIO, DE_PIN, 0) // 接收使能

实测可用的工程已整理好模块化结构:

  • /drivers 放RS485底层驱动
  • /modbus 协议栈隔离硬件
  • /tasks 操作系统任务调度

直接替换/drivers目录下的硬件操作,十分钟就能在新板子上跑通MODBUS主从机通信。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐