发新帖我要提问
12
返回列表
打印
[STM32F4]

实战STM32F4 CAN!

[复制链接]
楼主: labasi
手机看帖
扫描二维码
随时随地手机跟帖
21
labasi|  楼主 | 2021-8-5 11:35 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
2.6.2 CAN 发送及接收结构体
在发送或接收报文时,需要往发送邮箱中写入报文信息或从接收 FIFO 中读取报文信息,利用STM32HAL 库的发送及接收结构体可以方便地完成这样的工作,它们的定义见代码清单 。代码清单 39‑2 CAN 发送及接收结构体

typedef struct
{
  uint32_t StdId;    /* 存储报文的标准标识符 11 位,0-0x7FF. */
  uint32_t ExtId;    /* 存储报文的扩展标识符 29 位,0-0x1FFFFFFF. */
  uint32_t IDE;     /* 存储 IDE 扩展标志 */
  uint32_t RTR;    /* 存储 RTR 远程帧标志 */
  uint32_t DLC;     /* 存储报文数据段的长度,0-8 */
  FunctionalState TransmitGlobalTime;
} CAN_TxHeaderTypeDef;
typedef struct
{
  uint32_t StdId;    /* 存储报文的标准标识符 11 位,0-0x7FF. */
  uint32_t ExtId;    /* 存储报文的扩展标识符 29 位,0-0x1FFFFFFF. */
  uint32_t IDE;     /* 存储 IDE 扩展标志 */
  uint32_t RTR;      /* 存储 RTR 远程帧标志 */
  uint32_t DLC;     /* 存储报文数据段的长度,0-8 */
  uint32_t Timestamp;
  uint32_t FilterMatchIndex;
} CAN_RxHeaderTypeDef;
这些结构体成员, 说明如下:

(1) StdId

本成员存储的是报文的 11 位标准标识符,范围是 0-0x7FF。

(2) ExtId

本成员存储的是报文的 29 位扩展标识符,范围是 0-0x1FFFFFFF。ExtId 与 StdId 这两个成员根据下面的 IDE 位配置,只有一个是有效的。

(3) IDE

本成员存储的是扩展标志 IDE 位,当它的值为宏 CAN_ID_STD 时表示本报文是标准帧,使用 StdId 成员存储报文 ID;当它的值为宏 CAN_ID_EXT 时表示本报文是扩展帧,使用 ExtId 成员存储报文 ID。

(4) RTR

本成员存储的是报文类型标志 RTR 位,当它的值为宏 CAN_RTR_Data 时表示本报文是数据帧;当它的值为宏 CAN_RTR_Remote 时表示本报文是遥控帧,由于遥控帧没有数据段,所以当报文是遥控帧时,数据是无效的

(5) DLC

本成员存储的是数据帧数据段的长度,它的值的范围是 0-8,当报文是遥控帧时 DLC值为 0。


使用特权

评论回复
22
labasi|  楼主 | 2021-8-5 11:35 | 只看该作者
2.6.3 CAN 筛选器结构体
CAN 的筛选器有多种工作模式,利用筛选器结构体可方便配置,它的定义见代码清单 。代码清单CAN 筛选器结构体

typedef struct
{
  uint32_t FilterIdHigh;         /*CAN_FxR1 寄存器的高 16 位 */
  uint32_t FilterIdLow;         /*CAN_FxR1 寄存器的低 16 位 */
  uint32_t FilterMaskIdHigh;   /*CAN_FxR2 寄存器的高 16 位 */
  uint32_t FilterMaskIdLow;    /*CAN_FxR2 寄存器的低 16 位 */
  uint32_t FilterFIFOAssignment;  /* 设置经过筛选后数据存储到哪个接收 FIFO */
  uint32_t FilterBank;            /* 筛选器编号,范围 0-27,数据手册上说0-27是CAN1/CAN2共享,但是实测发现并不是这样,CAN1是0-13,CAN2是14-27 */
  uint32_t FilterMode;            /* 筛选器模式 */
  uint32_t FilterScale;           /* 设置筛选器的尺度 */
  uint32_t FilterActivation;      /* 是否使能本筛选器 */
  uint32_t SlaveStartFilterBank;  
} CAN_FilterTypeDef;
这些结构体成员都是“41.2.14 验收筛选器”小节介绍的内容,可对比阅读,各个结构体成员的介绍如下:

(1) FilterIdHigh

FilterIdHigh 成员用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的高 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。

(2) FilterIdLow

类似地,FilterIdLow 成员也是用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的低 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。

(3) FilterMaskIdHigh

FilterMaskIdHigh 存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与 FilterIdHigh 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 FilterIdHigh 成员对应的掩码,与 FilterIdLow 组成一组筛选器。

(4) FilterMaskIdLow

类似地, FilterMaskIdLow 存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与 FilterIdLow 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 FilterIdLow 成员对应的掩码,与 FilterIdLow 组成一组筛选器。上面四个结构体的存储的内容很容易让人糊涂,请结合前面的图 39_0_15 和下面的表 39‑7 理解,如果还搞不清楚,再结合库函数 FilterInit 的源码来分析。

表不同模式下各结构体成员的内容


对这些结构体成员赋值的时候,还要注意寄存器位的映射,即注意哪部分代表 STID,哪部分代表 EXID 以及 IDE、RTR 位。


(5) FilterFIFOAssignment


本成员用于设置当报文通过筛选器的匹配后,该报文会被存储到哪一个接收 FIFO,它的可选值为 FIFO0 或 FIFO1(宏 CAN_FILTER_FIFO0/1)。


(6) FilterBank


本成员用于设置筛选器的编号,即本过滤器结构体配置的是哪一组筛选器,CAN 一共有 28 个筛选器,所以它的可输入参数范围为 0-27。


(7) FilterMode


本 成 员 用 于 设 置 筛 选 器 的 工 作 模 式, 可 以 设 置 为 列 表 模 式 (宏CAN_FILTERMODE_IDLIST) 及掩码模式 (宏 CAN_FILTERMODE_IDMASK)。


(8) FilterScale


本成员用于设置筛选器的尺度,可以设置为 32 位长 (宏 CAN_FILTERSCALE_32BIT)及 16 位长 (宏 CAN_FILTERSCALE_16BIT)。


(9) FilterActivation


本成员用于设置是否激活这个筛选器 (宏 ENABLE/DISABLE)。



使用特权

评论回复
23
labasi|  楼主 | 2021-8-5 11:36 | 只看该作者
三. CAN Cubemx配置

我们通过问题来熟悉下cubemx配置,你熟悉了这些问题基本就知道怎么配置了!

问题:Parameter Settings分别都是设置什么的?

答案:如图


使用特权

评论回复
24
labasi|  楼主 | 2021-8-5 11:44 | 只看该作者
问题:怎么配置波特率呢?

答案:用我上面贴的工具(CAN波特率计算 f103AHP1_36M  f407AHP1_42M  采样点软件有说明.rar)直接配置,举两个个例子

例子1:我们要配置成500KHz,那么我们这样配置


我们用采集点为80%,所以BS1为4tq,BS2为2tq,分频系数为12,代进公式Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)=42M/(4+2+1)/12=500kHz


例子2:我们要配置成1M Hz,那么我们这样配置



我们用采集点为75%,所以BS1为3tq,BS2为2tq,分频系数为7,代进公式Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)=42M/(3+2+1)/7=1MHz



使用特权

评论回复
25
labasi|  楼主 | 2021-8-5 11:45 | 只看该作者
问题:Basic Parameter分别是啥意思呢?


Timer Triggered Communication Mode:否使用时间触发功能 (ENABLE/DISABLE),时间触发功能在某些CAN 标准中会使用到。

Automatic Bus-Off Management:用于设置是否使用自动离线管理功能 (ENABLE/DISABLE),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预。

Automatic Wake-Up Mode:用于设置是否使用自动唤醒功能 (ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。

Automatic Retransmission:用于设置是否使用自动重传功能 (ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止。

Receive Fifo Locked Mode:用于设置是否使用锁定接收 FIFO(ENABLE/DISABLE),锁定接收 FIFO 后,若FIFO 溢出时会丢弃新数据,否则在 FIFO 溢出时以新数据覆盖旧数据。

Transmit Fifo Priority:用于设置发送报文的优先级判定方法 (ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 ID 的优先级来发送。配置完这些结构体成员后,我们调用库函数 HAL_CAN_Init 即可把这些参数写入到 CAN 控制寄存器中,实现 CAN 的初始化


使用特权

评论回复
26
labasi|  楼主 | 2021-8-5 11:45 | 只看该作者
问题:为啥CAN分为RX0,RX1中断呢?


答案:STM32有2个3级深度的接收缓冲区:FIFO0和FIFO1,每个FIFO都可以存放3个完整的报文,它们完全由硬件来管理。如果是来自FIFO0的接收中断,则用CAN1_RX0_IRQn中断来处理。如果是来自FIFO1的接收中断,则用CAN1_RX1_IRQn中断来处理,如图:



使用特权

评论回复
27
labasi|  楼主 | 2021-8-5 11:45 | 只看该作者
问题:CAN SCE中断时什么?

答案:status chanege error,错误和状态变化中断!


使用特权

评论回复
28
labasi|  楼主 | 2021-8-5 11:46 | 只看该作者
四. 实验1.Normal模式测试500K 波特率(定时发送,轮询接收)1.1 CubeMx配置


使用特权

评论回复
29
labasi|  楼主 | 2021-8-5 11:49 | 只看该作者
1.2 设置Filter过滤,我们只使能FIFO0,并且不过滤任何消息
uint8_t bsp_can1_filter_config(void)
{
    CAN_FilterTypeDef filter = {0};
    filter.FilterActivation = ENABLE;
    filter.FilterMode = CAN_FILTERMODE_IDMASK;
    filter.FilterScale = CAN_FILTERSCALE_32BIT;
    filter.FilterBank = 0;
    filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    filter.FilterIdLow = 0;
    filter.FilterIdHigh = 0;
    filter.FilterMaskIdLow = 0;
    filter.FilterMaskIdHigh = 0;
    HAL_CAN_ConfigFilter(&hcan1, &filter);
    return BSP_CAN_OK;
}


使用特权

评论回复
30
labasi|  楼主 | 2021-8-5 11:50 | 只看该作者
1.3 开启CAN(注意,默认Cubemx生成的代码并没有can start)
HAL_CAN_Start(&hcan1);
1.4 编写发送函数
我们开出了几个参数,id_type是扩展帧还是标准帧,basic_id标准帧ID(在标准帧中有效),ex_id扩展帧ID(在扩展帧中有效),data要发送的数据,data_len要发送的数据长度

uint8_t bsp_can1_send_msg(uint32_t id_type,uint32_t basic_id,uint32_t ex_id,uint8_t *data,uint32_t data_len)
{
    uint8_t index = 0;
    uint32_t *msg_box;
        uint8_t send_buf[8] = {0};
    CAN_TxHeaderTypeDef send_msg_hdr;
    send_msg_hdr.StdId = basic_id;
    send_msg_hdr.ExtId = ex_id;
    send_msg_hdr.IDE = id_type;
    send_msg_hdr.RTR = CAN_RTR_DATA;
    send_msg_hdr.DLC = data_len;
        send_msg_hdr.TransmitGlobalTime = DISABLE;
        for(index = 0; index < data_len; index++)
          send_buf[index] = data[index];

    HAL_CAN_AddTxMessage(&hcan1,&send_msg_hdr,send_buf,msg_box);
    return BSP_CAN_OK;
}
我们在main函数中1s发送一帧,标准帧跟扩展帧交叉调用,代码如下:

    send_data[0]++;
        send_data[1]++;
        send_data[2]++;
        send_data[3]++;
        send_data[4]++;
        send_data[5]++;
        send_data[6]++;
        send_data[7]++;
        if(id_type_std == 1)
        {
      bsp_can1_send_msg(CAN_ID_STD,1,2,send_data,8);
      id_type_std = 0;
        }
        else
        {
      bsp_can1_send_msg(CAN_ID_EXT,1,2,send_data,8);
      id_type_std = 1;
        }
        HAL_Delay(1000);
我们通过CAN协议分析仪来抓下结果




使用特权

评论回复
31
labasi|  楼主 | 2021-8-5 11:51 | 只看该作者
1.5 编写轮询接收函数
uint8_t bsp_can1_polling_recv_msg(uint32_t *basic_id,uint32_t *ex_id,uint8_t *data,uint32_t *data_len)
{
        uint8_t index = 0;
        uint8_t recv_data[8];
    CAN_RxHeaderTypeDef header;

  while (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) != 0)
  {
    if (__HAL_CAN_GET_FLAG(&hcan1, CAN_FLAG_FOV0) != RESET)
      printf("[CAN] FIFO0 overrun!\n");

    HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &header, recv_data);
        if(header.IDE == CAN_ID_STD)
        {
              printf("StdId ID:%d\n",header.StdId);
        }
        else
        {
              printf("ExtId ID:%d\n",header.ExtId);
        }
        printf("CAN IDE:0x%x\n",header.IDE);
        printf("CAN RTR:0x%x\n",header.RTR);
        printf("CAN DLC:0x%x\n",header.DLC);
        printf("RECV DATA:");
        for(index = 0; index < header.DLC; index++)
        {
              printf("0x%x ",recv_data[index]);
        }
        printf("\n");
  }
}


实验一总结:

1.没用调用HAL_CAN_Start(&hcan1);使能CAN

2.没有编写Filter函数,我开始自认为不设置就默认不过滤,现在看来是我想多了,其实想想也合理,你如果不过滤分配FIFO,STM32怎么决定把收到的放到哪个FIFO中

待提升:

1.目前只用到FIFO0,待把FIFO1使用起来


使用特权

评论回复
32
labasi|  楼主 | 2021-8-5 11:52 | 只看该作者
2.Normal模式测试500K 波特率(定时发送,中断接收)2.1 CubeMx配置


使用特权

评论回复
33
labasi|  楼主 | 2021-8-5 11:54 | 只看该作者
步骤2,3,4跟polling完全一致,我们来直接说下中断怎么用(主要是使能notifity就行了)

static void MX_CAN1_Init(void)
{
  /* USER CODE BEGIN CAN1_Init 0 */
  /* USER CODE END CAN1_Init 0 */
  /* USER CODE BEGIN CAN1_Init 1 */
  /* USER CODE END CAN1_Init 1 */
  hcan1.Instance = CAN1;
  hcan1.Init.Prescaler = 12;
  hcan1.Init.Mode = CAN_MODE_NORMAL;
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan1.Init.TimeSeg1 = CAN_BS1_4TQ;
  hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
  hcan1.Init.TimeTriggeredMode = DISABLE;
  hcan1.Init.AutoBusOff = ENABLE;
  hcan1.Init.AutoWakeUp = ENABLE;
  hcan1.Init.AutoRetransmission = DISABLE;
  hcan1.Init.ReceiveFifoLocked = DISABLE;
  hcan1.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN1_Init 2 */
  bsp_can1_filter_config();
        HAL_CAN_Start(&hcan1);
        HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);
  /* USER CODE END CAN1_Init 2 */
}


使用特权

评论回复
34
labasi|  楼主 | 2021-8-5 11:57 | 只看该作者
下面我们来编写下中断函数

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
        uint8_t index = 0;
        uint8_t recv_data[8];
      CAN_RxHeaderTypeDef header;

        HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &header, recv_data);
        if(header.IDE == CAN_ID_STD)
        {
          printf("StdId ID:%d\n",header.StdId);
        }
        else
        {
          printf("ExtId ID:%d\n",header.ExtId);
        }
        printf("CAN IDE:0x%x\n",header.IDE);
        printf("CAN RTR:0x%x\n",header.RTR);
        printf("CAN DLC:0x%x\n",header.DLC);
        printf("RECV DATA:");
        for(index = 0; index < header.DLC; index++)
        {
          printf("0x%x ",recv_data[index]);
        }
        printf("\n");
}


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则