|

STM32 驱动 CAN 拉线传感器:从接线到代码全解析

前言:CAN 接口拉线位移传感器 STM32 驱动实操方案来啦!欧艾迪传感器实测,从 CAN 帧自定义结构、完整驱动代码到距离计算公式,封装发送 / 解析函数 + 中断自动处理,还避了总线匹配、数据解析的坑,液压、仓储、工控场景直接移植~

概述:本文将以欧艾迪拉线位移传感器,CAN接口,1024分辨率的,量程长度为1m为例。默认ID地址为1(站号),线性精度为0.1%,CAN 编码器出厂时的默认配置, 波特率默认为 500K,,采用数据帧,地址设置为1.(默认的),数据长度最大为8.数据传输根据编码器的协议, 填写数据域内容。 数据域的内容为多字节时, 低字节在前。例如: A、 主机向 1 号编码器发送指令: “读取编码器值”, 数据域长度 4;数据域: 0x04(数据长度) + 0x01(编码器地址) + 0x01(指令码) + 0x00(数据 1)

标识符IDData[0]Data[1]Data[2]Data[3]Data[4]Data[5]Data[6]Data[7]
0x010x040x010x010x00NULLNULLNULLNULL

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

标识符IDData[0]Data[1]Data[2]Data[3]Data[4]Data[5]Data[6]Data[7]
0x010x070x010x010x450x230x010x00NULL

接线定义如下:

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

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

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

1. 头文件(can_driver.h)

#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

  • 源文件(can_driver.c)

#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)

                {

                    // 执行电机启动逻辑

                }

            }

        }

    }

}

  • 主函数调用示例(main.c)

#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是读出的值。

工作原理

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

● 拉绳位移传感器安装在固定位置上, 拉绳缚在移动物体上。 拉绳直线运动和移动物体运动轴线对准。

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

三、关键代码解释

帧结构封装(发送侧)

    • CAN_Send_Custom_Frame函数封装了所有自定义帧的组装逻辑,你只需传入 “设备地址、指令、数据、数据长度”,无需手动拼接字节,降低出错概率;
    • 自动校验数据长度(最大 5 字节),避免超出 CAN 帧的有效数据段范围;
    • 未使用的字节自动填 0,保证帧结构的统一性。

帧结构解析(接收侧)

    • CAN_Parse_Custom_Frame函数将 8 字节原始数据解析为CAN_Receive_Data_t结构体,直接提取 “长度、地址、指令、数据”,无需手动索引字节;
    • 增加数据长度合法性校验,避免因非法帧导致数组越界(环境易出现干扰帧)。

中断接收处理

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

四、调试与适配注意事项

  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 位整数),解析时再重组:

// 示例:将2字节数据重组为16位整数(大端模式)

uint16_t distance = (can_parse_data->data[0] << 8) | can_parse_data->data[1];

总结

  1. 核心逻辑将自定义帧结构 “数据长度 + 地址 + 指令 + 数据” 封装为专用发送 / 解析函数,简化业务层调用,避免手动拼接字节;
  2. 可靠性增加参数校验和非法帧过滤,适配复杂的通信环境;
  3. 扩展性通过设备地址和指令码的组合,可轻松扩展机器人的多设备通信(声纳、电机、传感器等)。

这份代码可直接移植到你的机器人项目中,只需根据实际设备的地址和指令码修改业务逻辑即可。

五、应用领域

适⽤于: 液压油缸⾏程检测, 闸⻔开度检测及控制, 吊⻋提升机检测, ⾃动仓储检测, ⽊⼯机械检测, 试验机检测, ⼤包装机械, ⽊⼯机械, 压⼒机械, 仓储位置定位, 造纸机械, 纺织机械, ⾦属板材机械, 印刷机械, 建筑机械, ⽔平控制仪, ⾼度机等相关尺⼨测量和位置控制, ⼯业机械, ⾃动化控制等。

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


欧艾迪该系列拉线位移传感器全系列支持 CAN/RS485等主流通信接口,量程可定制(0-20m可选),线性精度均≤0.1%,适配机床、液压油缸、仓储定位、建筑机械等各类工业自动化场景的位移检测需求。


若你在使用过程中需要完整版测试代码、产品手册、选型指南,或有定制化量程/接口的需求,可通过以下方式对接欧艾迪技术工程师:
官方官网:www.oidencoder.com(可免费下载全套技术资料)
咨询热线:400-166-0195, 15814017675(同微)

类似文章