RS485绝对值编码器-STM32驱动代码
本文就RS485绝对值编码器与SMT32单片机通信,实操性拉满的驱动方案!从 CubeMX 硬件配置到 Modbus RTU 协议实现,完整代码 + 接线说明 + 200ms 非阻塞读取优化,还标注了所有需修改的参数位,机床、自动化设备开发直接套用,新手也能快速落地~
先看下接线

一、核心前提(硬件+ CubeMX 配置)
硬件连接、CubeMX 的UART(带中断)、GPIO(DE/RE)、定时器(3.5字符超时),200ms 读取,需再配置一个通用定时器(如TIM2),用于生成200ms 定时中断(优先级低于UART 和3.5字符超时定时器),配置参数:
- 内部时钟、预分频器 7199(72MHz 主频);
- 自动重装值 1999(200ms 溢出);
- 开启更新中断,NVIC 抢占优先级 4,子优先级 1。
编码器 Modbus RTU 参数仍需提前确认:从机地址、波特率、寄存器地址、8N1 串口格式,必须与代码宏定义一致。
注:深圳欧艾迪的 RS485 绝对值编码器默认参数为:从机地址 0x01、波特率 9600、寄存器地址 0x0000(位置),无需额外修改即可适配本文驱动代码。
二、完整代码实现
代码分为modbus_485.h/c(RS485+Modbus RTU 核心)和main.c(200ms 读取逻辑),核心通信代码不变,仅修改定时读取部分,标注所有需根据实际修改的位置。
1. 核心头文件 modbus_485.h
#ifndef __MODBUS_485_H
#define __MODBUS_485_H
#include “stm32f1xx_hal.h”
#include <stdint.h>
#include <string.h>
/************************* 硬件&编码器参数(根据实际修改!) *************************/
#define MB_UART_HANDLE &huart1 // RS485对应的UART句柄
#define MB_DE_RE_GPIO GPIOA // DE/RE引脚GPIO口
#define MB_DE_RE_PIN GPIO_PIN_8 // DE/RE引脚
#define MB_SLAVE_ADDR 0x01 // 编码器从机地址
#define MB_BAUDRATE 9600 // 编码器波特率(9600/19200)
#define TMR_35TICK &htim2 // 3.5字符超时定时器(如TIM2)
/************************* Modbus RTU 配置 *************************/
#define MB_FUNC_READ_HOLD_REG 0x03 // 读保持寄存器(编码器主流)
#define MB_BUF_SIZE 64 // 接收缓冲区大小(足够存储1帧RTU)
#define MB_35TICK_MS 4 // 3.5字符超时ms(9600=4,19200=2)
#define MB_READ_TIMEOUT 200 // 单次读取接收超时ms(100ms读一次,设200足够)
/************************* 编码器寄存器(根据手册修改!) *************************/
#define ENC_REG_POS 0x0000 // 绝对位置寄存器地址
#define ENC_REG_SPD 0x0002 // 转速寄存器地址(按需保留)
/************************* 函数声明 *************************/
void RS485_Send_Enable(void); // RS485发送使能
void RS485_Receive_Enable(void); // RS485接收使能
uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len); // CRC16校验
uint8_t Modbus_Read_Reg(uint16_t reg_addr, uint16_t reg_num, uint16_t *data_buf); // 读寄存器
void Modbus_UART_Rx_Callback(uint8_t rx_data); // 串口接收回调
void Modbus_Timeout_Callback(void); // 3.5字符超时回调
#endif
2. 核心源文件 modbus_485.c
保留RS485 收发、CRC16、中断接收、3.5字符超时等核心逻辑,优化单次读取的超时时间为 200ms:
#include “modbus_485.h”
/************************* 内部全局变量 *************************/
static uint8_t mb_rx_buf[MB_BUF_SIZE] = {0}; // 接收环形缓冲区
static uint16_t mb_rx_idx = 0; // 接收写入索引
static uint8_t mb_frame_complete = 0; // RTU帧接收完成标志
/************************* RS485收发切换(核心) *************************/
void RS485_Send_Enable(void)
{
HAL_GPIO_WritePin(MB_DE_RE_GPIO, MB_DE_RE_PIN, GPIO_PIN_SET);
}
void RS485_Receive_Enable(void)
{
HAL_GPIO_WritePin(MB_DE_RE_GPIO, MB_DE_RE_PIN, GPIO_PIN_RESET);
}
/************************* Modbus RTU 标准CRC16校验 *************************/
uint16_t Modbus_CRC16(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF;
uint16_t i, j;
for (i = 0; i < len; i++)
{
crc ^= buf[i];
for (j = 0; j < 8; j++)
{
if (crc & 0x0001)
{
crc >>= 1;
crc ^= 0xA001; // Modbus标准多项式
}
else crc >>= 1;
}
}
return crc;
}
/************************* 3.5字符超时定时器控制 *************************/
static void Modbus_Timer_Start(void)
{
__HAL_TIM_CLEAR_FLAG(TMR_35TICK, TIM_FLAG_UPDATE);
HAL_TIM_Base_Start_IT(TMR_35TICK);
}
static void Modbus_Timer_Stop(void)
{
HAL_TIM_Base_Stop_IT(TMR_35TICK);
__HAL_TIM_SET_COUNTER(TMR_35TICK, 0);
}
/************************* 串口接收中断回调(单字节) *************************/
void Modbus_UART_Rx_Callback(uint8_t rx_data)
{
if (mb_rx_idx < MB_BUF_SIZE)
{
mb_rx_buf[mb_rx_idx++] = rx_data;
Modbus_Timer_Start(); // 每收1字节,重启3.5字符超时定时器
}
else // 缓冲区溢出,复位
{
mb_rx_idx = 0;
memset(mb_rx_buf, 0, MB_BUF_SIZE);
}
}
/************************* 3.5字符超时回调(帧结束) *************************/
void Modbus_Timeout_Callback(void)
{
Modbus_Timer_Stop();
mb_frame_complete = 1; // 置位帧完成标志,通知解析
}
/************************* RTU帧解析(内部函数) *************************/
static uint8_t Modbus_Frame_Analysis(uint8_t *rx_buf, uint16_t rx_len, uint16_t *data_buf)
{
uint16_t crc_calc = Modbus_CRC16(rx_buf, rx_len – 2);
uint16_t crc_recv = (rx_buf[rx_len-1] << 8) | rx_buf[rx_len-2];
if (rx_len < 8) return 4; // 帧长度错误
if (rx_buf[0] != MB_SLAVE_ADDR) return 2; // 从机地址错误
if (crc_calc != crc_recv) return 1; // CRC校验错误
if (rx_buf[1] != MB_FUNC_READ_HOLD_REG) return 3; // 功能码错误
// 解析16位寄存器数据(大端存储,Modbus标准)
uint8_t data_len = rx_buf[2];
for (uint8_t i = 0; i < data_len / 2; i++)
{
data_buf[i] = (rx_buf[3 + 2*i] << 8) | rx_buf[4 + 2*i];
}
return 0; // 解析成功
}
/************************* Modbus RTU 读寄存器(核心对外函数) *************************/
// 返回值:0=成功,1=CRC错,2=地址错,3=功能码错,4=帧长错,5=发送失败,6=接收超时
uint8_t Modbus_Read_Reg(uint16_t reg_addr, uint16_t reg_num, uint16_t *data_buf)
{
uint8_t mb_tx_buf[8] = {0};
uint16_t crc = 0;
uint32_t timeout = 0;
uint8_t ret = 0;
// 1. 复位接收状态
mb_rx_idx = 0;
mb_frame_complete = 0;
memset(mb_rx_buf, 0, MB_BUF_SIZE);
memset(data_buf, 0, reg_num * 2);
// 2. 构造Modbus RTU读帧(8字节固定)
mb_tx_buf[0] = MB_SLAVE_ADDR;
mb_tx_buf[1] = MB_FUNC_READ_HOLD_REG;
mb_tx_buf[2] = (reg_addr >> 8) & 0xFF; // 寄存器地址高字节
mb_tx_buf[3] = reg_addr & 0xFF; // 寄存器地址低字节
mb_tx_buf[4] = (reg_num >> 8) & 0xFF; // 寄存器个数高字节
mb_tx_buf[5] = reg_num & 0xFF; // 寄存器个数低字节
crc = Modbus_CRC16(mb_tx_buf, 6);
mb_tx_buf[6] = crc & 0xFF; // CRC低字节(RTU标准)
mb_tx_buf[7] = (crc >> 8) & 0xFF; // CRC高字节
// 3. RS485半双工发送
RS485_Send_Enable();
if (HAL_UART_Transmit(MB_UART_HANDLE, mb_tx_buf, 8, 100) != HAL_OK)
{
RS485_Receive_Enable();
return 5; // 发送失败
}
HAL_Delay(1); // 保证最后1位数据发送完成,避免提前切接收丢失
RS485_Receive_Enable();
// 4. 等待帧完成(超时保护:MB_READ_TIMEOUT=200ms)
while (!mb_frame_complete)
{
HAL_Delay(1);
if (++timeout > MB_READ_TIMEOUT) return 6; // 接收超时
}
// 5. 解析帧数据
ret = Modbus_Frame_Analysis(mb_rx_buf, mb_rx_idx, data_buf);
return ret;
}
4. 主函数 main.c(200ms 读取核心)
提供 200ms 读取方案,用定时器中断实现精准200ms,非阻塞,不影响主循环其他逻辑。main.c
#include “main.h”
#include “usart.h”
#include “gpio.h”
#include “tim.h”
#include “modbus_485.h”
/************************* 全局变量 *************************/
uint8_t rx_data = 0;
uint16_t enc_pos = 0;
uint8_t mb_err = 0;
uint8_t modbus_read_flag = 0; // 100ms读取标志(中断置位)
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init(); // 3.5字符超时定时器
MX_TIM3_Init(); // 100ms读取定时器(必须开启)
// 初始化
RS485_Receive_Enable();
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
HAL_TIM_Base_Start_IT(&htim3); // 开启100ms定时中断
while (1)
{
// 检测到100ms标志,执行读取
if (modbus_read_flag)
{
modbus_read_flag = 0; // 清标志,避免重复执行
// 读取编码器位置
mb_err = Modbus_Read_Reg(ENC_REG_POS, 1, &enc_pos);
if (mb_err == 0)
{
printf(“编码器位置:%d | 精准100ms非阻塞\r\n”, enc_pos);
}
else
{
printf(“读取失败,错误码:%d\r\n”, mb_err);
}
}
// 主循环可添加其他逻辑,不影响100ms读取(非阻塞核心)
// 示例:LED闪烁、按键检测等
// HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
// HAL_Delay(50);
}
}
/************************* 中断回调重写(修改TIM3部分) *************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
Modbus_UART_Rx_Callback(rx_data);
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// 3.5字符超时(TIM2)
if (htim->Instance == TIM2)
{
Modbus_Timeout_Callback();
}
// 200ms读取定时(TIM3),核心修改
else if (htim->Instance == TIM3)
{
modbus_read_flag = 1; // 置位读取标志
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE); // 清中断标志
}
}
// 其余函数(串口重定向、系统时钟)与方案一一致,无需修改
三、关键细节(适配200ms 高频读取)
- 接收超时时间优化将单次读取的接收超时设为200ms,既保证通信容错,又不会因超时太久导致下一次读取被阻塞;
- 串口中断必须循环开启HAL_UART_Receive_IT是单次中断,必须在回调中重新调用,否则只能接收 1 个字节,这是通信的核心前提;
- RS485 切换延时发送后HAL_Delay(1)不可省略,STM32 的 UART 硬件在HAL_UART_Transmit返回后,可能还在发送最后 1 位数据,提前切接收会导致数据丢失;
- 非阻塞版的标志清 0200ms 中断置位modbus_read_flag后,主循环读取完成必须立即清 0,否则会重复执行读取逻辑,导致通信混乱;
- 缓冲区大小设为 64 字节足够,Modbus RTU 单帧数据最大不超过 256 字节,编码器读取一般仅需 8~10 字节,64 字节无溢出风险。
四、产品适配与应用场合
本文驱动方案已在深圳欧艾迪RS485绝对值编码器上实测验证,无需修改核心代码即可直接使用。
广泛应用于机床、3D 打印机、电控滑轨模组、自动化流水线、钢铁工业、运送设备、纺织机
械、港口机械、塑料机械、起重机械、压力机械、玻璃机械、印刷机械、木材机械、包装机械、物流
机械、轮胎机械、电梯自动化、水泥厂、工业机器人、喷码机、工程机械等自动化控制领域。








