细说STM32F407单片机轮询方式CAN通信
目录
一、项目介绍
二、项目配置
1、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator
2、CAN1
(1)Bit Timings Parameters组,位时序参数
(2)Basic Parameters组,基本参数
(3)Advanced Parameters组,高级参数
三、软件设计
1、KEYLED
2、can.h
3、can.c
4、main.c
四、下载与调试
本文旨在以案例说明STM32F407单片机轮询方式CAN通信的方法。本文仍然使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。需要参考本文作者的其他文章:
参考文章1:细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV_stm32f407 spiflash驱动程序-CSDN博客 https://wenchm.blog.csdn.net/article/details/144587209
参考文章2:细说STM32F407单片机CAN基础知识及其HAL驱动程序-CSDN博客 https://wenchm.blog.csdn.net/article/details/144769950
一、项目介绍
本文实例的动作说明:使用开发板上的按键S2、S3,按下S2后,发送一个数据帧,按下S3后,发送一个遥控帧。开发板上的LED1、LED2,依次响应按键的动作。按下S6后,开发板复位。
[S2]KeyUp = Send a Data Frame. LED1 ON
[S3]KeyDown= Send a Remote Frame. LED2 ON
示例还有以下功能:
开发板上CAN收发器型号是VD230。相关跳线的操作在这里省略10000字。有关CAN的原理图如下:
二、项目配置
1、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator
与参考文章1相同或近似。其中的时钟在本例中,HCLK=100MHz,PCLK1=25MHz,PCLK2=50MHz。
2、CAN1
CAN1的参数设置分为3个部分:
(1)Bit Timings Parameters组,位时序参数
位时序和波特率的原理详见参考文章2,这里设置的参数如下:
注意,STM32F407的CAN控制器在闭环CAN网络中波特率范围是125kbit/s~1Mbit/s,如果计算的实际波特率不在这个范围内,则需要调整分频系数或各位段的时间片个数。
(2)Basic Parameters组,基本参数
基本参数与CAN主控制寄存器CAN_MCR中的一些位对应,对CAN模块的一些特性进行设置。
(3)Advanced Parameters组,高级参数
本例使用轮询方式测试CAN模块的数据发送和接收功能,所以不开启CAN1的任何中断。
三、软件设计
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 <stdio.h>
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
//设置筛选器,要在完成CAN初始化之后调用此函数
HAL_StatusTypeDef CAN_SetFilters()
{
CAN_FilterTypeDef canFilter;//筛选器结构体变量
// Configure the CAN Filter
canFilter.FilterBank = 0; //筛选器组编号
canFilter.FilterMode = CAN_FILTERMODE_IDMASK; //ID掩码模式
canFilter.FilterScale = CAN_FILTERSCALE_32BIT; //32位长度
//设置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位,所有位任意
//设置2:只接收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);
return result;
}
void CAN_TestPoll(uint8_t msgID, uint8_t frameType)
{
//1. 要发送的数据
uint8_t TxData[8]; //发送数据缓存区,最多8字节
TxData[0]=msgID;
TxData[1]=msgID+11;
CAN_TxHeaderTypeDef TxHeader; //发送消息的结构体变量
TxHeader.StdId = msgID; //stdID
TxHeader.RTR = frameType; //数据帧或遥控帧,CAN_RTR_DATA或CAN_RTR_REMOTE
TxHeader.IDE = CAN_ID_STD; //标准格式
TxHeader.DLC = 2; //数据字节数
TxHeader.TransmitGlobalTime = DISABLE;//禁用时间戳
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) < 1) {
} //等待有可用的发送邮箱
uint32_t TxMailbox; //临时变量,用于返回使用的邮箱编号
/* 将消息发送到邮箱 */
if(HAL_CAN_AddTxMessage(&hcan1,&TxHeader,TxData,&TxMailbox) != HAL_OK)
{
printf("Transmit error.\r\n");
return;
}
printf("Send MsgID= %X\r\n",msgID);
//2. 轮询方式接收消息
/* 等待邮箱发送完毕,也就是等待空闲邮箱个数恢复为3 */
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) != 3) {
}
CAN_RxHeaderTypeDef RxHeader; //接收消息的结构体变量
uint8_t RxData[8]; //接收数据缓存区
HAL_Delay(1);
if(HAL_CAN_GetRxFifoFillLevel(&hcan1,CAN_RX_FIFO0) != 1)
{
printf("Message is not received.\r\n");
return;
}
printf("Message is received.\r\n");
if(HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&RxHeader,RxData) == HAL_OK)
{
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("DLC(Data length)= %lX\r\n",RxHeader.DLC);
//数据帧,显示数据内容,遥控帧没有数据
if (TxHeader.RTR == CAN_RTR_DATA)
{
printf("Data[0]= %X\r\n",RxData[0]);
printf("Data[1]= %X\r\n",RxData[1]);
}
}
}
/* USER CODE END 1 */
在文件can.h中有两个自定义函数,其中函数CAN_SetFilters()用于筛选器设置。
4、main.c
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
printf("Demo18_1_CAN:CAN Polling.\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");
printf("[S3]KeyDown= Send a Remote Frame.\r\n");
// MCU output low level LED light is on
LED1_OFF();
LED2_OFF();
/* USER CODE END 2 */
/* 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_TestPoll(msgID++,CAN_RTR_DATA); //发送数据帧
LED1_ON();
LED2_OFF();
}
else if (curKey==KEY_DOWN)
{
CAN_TestPoll(msgID++,CAN_RTR_REMOTE); //发送遥控帧
LED1_OFF();
LED2_ON();
}
printf("** Reselect menu or reset **\r\n");
HAL_Delay(500);
}
/* USER CODE END 3 */
在进入while循环之前,显示了菜单提示信息。
在while()循环中,检测按键输入,当KeyUp键按下时,调用函数CAN_TestPoll()测试发送数据帧,当KeyDown键按下时,调用函数CAN_TestPoll()测试发送遥控帧。函数CAN_TestPoll()是在文件can.c中实现的自定义函数。 函数CAN_TestPoll()用于测试CAN1模块在轮询方式下的数据发送和接收。
/* USER CODE BEGIN 4 */
//串口打印
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END 4 */
四、下载与调试
下载后,自动显示:
按下S2键,可以按下多次:
按下S3键,可以按下多次:
作者:wenchm