在实际的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
|
|