打印
[STM32F4]

细说STM32F407单片机中断方式CAN通信

[复制链接]
58|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2025-1-10 13:57 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
     在实际的CAN通信中,使用轮询方式发送消息,使用中断方式接收消息更加实用和普遍。本实例设计一个CAN通信,使用中断方式接收消息,并且测试在两个FIFO上使用不同的筛选器。

        本实例仍然使用使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。需要参考本文作者的其他文章:

        参考文章1: 细说STM32F407单片机轮询方式CAN通信_stm32f407can波特率配置表-CSDN博客  https://wenchm.blog.csdn.net/article/details/144852504

        参考文章2:细说STM32F407单片机CAN基础知识及其HAL驱动程序-CSDN博客  https://wenchm.blog.csdn.net/article/details/144769950

        示例仍然引用KEYLED文件夹中的文件。

        示例的功能和使用流程如下:

        示例工程中,使用开发板上的S2键,按下S2键后,发送一个随机数的数据帧。并用LED1显示工作状态,输出到串口助手上。按下S6键,开发板复位。示例的功能还包括:

使用CAN1的回环模式自发自收。
开启FIFO0的接收中断,开启FIFO1的接收中断。
为FIFO0设置筛选器,只接收标识符ID为奇数的消息;为FIFO1设置筛选器,接收所有消息。
使用随机数生成器(Random Number Generator,RNG),在发送消息时,用随机数作为帧的数据。
[S2]KeyUp  = Send a Data Frame    LED1 ON
一、工程配置
        CAN接口原理图同参考文件1。

1、时钟、DEBUG、USART6、GPIO、CodeGenerator
        与参考 文件1的设置相同或近似。

        这里,设置HCLK=168MHz,PCLK1=42MHz,PCLK2=84MHz。

2、CAN1
         CAN1模块的参数设置与参考文件1相似。





3、NVIC
        使用CAN1模块的接收中断,打开CAN1 RX0中断和CAN1 RX1中断,两个中断的抢占优先级都设置为1,一个CAN模块有4个中断,RX0中断是FIFO0的中断,RX1中断是FIFO1的中断,每个中断有几个中断事件和对应的回调函数,详见参考文章2。



二、软件设计
1、KEYLED
        本例的项目中要使用KEYLED,其中的keyled.c和keyled.h的使用方法与参考文章1相同。

2、can.h
/* USER CODE BEGIN Prototypes */
HAL_StatusTypeDef CAN_SetFilters();
void CAN_TestPoll(uint8_t msgID,uint8_t frameType);
/* USER CODE END Prototypes */
3、can.c
/* USER CODE BEGIN 0 */
#include "rng.h"
#include <stdio.h>
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
//设置筛选器,需要在完成CAN初始化之后调用此函数
HAL_StatusTypeDef CAN_SetFilters()
{
        CAN_FilterTypeDef canFilter;

//1. 设置FIFO0的筛选器
        canFilter.FilterBank = 0;                                                //筛选器组编号
        canFilter.FilterMode = CAN_FILTERMODE_IDMASK;        //ID掩码模式
        canFilter.FilterScale = CAN_FILTERSCALE_32BIT;        //32位长度

        //只接收stdID为奇数的帧
        canFilter.FilterIdHigh = 0x0020;                                //CAN_FxR1寄存器的高16位
        canFilter.FilterIdLow = 0x0000;                                        //CAN_FxR1寄存器的低16位
        canFilter.FilterMaskIdHigh = 0x0020;                        //CAN_FxR2寄存器的高16位
        canFilter.FilterMaskIdLow = 0x0000;                                //CAN_FxR2寄存器的低16位

        canFilter.FilterFIFOAssignment = CAN_RX_FIFO0;        //应用于FIFO0
        canFilter.FilterActivation = ENABLE;                        //使用筛选器
        canFilter.SlaveStartFilterBank = 14;                        //从CAN控制器筛选器起始的Bank

        HAL_StatusTypeDef result=HAL_CAN_ConfigFilter(&hcan1, &canFilter);

//2. 设置FIFO1的筛选器
        canFilter.FilterBank = 1;                                                //筛选器组编号
        //接收所有帧
        canFilter.FilterIdHigh = 0x0000;                                //CAN_FxR1寄存器的高16位
        canFilter.FilterIdLow = 0x0000;                                        //CAN_FxR1寄存器的低16位
        canFilter.FilterMaskIdHigh = 0x0000;                        //CAN_FxR2寄存器的高16位,所有位任意
        canFilter.FilterMaskIdLow = 0x0000;                                //CAN_FxR2寄存器的低16位,所有位任意

        canFilter.FilterFIFOAssignment = CAN_RX_FIFO1;        //应用于FIFO 1

        result=HAL_CAN_ConfigFilter(&hcan1, &canFilter);

        return result;
}

void CAN_SendMsg(uint8_t msgID,uint8_t frameType)
{
        CAN_TxHeaderTypeDef TxHeader;
        TxHeader.StdId = msgID;                        //stdID
        TxHeader.RTR = frameType;                //数据帧,CAN_RTR_DATA
        TxHeader.IDE = CAN_ID_STD;                //标准格式
        TxHeader.DLC =4;                                   //数据长度
        TxHeader.TransmitGlobalTime = DISABLE;

        uint32_t rand;
        HAL_RNG_GenerateRandomNumber(&hrng, &rand);        //产生32位随机数
        uint8_t TxData[8];                                //最多8个字节
        TxData[3] = rand & 0x000000FF;
        TxData[2] = (rand & 0x0000FF00)>>8;
        TxData[1] = (rand & 0x00FF0000)>>16;
        TxData[0] = (rand & 0xFF000000)>>24;

        while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) < 1) {
        } //等待有可用的发送邮箱

        printf("Send MsgID= %X\r\n",msgID);

        uint32_t TxMailbox;                //临时变量,用于返回使用的邮箱编号
        /* 发送到邮箱,由CAN模块负责发送到CAN总线 */
        if(HAL_CAN_AddTxMessage(&hcan1,&TxHeader,TxData,&TxMailbox) != HAL_OK)
                printf("Send to mailbox error.\r\n");
}

//读取和显示FIFO0或FIFO1的消息
//参数FIFO_num是FIFO编号,CAN_RX_FIFO0或CAN_RX_FIFO1
void CAN_ReadMsg(uint32_t FIFO_num)
{
        CAN_RxHeaderTypeDef RxHeader;
        uint8_t RxData[8];        //接收数据缓存区,8个字节

        if(FIFO_num==CAN_RX_FIFO0)
        {
                printf("Msg received by FIFO0.\r\n");
                if(HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&RxHeader,RxData) != HAL_OK)
                {
                        printf("Read FIFO0 error.\r\n");
                        return;
                }
        }
        else
        {
                printf("Msg received by FIFO1.\r\n");
                if(HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO1, &RxHeader, RxData) != HAL_OK)
                {
                        printf("Read FIFO1 error.\r\n");
                        return;
                }
        }

        //显示接收到的消息
        printf("StdID= %lX\r\n",RxHeader.StdId);
        printf("RTR(0=Data,2=Remote)= %lX\r\n",RxHeader.RTR);
        printf("IDE(0=Std,4=Ext)= %lX\r\n",RxHeader.IDE);
        printf("FilterMatchIndex= %lX\r\n",RxHeader.FilterMatchIndex);
        printf("DLC(Data length)= %lX\r\n",RxHeader.DLC);

        for (uint8_t i=0; i<4; i++)
        {
                printf("Data[0]= %X\r\n",RxData[0]);
                printf("Data[1]= %X\r\n",RxData[1]);
                printf("Data[2]= %X\r\n",RxData[2]);
                printf("Data[3]= %X\r\n",RxData[3]);
        }
        printf("** Reselect menu or reset **\r\n");
}
//FIFO0接收到新消息事件中断回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
        CAN_ReadMsg(CAN_RX_FIFO0);
}

//FIFO1接收到新消息事件中断回调函数
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
        CAN_ReadMsg(CAN_RX_FIFO1);
}
/* USER CODE END 1 */

(1)CAN1中断初始化
        函数MX_CAN1_Init()的代码与参考文章1完全相同,函数HAL_CAN_MspInit()中与参考文章1比较,增加了两个中断的初始化设置,开启了CAN1_RX0和CAN1_RX1中断:

    /* CAN1 interrupt Init */
    HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
    HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
(2)RNG初始化和随机数产生
        RNG是MCU的一个内部单元,其初始化很简单,就是定义了RNG模块的外设对象变量,开启其时钟。相关代码如下:

#include "rng.h"
RNG_HandleTypeDef hrng;

void MX_RNG_Init(void)
{
  hrng.Instance = RNG;
  if (HAL_RNG_Init(&hrng) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_RNG_MspInit(RNG_HandleTypeDef* rngHandle)
{

  if(rngHandle->Instance==RNG)
  {
    /* RNG clock enable */
    __HAL_RCC_RNG_CLK_ENABLE();
  }
}

void HAL_RNG_MspDeInit(RNG_HandleTypeDef* rngHandle)
{

  if(rngHandle->Instance==RNG)
  {
    /* Peripheral clock disable */
    __HAL_RCC_RNG_CLK_DISABLE();
  }
}

         可以使用轮询方式或中断方式产生32位的随机数,分别对应两个函数。

HAL_RNG_GenerateRandomNumber()    //轮询方式产生随机数。
HAL_RNG_GetRandomNumber_IT()      //中断方式产生随机数。
        HAL_RNG_GenerateRandomNumber()的函数原型如下:

HAL_StatusTypeDef HAL_RNG_GenerateRandomNumber(RNG_HandleTypeDef *hrng,uint32_t*random32bit)
        RNG由时钟树中的时钟信号由PCLK1 42MHz驱动,典型值是42MHz,这个时钟频率不能太低,在本例中,这个时钟频率是42MHz。产生两个连续随机数的最小间隔是42个PLL42CLK周期。

(3) 筛选器组设置
        在文件can.h中,自定义的函数CAN_SetFilters()用于对FIFO0和FIFO1进行筛选器设置。详细代码在can.c中实现。

        为FIFO0设置的筛选器是只接收标识符ID为奇数的消息,为FIFO1设置的筛选器是可以接收任何消息。注意,可以为一个FIFO设置多个筛选器,但是一个筛选器只能用于一个FIFO,所以,这两个筛选器的FilterBank必须不同。结构体CAN_FilterTypeDef各成员变量的意义以及筛选器的设置原理详见参考文章1。

(4)发送消息
        在main.c中调用函数CAN_SendMsg()发送数据帧,这个自定义函数的实现代码与参考文章1不同,并在can.c中实现这个函数的代码。

        在其实现的程序中还调用函数HAL_RNG_GenerateRandomNumber(),产生一个32位随机数,然后分解为4字节存入发送数据缓冲区。程序仍然是用函数HAL_CAN_AddTxMessage()将需要发送的消息写入发送邮箱,由CAN模块自动将消息发送到CAN总线上。函数CAN_SendMsg()只管发送消息,接收消息由中断去处理。

(5)中断方式接收消息
        由于开启了CAN1的RX0中断和RX1中断,在文件stm32f4xx_it.c中自动生成了这两个中断的ISR框架。

        CAN1_RX0是FIFO0接收消息、满或上溢时产生的中断,接收消息中断事件对应的回调函数是HAL_CAN_RxFifo0MsgPendingCallback()。同样的,FIFO1接收消息中断事件对应的回调函数是HAL_CAN_RxFifo1MsgPendingCallback()。CAN1_RX0和CAN1_RX1中断事件与回调函数的对应关系详见参考文章1。所以,要使用中断方式处理FIFO0和FIFO1接收的消息,只需重新实现这两个回调函数即可。在文件can.c中重新实现这两个回调函数。

//FIFO0接收到新消息事件中断回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
        CAN_ReadMsg(CAN_RX_FIFO0);
}

//FIFO1接收到新消息事件中断回调函数
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
        CAN_ReadMsg(CAN_RX_FIFO1);
}
        两个回调函数都调用了同一个函数CAN_ReadMsg(),只是传递了相应的FIFO编号。函数CAN_ReadMsg()负责读取FIFO0或FIFO1的消息并显示。读取FIFO里面收到的消息仍然使用函数HAL_CAN_GetRxMessage(),消息头结构体CAN_RxHeaderTypeDef的意义见参考文章1。这里显示了一个成员变量FilterMatchIndex的值,这是接收消息的FIFO内接收了消息的筛选器的序号,是在一个FIFO内的筛选器的序号,而不是筛选器的FilterBank属性值。

4、main.c
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
  printf("Demo18_2_CAN:CAN Interrupt.\r\n");
  printf("Test mode:Loopback.\r\n");

  if (CAN_SetFilters() == HAL_OK)           //设置筛选器
    printf("ID Filter: Only Odd IDs.\r\n");
  if (HAL_CAN_Start(&hcan1) == HAL_OK)  //启动CAN1模块
        printf("CAN is started.\r\n");

  printf("[S2]KeyUp  = Send a Data Frame.\r\n");

  //必须开启FIFO接收到消息中断事件,否则不会响应中断事件
  __HAL_CAN_ENABLE_IT(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
  __HAL_CAN_ENABLE_IT(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);

  //HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
  //HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);

  // MCU output low level LED light is on
  LED1_OFF();
  /* USER CODE END 2 */

        在外设初始化部分,函数MX_RNG_Init()用于RNG的初始化,函数MX_CAN1_Init()用于CAN1模块的初始化。

        函数CAN_SetFilters()用于设置FIFO0和FIFO1的筛选器组,与参考文章1的同名函数代码不同。这里要使用中断方式进行消息接收,还需要开启FIFO0和FIFO1的接收新消息的中断事件,即

__HAL_CAN_ENABLE_IT(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);
__HAL_CAN_ENABLE_IT(&hcan1,CAN_IT_RX_FIFO1_MSG_PENDING);
        其中的两个宏分别是FIFO0和FIFO1接收新消息的中断事件使能控制位的宏定义,也作为中断事件类型宏定义,详见参考文章2。

        主程序的while()循环中调用自定义函数CAN_SendMsg()以轮询方式发送一个数据帧,接收数据帧在中断里处理。

  /* USER CODE BEGIN WHILE */
  uint8_t msgID=1;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        KEYS curKey = ScanPressedKey(KEY_WAIT_ALWAYS);
        if (curKey==KEY_UP)
        {
                CAN_SendMsg(msgID++, CAN_RTR_DATA);
                LED1_ON();
        }
        else
                LED1_OFF();

        HAL_Delay(500);        //延时,消除按键抖动
  }
  /* USER CODE END 3 */

/* USER CODE BEGIN 4 */
//串口打印
int __io_putchar(int ch)
{
        HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
        return ch;
}
/* USER CODE END 4 */
三、下载与运行
        下载后在串口助手上先显示菜单,或者按下S6复位键也显示菜单。

        每次按下KeyUp键可以发送一个标准格式数据帧,msgID加1,msgID作为数据帧的标识符ID。

        运行时会发现,msgID为奇数时,是由FIFO0接收消息,msgID为偶数时,是由FIFO1接收消息。因为在设置筛选器组时,设置FIFO0只能接收标识符ID为奇数的消息,FIFO1可以接收任意标识符ID的消息。当标识符ID为偶数时,只能由FIFO1接收,当标识符ID为奇数时,两个FIFO都可以接收,但是由FIFO0优先接收。

        不管是哪个FIFO接收的消息,显示的FilterMatchIndex的值都是0,因为它们都只有一个筛选器。



————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/wenchm/article/details/144913579

使用特权

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

本版积分规则

2053

主题

16005

帖子

15

粉丝