|

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 高频读取)

  1. 接收超时时间优化将单次读取的接收超时设为200ms,既保证通信容错,又不会因超时太久导致下一次读取被阻塞;
  2. 串口中断必须循环开启HAL_UART_Receive_IT是单次中断,必须在回调中重新调用,否则只能接收 1 个字节,这是通信的核心前提;
  3. RS485 切换延时发送后HAL_Delay(1)不可省略,STM32 的 UART 硬件在HAL_UART_Transmit返回后,可能还在发送最后 1 位数据,提前切接收会导致数据丢失;
  4. 非阻塞版的标志清 0200ms 中断置位modbus_read_flag后,主循环读取完成必须立即清 0,否则会重复执行读取逻辑,导致通信混乱;
  5. 缓冲区大小设为 64 字节足够,Modbus RTU 单帧数据最大不超过 256 字节,编码器读取一般仅需 8~10 字节,64 字节无溢出风险。

四、产品适配与应用场合

本文驱动方案已在深圳欧艾迪RS485绝对值编码器上实测验证,无需修改核心代码即可直接使用。

广泛应用于机床、3D 打印机、电控滑轨模组、自动化流水线、钢铁工业、运送设备、纺织机

械、港口机械、塑料机械、起重机械、压力机械、玻璃机械、印刷机械、木材机械、包装机械、物流

机械、轮胎机械、电梯自动化、水泥厂、工业机器人、喷码机、工程机械等自动化控制领域。

RS485绝对值编码器-防爆

RS485绝对值编码器-防爆 绝对式单圈多圈编码器,掉电记忆,全量程范围内,位置数据唯一,具有RS485接口,…

RS485绝对值编码器-微型

RS485绝对值编码器-微型 OID-R2706D是一款超小型实心轴绝对旋转编码器,非常适合空间有限的应用安装…

RS485绝对值编码器-IP68防水

RS485绝对值编码器-IP68防水 绝对式单圈多圈编码器,掉电记忆,全量程范围内,位置数据唯一,具有RS48…

RS485拉绳位移传感器

RS485拉绳位移传感器 拉线位移传感器可以完成几乎任何线性行程测量任务,即使在空间狭小或者环境条件苛刻的情况…

RS485单圈绝对值编码器

RS485单圈绝对值编码器 单圈绝对值编码器,在360°范围内,位置数据唯一,具有RS485接口,标准Modb…

RS485多圈绝对值编码器

RS485多圈绝对值编码器 精密机械齿轮多圈绝对值编码器,无内部电池,绝对式多圈,掉电记忆,全量程范围内,位置…

类似文章