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

目录

一、工程配置

1、时钟、DEBUG、USART6、GPIO、CodeGenerator

2、CAN1 

3、NVIC

二、软件设计

1、KEYLED

2、can.h

3、can.c

(1)CAN1中断初始化

(2)RNG初始化和随机数产生

(3) 筛选器组设置

(4)发送消息

(5)中断方式接收消息

4、main.c

三、下载与运行


        在实际的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,因为它们都只有一个筛选器。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wenchm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值