基于CUBEMX的HAL库can通信实操代码(非理论)

1、摘要

  1. 本文所用型号为RoboMaster开发板C型,型号STM32F407IGHx。
  2. 背景所述代码以控制四个M3508电机为例。
  3. 本篇文章没有很多理论的知识,大部分为代码实操,代码都有明确的注释和解释。

2、CUBEMX配置

时钟数配置

基于CUBEMX的HAL库can通信实操代码(非理论)_第1张图片

 这里只展示CAN的配置:

基于CUBEMX的HAL库can通信实操代码(非理论)_第2张图片

 在配置的过程中要注意,需要查找所用电机手册的can通讯波特率是多少

计算公式:

Baud Rate=\frac{APB1 peripheral }{Prescaler *(Bit Segment 1+Bit Segment 2+ReSynchronization Jump Width)}

can通讯波特率=APB1时钟频/分频系数Prescaler*(BS1+BS2+同步时间段)

基于CUBEMX的HAL库can通信实操代码(非理论)_第3张图片

在配置分频系数的时候可能会遇到这个问题,是因为在配置Prescaler、BS1、BS2参数错误,按照提示要求,需要更改参数:将Bit Segment (BS2)的设置,将其值增加到至少3;将Prescaler增加到至少3。

3、代码部分

can通信包含两部分,发送和接收,顾名思义

  1. 发送是电脑发送指令给电机,通常指发送电机电流多少;

  2. 接收是电脑通过通信协议读取到电机的数据,比如线速度和角速度等等相关信息 

3.1 can发送:

发送包含两部分:筛选器和发送信息 

3.1.1 筛选器

筛选器相当于邮局,用于中转信息,确保发送的ID是否正确。

配置CAN滤波器,启动CAN总线,并激活接收中断,以便在有CAN消息到达时能够及时处理。这通常是在使用CAN总线进行通信时的一般设置过程。

//筛选器
void can_filter_init(void)
{
    CAN_FilterTypeDef can_filter_st;             //CAN滤波器配置结构体,用于配置CAN过滤器的参数
    can_filter_st.FilterActivation = ENABLE;     //启用CAN过滤器功能
    can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK;//设置CAN滤波器的模式为标识符掩码模式
    can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT;//设置CAN滤波器的比例为32位,表示过滤器的标识符和掩码的总位数。
    can_filter_st.FilterIdHigh = 0x0000;//设置CAN滤波器的高16位标识符。
    can_filter_st.FilterIdLow = 0x0000;//设置CAN滤波器的低16位标识符。
    can_filter_st.FilterMaskIdHigh = 0x0000;//设置CAN滤波器的高16位掩码。
    can_filter_st.FilterMaskIdLow = 0x0000;//设置CAN滤波器的低16位掩码。
    can_filter_st.FilterBank = 0;//设置CAN滤波器的编号
    can_filter_st.FilterFIFOAssignment = CAN_RX_FIFO0;//设置CAN滤波器匹配的消息将被传送到的FIFO0缓冲区
    HAL_CAN_ConfigFilter(&hcan1, &can_filter_st);//配置CAN滤波器,应用到CAN1总线
    HAL_CAN_Start(&hcan1);//启动CAN1总线。
    HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);//激活CAN1总线的接收中断通知,当RX_FIFO0中有消息待处理时触发中断。

}

3.1.2 发送信息

将数值(电流值)发送给电机,类似于PWM占空比

static CAN_TxHeaderTypeDef  can_3508_tx_message;

void can_msg_send(int16_t motor1,int16_t motor2, int16_t motor3, int16_t motor4)
{
    uint32_t send_mail_box;
    can_3508_tx_message.StdId = CAN_3508_ALL_ID;//CAN消息的标准标识符,类似于电机ID
    can_3508_tx_message.IDE = CAN_ID_STD;//使用标准标识符
    can_3508_tx_message.RTR = CAN_RTR_DATA;//使用数据帧
    can_3508_tx_message.DLC = 0x08;//数据长度8字节
    can_3508_tx_send_data[0] = motor1 >> 8;//高八位
    can_3508_tx_send_data[1] = motor1;//低八位
    can_3508_tx_send_data[2] = motor2 >> 8;
    can_3508_tx_send_data[3] = motor2;
    can_3508_tx_send_data[4] = motor3 >> 8;
    can_3508_tx_send_data[5] = motor3;
    can_3508_tx_send_data[6] = motor4 >> 8;
    can_3508_tx_send_data[7] = motor4;

    HAL_CAN_AddTxMessage(&CAN_3508, &can_3508_tx_message, can_3508_tx_send_data, &send_mail_box);
}
//这里是将四个3508都写入进去,可以控制4个3508电机
3.1.2.1 解释一下关于CAN_RxHeaderTypeDef的参数:

  1. StdId(Standard Identifier):

    1. 意义:存储CAN消息的标准标识符(11位长),用于标识CAN网络中的消息源或目标节点。

  2. IDE(Identifier Extension):

    1. 意义:表示CAN消息的标识符类型。

    2. 可填值:

      • CAN_ID_STD:标准标识符,11位长。

      • CAN_ID_EXT:扩展标识符,29位长。

  3. RTR(Remote Transmission Request):

    1. 意义:表示CAN消息的远程传输请求。

    2. 可填值:

      • CAN_RTR_DATA:数据帧,用于传输实际数据。

      • CAN_RTR_REMOTE:远程帧,用于请求其他节点发送数据。

  4. DLC(Data Length Code):

    1. 意义:表示CAN消息携带的数据长度。

    2. 可填值:整数,通常范围为0到8,表示数据的字节数。

3.1.2.2 解释一下高八位低八位

发送给电机的数据类型是int16_t,发送CAN消息之前,这些16位整数被拆分成高8位和低8位,然后分别存储在 can_3508_tx_send_data 数组的相应位置。

在大疆3508电机使用手册中:

基于CUBEMX的HAL库can通信实操代码(非理论)_第4张图片

将手册中的数据一一对应到代码中去。

在CAN通信中,数据通常以字节为单位进行传输。高8位和低8位的分离使得可以更容易地在接收端重新组合数据。

3.2 can接收如何写:

包含两部分,hal库的can回调函数HAL_CAN_RxFifo0MsgPendingCallback(),和缓冲区的信息(收到的数据处理)。

这里给出两种方式去写接收:

3.2.1 第一种方式:

先获取接收数据长度,在做数据处理的时候根据标准标识符存放数据

3.2.1.1 回调函数

用于做电机信息比对,确保收到的数据正确

//hal库CAN回调函数,接收电机数据
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)//回调函数
{
    CAN_RxHeaderTypeDef rx_header1;//定义结构体变量:用于存储接收到的CAN消息的头部信息,包括标识符、标识符类型、远程传输请求和数据长度等。
    uint8_t rx_data1[8];//定义了一个长度为8的数组,用于存储接收到的CAN消息的数据部分。
    
    if(hcan == &hcan1)
    {
     HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header1, rx_data1);//从FIFO0中获取CAN消息的头部信息和数据。将接收到的CAN消息的相关信息填充到 rx_header1 和 rx_data1 中。
     static uint8_t i = 0;//用于循环遍历接收到的CAN消息的数据部分
     for( i = 0; i < rx_header1.DLC; ++i)//rx_header1.DLC 表示接收到的数据的长度,循环遍历的目的是将接收到的数据存储到 Can1_ReceiveBuffer 数组中。
        {
            Can1_ReceiveBuffer[i] = rx_data1[i];//将接收到的CAN消息的数据存储到 Can1_ReceiveBuffer 数组中。
        }
        //从缓冲区提取信息
      Get3508_Info(rx_header1.StdId, Can1_ReceiveBuffer);//将接收到的CAN消息的标准标识符和数据作为参数传递做数据处理。
    }
}
3.2.1.2 数据处理函数:

由于收到的数据都是原始数据,需要将收到的数据转化为自己需要使用的

typedef struct
{
    uint16_t ecd;
    int16_t  speed_rpm;
    int16_t  given_current;
    uint8_t  temperate;
} motor_measure_t;//这里建议把结构体放进.h文件中去

motor_measure_t chassis_motor_info[4];

void Get3508_Info(uint32_t motor_id, uint8_t *canbuf_receive)
{
    static uint32_t cnt = 0;
    if( (motor_id - 0x201 >= 0) && (motor_id - 0x201 < 4))
    {
    //读取当前信息
    chassis_motor_info[motor_id - 0x201].ecd           = canbuf_receive[0] << 8 | canbuf_receive[1];
    chassis_motor_info[motor_id - 0x201].speed_rpm     = (signed short)(canbuf_receive[2] << 8 | canbuf_receive[3]);
    chassis_motor_info[motor_id - 0x201].given_current = (signed short)(canbuf_receive[4] << 8 | canbuf_receive[5]);
    chassis_motor_info[motor_id - 0x201].temperate     = canbuf_receive[6];
    }   
}

3.2.2 第二种方式:

先获取标准标识符,在做数据处理的时候填放数据

3.2.2.1 回调函数:
typedef struct
{
    int16_t ecd;
    int16_t speed;
    int16_t given_current;
    uint8_t temperate;
} motor_measure_t;

motor_measure_t motor_chassis[4];

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
        CAN_RxHeaderTypeDef rx_header;
        uint8_t rx_data[8];
        HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data);
        if(hcan==&hcan1)
        {
        switch (rx_header.StdId)//接收到的CAN消息的标准标识符 StdId 进行分支选择。
        {
            case M3508_1_ID:
            case M3508_2_ID:
            case M3508_3_ID:
            case M3508_4_ID:
            {
                static uint8_t i = 0;
                // get motor id
                i = rx_header.StdId - M3508_1_ID;//算电机id数,便于存放数据
                Get3508_Info(&motor_chassis[i], rx_data);//将对应电机的信息和接收到的CAN消息数据传递给该函数进行处理。
                break;
            }
            default:
            {
                break;
            }
        }
}
3.2.2.2 数据处理函数:

void Get3508_Info(motor_measure_t *motor, uint8_t *canbuf_receive)
{
    motor.ecd           = canbuf_receive[0] << 8 | canbuf_receive[1];
    motor.speed_rpm     = (signed short)(canbuf_receive[2] << 8 | canbuf_receive[3]);
    motor.given_current = (signed short)(canbuf_receive[4] << 8 | canbuf_receive[5]);
    motor.temperate     = canbuf_receive[6];
}

3.2.3 总结一下:

接收这里有两种不同方式的编写方式,

第一种是先存放数据信息,再区分ID存放到相应结构体中去;

第二种是先根据ID区分,再存放数据到结构体中。

两种方式都可以,我个人更倾向于使用第二种方式获取数据,更符合逻辑些。

3.3 CAN_RxHeaderTypeDef

CAN通信的重点在于信息的截取与存放,ID是否与电机配对。这里详细介绍一下HAL库中CAN消息接收头部的结构体定义:

typedef struct
{
  uint32_t StdId;    /*!< Specifies the standard identifier.
                          This parameter must be a number between Min_Data = 0 and Max_Data = 0x7FF. */

  uint32_t ExtId;    /*!< Specifies the extended identifier.
                          This parameter must be a number between Min_Data = 0 and Max_Data = 0x1FFFFFFF. */

  uint32_t IDE;      /*!< Specifies the type of identifier for the message that will be transmitted.
                          This parameter can be a value of @ref CAN_identifier_type */

  uint32_t RTR;      /*!< Specifies the type of frame for the message that will be transmitted.
                          This parameter can be a value of @ref CAN_remote_transmission_request */

  uint32_t DLC;      /*!< Specifies the length of the frame that will be transmitted.
                          This parameter must be a number between Min_Data = 0 and Max_Data = 8. */

  uint32_t Timestamp; /*!< Specifies the timestamp counter value captured on start of frame reception.
                          @note: Time Triggered Communication Mode must be enabled.
                          This parameter must be a number between Min_Data = 0 and Max_Data = 0xFFFF. */

  uint32_t FilterMatchIndex; /*!< Specifies the index of matching acceptance filter element.
                          This parameter must be a number between Min_Data = 0 and Max_Data = 0xFF. */

} CAN_RxHeaderTypeDef;

uint32_t StdId;

  • 含义:标准标识符,指定CAN消息的标准标识符。
  • 可填值:范围为0到0x7FF(11位)。

uint32_t ExtId;

  • 含义:扩展标识符,指定CAN消息的扩展标识符。
  • 可填值:范围为0到0x1FFFFFFF(29位)。

uint32_t IDE;

  • 含义:标识符类型,指定CAN消息的标识符类型。
  • 可填值:可以是以下值之一:
    • CAN_ID_STD:标准标识符。
    • CAN_ID_EXT:扩展标识符。

uint32_t RTR;

  • 含义:远程传输请求,指定CAN消息的远程传输请求类型。
  • 可填值:可以是以下值之一:
    • CAN_RTR_DATA:数据帧。
    • CAN_RTR_REMOTE:远程帧。

uint32_t DLC;

  • 含义:数据长度,指定CAN消息携带的数据长度。
  • 可填值:范围为0到8,表示数据的字节数。

uint32_t Timestamp;

  • 含义:时间戳,指定CAN消息接收的时间戳。
  • 可填值:范围为0到0xFFFF。

uint32_t FilterMatchIndex;

  • 含义:过滤器匹配索引,指定匹配的过滤器元素的索引。
  • 可填值:范围为0到0xFF。

最常用的为前五个,StdId标准标识符/扩展标识符,IDE标识符类型,RTR传输请求类型与DLC数据长度。用于配置接收到的CAN消息的头部信息,以便可以根据这些信息正确地处理接收到的消息。

4、总结

以上就是can的全部代码,有问题可以评论或者私聊我,找到错误也可以说出来我改正。谢谢大家~希望能对大家有所帮助

你可能感兴趣的:(STM32,单片机,嵌入式硬件)