开发平台 硬件:STM32F407IGH6开发板
软件:STM32CubeMX、Keil
CAN通讯特点 关于CAN通讯的特点网上的文章介绍已经很全面了,我个人对CAN通讯的看法就是:挂载设备多(无需像SPI那样通过加线的方式挂载增加的设备)、硬件简单(只需要两条线或者四条线)、通讯距离长(IIC、SPI、串口的显著缺点就是如果通讯距离长,会因为通讯线的电阻压降导致信号出现问题无法保证正常通讯)、抗干扰能力强(原理上很简单通讯协议基于比较两条线上的电压差值进行通讯即差分电压)、通讯速率快数据量大(可以说是目前工业上应用经过实践检验非常可靠的通讯方式)
硬件介绍 硬件原理图如下:这里有一点需要注意的是CAN总线在接线的时候H接H L接L
文章图片
CubeMX配置介绍 HAL库是STM32编程的发展趋势和潮流,其代码可读性,兼容性远远高于标准库,且基础底层配置十分方便,接下来简单介绍一下双路CAN通讯的CubeMX底层配置:
CAN1:
文章图片
文章图片
文章图片
CAN2部分配置只介绍和CAN1不同的部分:
文章图片
NVIC中的配置:
文章图片
时钟配置如下(我这里使用的晶振是12M的,如果使用8M晶振需要注意):
文章图片
两路CAN通讯在CubeMX中的配置就结束了,这里面的中断部分的配置细节需要注意一下。
代码配置 我写的注释部分内容可能因为对STM32CAN通讯认识不深会有纰漏,希望大佬勿喷,CubeMX生成的代码并不是总能直接使用的,需要稍加修改:
CubeMX生成的can.c
/!**************************************************
- @file: can.c
- @brief: 使用CbueMX配置生成的can配置代码
- @date: 2021/12/29
- @note:
****************************************************/
#include “can.h”
CAN_HandleTypeDef hcan2;
/* CAN1通讯配置
/
/* 预分频配置3
通讯模式配置 正常模式
数据宽度配置 CAN_SJW_1TQ
时钟部分1配置 9
时钟部分2配置 4
定时器触发配置 不使能
自动缓存关闭配置 不使能
自动唤醒配置 不使能
自动限制任务 不使能
接收区FIFO锁定 不使能
发送区FIFO优先级 不使能
- @brief: 使用CbueMX配置生成的can通讯配置代码
- @date: 2021/12/29
- @note:
*/
void MX_CAN1_Init(void)
{
hcan1.Init.Prescaler = 3;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_9TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_4TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = DISABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
}
/* CAN2通讯配置
/
/* 预分频配置3
通讯模式配置 正常模式
数据宽度配置 CAN_SJW_1TQ
时钟部分1配置 9
时钟部分2配置 4
定时器触发配置 不使能
自动缓存关闭配置 不使能
自动唤醒配置 不使能
自动限制任务 不使能
接收区FIFO锁定 不使能
发送区FIFO优先级 不使能
- @brief: 使用CbueMX配置生成的can通讯配置代码
- @date: 2021/12/29
- @note: 对引脚进行配置配置的详细信息见上面
*/
void MX_CAN2_Init(void)
{
hcan2.Init.Prescaler = 3;
hcan2.Init.Mode = CAN_MODE_NORMAL;
hcan2.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan2.Init.TimeSeg1 = CAN_BS1_9TQ;
hcan2.Init.TimeSeg2 = CAN_BS2_4TQ;
hcan2.Init.TimeTriggeredMode = DISABLE;
hcan2.Init.AutoBusOff = DISABLE;
hcan2.Init.AutoWakeUp = DISABLE;
hcan2.Init.AutoRetransmission = DISABLE;
hcan2.Init.ReceiveFifoLocked = DISABLE;
hcan2.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan2) != HAL_OK)
{
Error_Handler();
}
}
static uint32_t HAL_RCC_CAN1_CLK_ENABLED=0;
/* 进一步对CAN进行配置,包括引脚映射初始化 /
void HAL_CAN_MspInit(CAN_HandleTypeDef canHandle)
{
【嵌入式|CAN通讯】GPIO_InitTypeDef GPIO_InitStruct = {0};
if(canHandle->Instance==CAN1)
{
/* USER CODE BEGIN CAN1_MspInit 0 */
/* USER CODE END CAN1_MspInit 0 /
/ CAN1 clock enable */
HAL_RCC_CAN1_CLK_ENABLED++;
if(HAL_RCC_CAN1_CLK_ENABLED==1){
__HAL_RCC_CAN1_CLK_ENABLE();
}
__HAL_RCC_GPIOD_CLK_ENABLE();
/**CAN1 GPIO Configuration
PD0------> CAN1_RX
PD1------> CAN1_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* CAN1通讯中断初始化 */
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
/* USER CODE BEGIN CAN1_MspInit 1 */
/* USER CODE END CAN1_MspInit 1 /
}
else if(canHandle->Instance==CAN2)
{
/ USER CODE BEGIN CAN2_MspInit 0 */
/* USER CODE END CAN2_MspInit 0 /
/ CAN2 clock enable */
__HAL_RCC_CAN2_CLK_ENABLE();
HAL_RCC_CAN1_CLK_ENABLED++;
if(HAL_RCC_CAN1_CLK_ENABLED==1){
__HAL_RCC_CAN1_CLK_ENABLE();
}
__HAL_RCC_GPIOB_CLK_ENABLE();
/**CAN2 GPIO Configuration
PB5------> CAN2_RX
PB6------> CAN2_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* CAN2通讯中断初始化 */
HAL_NVIC_SetPriority(CAN2_RX1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(CAN2_RX1_IRQn);
/* USER CODE BEGIN CAN2_MspInit 1 */
/* USER CODE END CAN2_MspInit 1 */
}
}
/* 本函数具体作用是取消CAN对以上功能的配置 /
void HAL_CAN_MspDeInit(CAN_HandleTypeDef canHandle)
{
if(canHandle->Instance==CAN1)
{
/* USER CODE BEGIN CAN1_MspDeInit 0 */
/* USER CODE END CAN1_MspDeInit 0 /
/ Peripheral clock disable */
HAL_RCC_CAN1_CLK_ENABLED–;
if(HAL_RCC_CAN1_CLK_ENABLED==0){
__HAL_RCC_CAN1_CLK_DISABLE();
}
/**CAN1 GPIO Configuration
PD0------> CAN1_RX
PD1------> CAN1_TX
*/
HAL_GPIO_DeInit(GPIOD, GPIO_PIN_0|GPIO_PIN_1);
/* CAN1 interrupt Deinit */
HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn);
/* USER CODE BEGIN CAN1_MspDeInit 1 */
/* USER CODE END CAN1_MspDeInit 1 /
}
else if(canHandle->Instance==CAN2)
{
/ USER CODE BEGIN CAN2_MspDeInit 0 */
/* USER CODE END CAN2_MspDeInit 0 /
/ Peripheral clock disable */
__HAL_RCC_CAN2_CLK_DISABLE();
HAL_RCC_CAN1_CLK_ENABLED–;
if(HAL_RCC_CAN1_CLK_ENABLED==0){
__HAL_RCC_CAN1_CLK_DISABLE();
}
/**CAN2 GPIO Configuration
PB5------> CAN2_RX
PB6------> CAN2_TX
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_5|GPIO_PIN_6);
/* CAN2 interrupt Deinit */
HAL_NVIC_DisableIRQ(CAN2_RX1_IRQn);
/* USER CODE BEGIN CAN2_MspDeInit 1 */
/* USER CODE END CAN2_MspDeInit 1 */
}
}
在这里插入代码片
can_usr.c:
/*!***************************************************
* @file: can_usr.c
* @brief:
* @author:
* @date: 2021年10月9日
* @note:
****************************************************/
#include "can_usr.h"
#include "string.h"
#include "stdint.h"CAN_TxHeaderTypeDef hCAN1_TxHeader;
//CAN1发送消息
CAN_TxHeaderTypeDef hCAN2_TxHeader;
//CAN1发送消息/*!***************************************************
* @param:can Handle
* @return:
* @note: 配置CAN过滤器1 开中断
* @date: 2022年7月2日
* @author:
****************************************************/
static void filter_init(CAN_HandleTypeDef *hcan)
{
CAN_FilterTypeDefcan_filter;
can_filter.FilterBank = 0;
// filter 0
can_filter.FilterMode =CAN_FILTERMODE_IDMASK;
// mask mode
can_filter.FilterScale = CAN_FILTERSCALE_32BIT;
can_filter.FilterIdHigh = 0;
can_filter.FilterIdLow= 0;
can_filter.FilterMaskIdHigh = 0;
can_filter.FilterMaskIdLow= 0;
// set mask 0 to receive all can id
can_filter.FilterFIFOAssignment = CAN_RX_FIFO0;
// assign to fifo0
can_filter.FilterActivation = ENABLE;
// enable can filter
can_filter.SlaveStartFilterBank= 14;
// only meaningful in dual can modeHAL_CAN_ConfigFilter(hcan, &can_filter);
// init can filter
HAL_CAN_Start(hcan);
// start can1
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
// enable can1 rx interrupt
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_FULL);
// enable can1 rx interrupt
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_OVERRUN);
// enable can1 rx interrupt}/*!***************************************************
* @param:can Handle
* @return:
* @note: 配置CAN过滤器2 开中断
* @date: 2022年7月2日
* @author:
****************************************************/
static void filter2_init(CAN_HandleTypeDef *hcan)
{CAN_FilterTypeDefcan_filter;
can_filter.FilterBank = 14;
// filter 0
can_filter.FilterMode =CAN_FILTERMODE_IDMASK;
// mask mode
can_filter.FilterScale = CAN_FILTERSCALE_32BIT;
can_filter.FilterIdHigh = 0;
can_filter.FilterIdLow= 0;
can_filter.FilterMaskIdHigh = 0;
can_filter.FilterMaskIdLow= 0;
// set mask 0 to receive all can id
can_filter.FilterFIFOAssignment = CAN_RX_FIFO1;
// assign to fifo0
can_filter.FilterActivation = ENABLE;
// enable can filter
can_filter.SlaveStartFilterBank= 14;
// only meaningful in dual can modeHAL_CAN_ConfigFilter(hcan, &can_filter);
// init can filter
HAL_CAN_Start(hcan);
// start can1
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO1_MSG_PENDING);
// enable can1 rx interrupt
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO1_FULL);
// enable can1 rx interrupt
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO1_OVERRUN);
// enable can1 rx interrupt}
/*!***************************************************
* @param:can Handle
* @return:
* @note: CAN1中断回调函数
* @date: 2022年7月2日
* @author:
****************************************************/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef rx_header;
//1字节帧头,4字节时间,2字节速度,1字节校验
uint8_t CAN1_rxBuf[16] = {0};
if (hcan->Instance == CAN1)
{
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, &CAN1_rxBuf[0]) != HAL_OK)
{
Error_Handler();
}
/* 根据通讯协议定义的ID去接收消息 */
switch (rx_header.StdId)
{
default:
break;
}
}
}/*!***************************************************
* @param:can Handle
* @return:
* @note: CAN2中断回调函数
* @date: 2022年7月2日
* @author:
****************************************************/
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef rx_header;
uint8_t CAN_rxBuf[16] = {0};
if (hcan->Instance == CAN2)
{
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO1, &rx_header, &CAN_rxBuf[0]) != HAL_OK)
{
Error_Handler();
}
/* 根据通讯协议定义的ID去接收消息 */
switch (rx_header.StdId)
{
default:
break;
}}
}/*******************************************************************************
* Function Name: vApp_CAN_TxHeader_Init
* Description: 初始化发送帧头句柄
* Input: pHeader 发送帧头指针
StdId 标识符
ExtId 扩展标识符
IDE 0:标准帧 1:拓展帧
RTR 0:数据帧 1:远程帧
DLC 数据长度
* Output: None
* Return: None
****************************************************************************** */
void vApp_CAN_TxHeader_Init(CAN_TxHeaderTypeDef*pHeader,
uint32_tStdId,
uint32_tExtId,
uint32_tIDE,
uint32_tRTR,
uint32_tDLC)
{
pHeader->StdId= StdId;
//11位标准标识符
pHeader->ExtId= ExtId;
//29位扩展标识符
pHeader->IDE= IDE;
//1位0:标准帧 1:拓展帧
pHeader->RTR= RTR;
//1位0:数据帧 1:远程帧
pHeader->DLC= DLC;
//4位发送的数据的长度
pHeader->TransmitGlobalTime=ENABLE;
}/*******************************************************************************
* Function Name : vApp_User_CAN1_TxMessage
* Description : 使用CAN1发送数据
* Input : None
* Output : None
* Return : None
****************************************************************************** */
void vApp_User_CAN1_TxMessage(uint16_t ID, uint8_t aTxData[], uint8_t DLC)
{
hCAN1_TxHeader.StdId = ID;
vApp_CAN_TxMessage(&hcan1, &hCAN1_TxHeader, aTxData, DLC);
}/*******************************************************************************
* Function Name : vApp_User_CAN2_TxMessage
* Description : 使用CAN2发送数据
* Input : None
* Output : None
* Return : None
****************************************************************************** */
void vApp_User_CAN2_TxMessage(uint16_t ID, uint8_t aTxData[], uint8_t DLC)
{
hCAN2_TxHeader.StdId = ID;
vApp_CAN_TxMessage(&hcan2, &hCAN2_TxHeader, aTxData, DLC);
}/*******************************************************************************
* Function Name: vApp_CAN_TxMessage
* Description: 邮箱发送数据
* Input: hcan
pTxHeader 发送帧头
aData 数据段
DLC 数据段长度
* Output: None
* Return: None
****************************************************************************** */
void vApp_CAN_TxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pTxHeader, uint8_t aData[], uint8_t DLC)
{
uint32_t Tx_MailBox;
/*-1- 配置数据段长度 ----------------------------------------*/
pTxHeader->DLC=DLC;
/*-2- 发送aData ---------------------------------------------*/
while (HAL_CAN_AddTxMessage(hcan, pTxHeader, aData, &Tx_MailBox) != HAL_OK)
{
}
}/*!***************************************************
* @param:can Handle
* @return:
* @note: CAN1 CAN2总线初始化函数
* @date: 2022年7月2日
* @author:
****************************************************/
void CAN12_init(void)
{
filter_init(&hcan1);
filter2_init(&hcan2);
vApp_CAN_TxHeader_Init(&hCAN1_TxHeader, 0x12, 0, CAN_ID_STD, CAN_RTR_DATA, 8);
vApp_CAN_TxHeader_Init(&hCAN2_TxHeader, 0x13, 0, CAN_ID_STD, CAN_RTR_DATA, 8);
}
can_user.h:
#define CAN_LEN100//CAN报文环形队列长度
#define CAN_FRAME_LEN 8 //CAN报文有效长度union CANFrame
{
uint8_t buf[CAN_FRAME_LEN+2];
//单条报文缓冲区
struct
{
uint8_t data[CAN_FRAME_LEN];
//报文有效数据
uint16_t flag;
//报文有效标志
} dat;
};
externunion CANFrame canFrame[CAN_LEN],can_send;
extern CAN_TxHeaderTypeDef hCAN1_TxHeader;
//CAN1发送消息
extern CAN_TxHeaderTypeDef hCAN2_TxHeader;
//CAN1发送消息/*!***************************************************
* @param:can Handle
* @return:
* @note: CAN1 CAN2总线初始化函数
* @date: 2022年7月2日
* @author:
****************************************************/
void CAN12_init(void);
/*!***************************************************
* @param:can Handle
* @return:
* @note: 配置CAN过滤器1 开中断
* @date: 2022年7月2日
* @author:
****************************************************/
static void filter_init(CAN_HandleTypeDef* hcan );
/*******************************************************************************
* Function Name: vApp_CAN_TxMessage
* Description: 邮箱发送数据
* Input: hcan
pTxHeader 发送帧头
aData 数据段
DLC 数据段长度
* Output: None
* Return: None
****************************************************************************** */
void vApp_CAN_TxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef * pTxHeader, uint8_t aData[], uint8_t DLC);
/*******************************************************************************
* Function Name : vApp_User_CAN1_TxMessage
* Description : 使用CAN1发送数据
* Input : None
* Output : None
* Return : None
****************************************************************************** */
void vApp_User_CAN1_TxMessage(uint16_t ID,uint8_t aTxData[], uint8_t DLC);
/*******************************************************************************
* Function Name : vApp_User_CAN2_TxMessage
* Description : 使用CAN2发送数据
* Input : None
* Output : None
* Return : None
****************************************************************************** */
void vApp_User_CAN2_TxMessage(uint16_t ID,uint8_t aTxData[], uint8_t DLC);
这里简单补充一下CAN的初始化顺序:
MX_CAN1_Init();
MX_CAN2_Init();
CAN12_init();
//配置CAN
总结 CAN通讯应用十分广泛,功能十分强大,本文所介绍的代码只是根据我的个人需求,所配置的CAN通讯,使用时不要盲目。
推荐阅读
- 程序员|树莓派4B安装Ubuntu Mate20.04
- STM32的使用|STM32F4软件IIC的使用
- stm32|基于stm32f103与IIC的0.96OLED屏幕显示字符与平滑滚动显示
- 嵌入式|STM32 0.96寸4针IOLED显示器驱动IIC(HAL库)
- stm32|STM32Cubemx——IIC驱动0.96寸OLED
- stm32|基于I2C/SPI总线的温湿度采集与OLED显示
- 嵌入式|STM32 0.96寸OLED IIC通信
- oled模块学习|OLED_I2C_SH1106屏幕教程
- STM32|STM32控制0.96寸OLED(4针/4Pin)汉字以及数字,英文显示