STM32 驱动CAN接口的拉线位移传感器

概述

公司项目需要一个拉线位移传感器,就在网上买了深圳欧艾迪CAN接口的。外观很精致,然后用开发板测试了一下,实测精度和稳定性都很不错。我买的是1024分辨率、1m量程的,线性精度达0.1%,官方标注运行寿命高达 500万次。这款CAN接口拉线传感器出厂默认配置波特率 500K、站号ID地址1,采用8字节数据帧传输,低字节在前的协议规则,适配主流工控开发板。

数据传输

根据编码器的协议, 填写数据域内容。 数据域的内容为多字节时, 低字节在前。

例如: A、 主机向 1 号编码器发送指令: "读取编码器值", 数据域长度 4;

数据域: 0x04(数据长度) + 0x01(编码器地址) + 0x01(指令码) + 0x00(数据 1)

|-------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
| 标识符ID | Data[0] | Data[1] | Data[2] | Data[3] | Data[4] | Data[5] | Data[6] | Data[7] |
| 0x01 | 0x04 | 0x01 | 0x01 | 0x00 | NULL | NULL | NULL | NULL |

返回的数据: 数据域长度 7;

数据域: 0X07(数据长度) + 0X01(编码器地址) + 0X01(指令码) + 0x00012345(数据)

|-------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
| 标识符ID | Data[0] | Data[1] | Data[2] | Data[3] | Data[4] | Data[5] | Data[6] | Data[7] |
| 0x01 | 0x07 | 0x01 | 0x01 | 0x45 | 0x23 | 0x01 | 0x00 | NULL |

下面把测试步骤和程序分享出来。

先看看接线图

一、先看看CAN 数据帧结构

|---------|-------------|-----------------------------------------------------|------------|
| 字节位置 | 字段名称 | 功能说明 | 取值范围 |
| 字节 0 | Data_Len | 实际有效数据的长度(非 CAN 帧 DLC,仅标识 "数据段" 的字节数) | 0~5 |
| 字节 1 | Device_Addr | 设备地址(区分不同设备:如声纳 0x01、电机 0x02、控制器 0x03) | 0x00~0xFF |
| 字节 2 | Cmd | 指令码(如读取数据 0x01、设置参数 0x02、启动 0x03、停止 0x04、反馈状态 0x05) | 0x00~0xFF |
| 字节 3~7 | Data | 有效数据段(最多 5 字节,根据 Data_Len 填充,未使用字节填 0) | 0x00~0xFF |

说明:CAN 帧 DLC 固定为 8(方便统一处理),实际有效数据由 Data_Len 标识,未使用的字节填 0,避免解析混乱。

二、完整 CAN 驱动代码(适配自定义帧结构)

1. 头文件(can_driver.h)
cpp 复制代码
#ifndef __CAN_DRIVER_H
#define __CAN_DRIVER_H
#include "stm32f4xx_hal.h"  // 根据你的STM32型号替换(f1/f7/h7等)
/* CAN句柄(与CubeMX配置的句柄名一致) */
extern CAN_HandleTypeDef hcan1;
/* CAN基础配置 */
#define CAN_BAUDRATE        500000    // 水下机器人常用500Kbps
#define CAN_DEFAULT_ID      0x123     // 通用通信ID(可按设备分组修改)
/* 自定义帧结构相关宏 */
#define CAN_FRAME_TOTAL_LEN 8         // CAN帧总长度(固定8字节)
#define MAX_DATA_LEN        5         // 数据段最大字节数(字节3~7共5字节)
/* 接收数据结构体:解析后的数据存储 */
typedef struct {
    uint8_t data_len;      // 解析出的有效数据长度
    uint8_t dev_addr;      // 解析出的设备地址
    uint8_t cmd;           // 解析出的指令码
    uint8_t data[MAX_DATA_LEN];  // 解析出的有效数据
} CAN_Receive_Data_t;
/* 函数声明 */
// CAN初始化(含过滤器、中断配置)
HAL_StatusTypeDef CAN_Init_Config(void);
// 发送自定义结构的CAN数据(封装:地址+指令+数据)
HAL_StatusTypeDef CAN_Send_Custom_Frame(uint8_t dev_addr, uint8_t cmd, uint8_t *data, uint8_t data_len);
// 解析接收的CAN帧(解析为自定义结构)
uint8_t CAN_Parse_Custom_Frame(uint8_t *rx_buf, CAN_Receive_Data_t *parse_data);
// CAN接收中断回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);
#endif
  1. 源文件(can_driver.c)
cpp 复制代码
#include "can_driver.h"
#include <string.h>
/* CAN句柄(需与CubeMX生成的代码一致) */
CAN_HandleTypeDef hcan1;
/* 接收缓冲区与解析后的数据存储 */
uint8_t can_rx_raw_buf[CAN_FRAME_TOTAL_LEN] = {0};  // 原始接收缓冲区
CAN_Receive_Data_t can_parse_data;                   // 解析后的结构化数据
/**
 * @brief  CAN初始化配置(过滤器+中断)
 * @retval HAL状态:HAL_OK/HAL_ERROR
 */
HAL_StatusTypeDef CAN_Init_Config(void)
{
    CAN_FilterTypeDef can_filter_config;
    /* 1. 配置CAN过滤器(接收指定ID的报文) */
    can_filter_config.FilterBank = 0;
    can_filter_config.FilterMode = CAN_FILTERMODE_IDMASK;
    can_filter_config.FilterScale = CAN_FILTERSCALE_32BIT;
    can_filter_config.FilterIdHigh = (CAN_DEFAULT_ID << 5) & 0xFFFF;  // 标准ID高位
    can_filter_config.FilterIdLow = 0x0000;
    can_filter_config.FilterMaskIdHigh = (0x7FF << 5) & 0xFFFF;      // 匹配所有11位标准ID
    can_filter_config.FilterMaskIdLow = 0x0000;
    can_filter_config.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    can_filter_config.FilterActivation = ENABLE;
    can_filter_config.SlaveStartFilterBank = 14;  // F4系列固定14,其他型号可查手册
    if (HAL_CAN_ConfigFilter(&hcan1, &can_filter_config) != HAL_OK)
    {
        return HAL_ERROR;
    }
    /* 2. 启动CAN外设 + 启用接收中断 */
    if (HAL_CAN_Start(&hcan1) != HAL_OK)
    {
        return HAL_ERROR;
    }
    if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
    {
        return HAL_ERROR;
    }
    return HAL_OK;
}
/**
 * @brief  发送自定义结构的CAN数据帧
 * @param  dev_addr:设备地址(如声纳0x01、电机0x02)
 * @param  cmd:指令码(如读取数据0x01、设置参数0x02)
 * @param  data:有效数据缓冲区(最多5字节)
 * @param  data_len:有效数据长度(0~5)
 * @retval HAL状态:HAL_OK/HAL_ERROR
 */
HAL_StatusTypeDef CAN_Send_Custom_Frame(uint8_t dev_addr, uint8_t cmd, uint8_t *data, uint8_t data_len)
{
    CAN_TxHeaderTypeDef tx_header;
    uint32_t tx_mailbox;
    uint8_t tx_buf[CAN_FRAME_TOTAL_LEN] = {0};  // 发送缓冲区(初始化全0)
    /* 1. 参数校验:避免越界 */
    if (data_len > MAX_DATA_LEN || (data_len > 0 && data == NULL))
    {
        return HAL_ERROR;
    }
    /* 2. 封装自定义帧结构 */
    tx_buf[0] = data_len;          // 字节0:有效数据长度
    tx_buf[1] = dev_addr;          // 字节1:设备地址
    tx_buf[2] = cmd;               // 字节2:指令码
    if (data_len > 0)
    {
        memcpy(&tx_buf[3], data, data_len);  // 字节3~7:填充有效数据
    }
    /* 3. 配置CAN发送头(标准帧、8字节数据) */
    tx_header.StdId = CAN_DEFAULT_ID;    // 11位标准ID
    tx_header.ExtId = 0x00;              // 无扩展ID
    tx_header.RTR = CAN_RTR_DATA;        // 数据帧
    tx_header.IDE = CAN_ID_STD;          // 标准帧
    tx_header.DLC = CAN_FRAME_TOTAL_LEN; // DLC固定为8
    tx_header.TransmitGlobalTime = DISABLE;
    /* 4. 发送数据 */
    return HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_buf, &tx_mailbox);
}
/**
 * @brief  解析接收的CAN原始数据帧为自定义结构
 * @param  rx_buf:CAN原始接收缓冲区(8字节)
 * @param  parse_data:解析后的结构化数据(输出参数)
 * @retval 0:解析失败,1:解析成功
 */
uint8_t CAN_Parse_Custom_Frame(uint8_t *rx_buf, CAN_Receive_Data_t *parse_data)
{
    /* 参数校验 */
    if (rx_buf == NULL || parse_data == NULL)
    {
        return 0;
    }
    /* 1. 提取基础字段 */
    parse_data->data_len = rx_buf[0];
    parse_data->dev_addr = rx_buf[1];
    parse_data->cmd = rx_buf[2];
    /* 2. 校验数据长度合法性(避免越界) */
    if (parse_data->data_len > MAX_DATA_LEN)
    {
        memset(parse_data->data, 0, MAX_DATA_LEN);  // 非法长度清空数据
        return 0;
    }
    /* 3. 提取有效数据段 */
    memset(parse_data->data, 0, MAX_DATA_LEN);  // 先清空
    if (parse_data->data_len > 0)
    {
        memcpy(parse_data->data, &rx_buf[3], parse_data->data_len);
    }
    return 1;
}
/**
 * @brief  CAN接收中断回调函数(自动触发,解析数据)
 * @note   接收到报文后,自动解析为自定义结构并处理
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rx_header;
    if (hcan->Instance == CAN1)
    {
        /* 1. 读取原始接收数据 */
        if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, can_rx_raw_buf) == HAL_OK)
        {
            /* 2. 解析为自定义结构 */
            if (CAN_Parse_Custom_Frame(can_rx_raw_buf, &can_parse_data) == 1)
            {
                /* 3. 此处添加业务逻辑(示例:根据设备地址和指令处理) */
                // 比如:声纳设备(0x01)的读取数据指令(0x01)
                if (can_parse_data->dev_addr == 0x01 && can_parse_data->cmd == 0x01)
                {
                    // 处理声纳返回的数据:can_parse_data->data
                    // 示例:打印解析结果(需串口支持)
                    // printf("声纳数据:长度=%d, 数据=0x%02X\r\n", can_parse_data->data_len, can_parse_data->data[0]);
                }
                // 比如:电机设备(0x02)的启动指令(0x03)
                else if (can_parse_data->dev_addr == 0x02 && can_parse_data->cmd == 0x03)
                {
                    // 执行电机启动逻辑
                }
            }
        }
    }
}
  1. 主函数调用示例(main.c)
cpp 复制代码
#include "can_driver.h"
#include "stdio.h"
int main(void)
{
    /* 初始化HAL库、系统时钟、外设 */
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_CAN1_Init();  // CubeMX生成的CAN底层初始化
    /* 初始化CAN驱动 */
    if (CAN_Init_Config() != HAL_OK)
    {
        Error_Handler();
    }
    /* 示例1:发送指令给声纳(设备地址0x01,读取数据指令0x01,无附加数据) */
    uint8_t sonar_data[] = {0};
    CAN_Send_Custom_Frame(0x01, 0x01, sonar_data, 0);
    /* 示例2:发送指令给电机(设备地址0x02,设置转速指令0x02,数据=100(0x64)) */
    uint8_t motor_data[] = {0x64};  // 转速100
    CAN_Send_Custom_Frame(0x02, 0x02, motor_data, 1);
    while (1)
    {
        /* 接收数据在中断回调中自动解析处理 */
        HAL_Delay(10);
    }
}

距离计算公式如下:

距离计算公式如下:

L=(X-1000)* 轮周长/分辨率(单位 mm),分辨率是1024.

X是读出的值。1000是我们设置的初始值。

工作原理

● 拉绳位移传感器的功能是把机械运动转换成可以计量、 记录或传送的电信号(通信信号) 。

● 拉绳位移传感器安装在固定位置上, 拉绳缚在移动物体上。 拉绳直线运动和移动物体运动轴线对
准。
● 拉绳位移传感器由可拉伸的不锈钢绳绕在拉线盒主体内的轮毂上, 此轮毂与旋转编码器连接在一
起, 拉动拉绳头即可带动编码器旋转, 输出一个与拉绳移动距离成比例的电信号, 即测量输出信号,
从而可以得出运动物体的位移、 方向或速率

三、关键代码解释

  1. 帧结构封装(发送侧)

    • CAN_Send_Custom_Frame

      函数封装了所有自定义帧的组装逻辑,你只需传入 "设备地址、指令、数据、数据长度",无需手动拼接字节,降低出错概率;

    • 自动校验数据长度(最大 5 字节),避免超出 CAN 帧的有效数据段范围;

    • 未使用的字节自动填 0,保证帧结构的统一性。

  2. 帧结构解析(接收侧)

    • CAN_Parse_Custom_Frame

      函数将 8 字节原始数据解析为CAN_Receive_Data_t结构体,直接提取 "长度、地址、指令、数据",无需手动索引字节;

    • 增加数据长度合法性校验,避免因非法帧导致数组越界(复杂环境易出现干扰帧)。

  3. 中断接收处理

    • 回调函数中先读取原始数据,再解析为结构化数据,最后根据 "设备地址 + 指令" 分流处理业务逻辑(如声纳数据、电机控制),符合机器人多设备通信的实际需求。

四、调试与适配注意事项

五。应用领域

适⽤于: 液压油缸⾏程检测, 闸⻔开度检测及控制, 吊⻋提升机检测, ⾃动仓储检测, ⽊⼯机械

检测, 试验机检测, ⼤包装机械, ⽊⼯机械, 压⼒机械, 仓储位置定位, 造纸机械, 纺织机械, ⾦属

板材机械, 印刷机械, 建筑机械, ⽔平控制仪, ⾼度机等相关尺⼨测量和位置控制, ⼯业机械, ⾃动

化控制等。

本文所有STM32测试代码、CAN帧解析方案,均在深圳欧艾迪CAN接口拉线位移传感器(1024 分辨率/1m量程)上实测验证,核心代码无需修改,仅需根据实际设备的地址和指令码,以及拉线传感器的量程/分辨率微调公式参数,即可直接移植使用。

  1. 设备地址与指令码规划

    提前定义所有设备的地址(如声纳 0x01、深度传感器 0x03、主控 0x00)和指令码(如 0x01 读取、0x02 设置、0x03 反馈),避免冲突;

  2. 波特率与硬件

    保持总线两端波特率一致(500Kbps),必须接 120Ω 终端电阻,CAN_TX/RX 引脚配置为复用功能;

  3. 多 ID 扩展

    若需按设备分组设置不同 CAN ID,可修改CAN_DEFAULT_ID为变量,在发送函数中传入不同 ID;

  4. 数据类型扩展

    若需传输 16 位 / 32 位数据(如声纳距离、电机转速),可在 "数据段" 中拼接字节(如 2 字节表示 16 位整数),解析时再重组:

    cpp 复制代码
    // 示例:将2字节数据重组为16位整数(大端模式)
    uint16_t distance = (can_parse_data->data[0] << 8) | can_parse_data->data[1];

    总结

  5. 核心逻辑

    将自定义帧结构 "数据长度 + 地址 + 指令 + 数据" 封装为专用发送 / 解析函数,简化业务层调用,避免手动拼接字节;

  6. 可靠性

    增加参数校验和非法帧过滤,适配复杂的通信环境;

  7. 扩展性

    通过设备地址和指令码的组合,可轻松扩展机器人的多设备通信(声纳、电机、传感器等)。

相关推荐
Struggle to dream2 小时前
STM32---关于DMA的入门详解
stm32·单片机·嵌入式硬件
BackCatK Chen2 小时前
第16篇:TMC2240多轴联动软件设计|2轴_3轴同步控制框架(保姆级)
嵌入式硬件·自动化·tmc2240·多轴联动·同步控制·2轴联动·3轴联动
Hello_Embed3 小时前
STM32F030CCT6 开发环境搭建
笔记·stm32·单片机·嵌入式·freertos
国科安芯3 小时前
航空级电机控制系统的抗辐照MCU功能安全设计与电磁兼容验证方法
单片机·嵌入式硬件·安全·性能优化·架构·安全性测试
myron66883 小时前
基于STM32LXXX的模数转换芯片ADC(ADS1220IPWR)驱动C程序设计
c语言·stm32·嵌入式硬件
隔壁大炮3 小时前
I2C基本电路结构
单片机·嵌入式硬件·铁头山羊
爱吃番茄鼠骗5 小时前
STM32C8T6---解析bin文件
stm32·单片机·嵌入式硬件
myron66886 小时前
基于STM32LXXX的模数转换芯片ADC(CS1237-SOP8)驱动C程序设计
c语言·stm32·嵌入式硬件
-Springer-6 小时前
STM32 学习 —— 个人学习笔记7(ADC 模数转换器 & 单通道及多通道)
笔记·stm32·学习