开发平台

硬件: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 hcan1;
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.Instance = CAN1;
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.Instance = CAN2;
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)
{

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 ConfigurationPD0 ------> CAN1_RXPD1 ------> 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 ConfigurationPB5 ------> CAN2_RXPB6 ------> 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 ConfigurationPD0 ------> CAN1_RXPD1 ------> 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 ConfigurationPB5 ------> CAN2_RXPB6 ------> 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 0can_filter.FilterMode =CAN_FILTERMODE_IDMASK;// mask modecan_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 idcan_filter.FilterFIFOAssignment = CAN_RX_FIFO0; // assign to fifo0can_filter.FilterActivation = ENABLE; // enable can filtercan_filter.SlaveStartFilterBank= 14;// only meaningful in dual can modeHAL_CAN_ConfigFilter(hcan, &can_filter);// init can filterHAL_CAN_Start(hcan);// start can1HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); // enable can1 rx interruptHAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_FULL); // enable can1 rx interruptHAL_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 0can_filter.FilterMode =CAN_FILTERMODE_IDMASK;// mask modecan_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 idcan_filter.FilterFIFOAssignment = CAN_RX_FIFO1; // assign to fifo0can_filter.FilterActivation = ENABLE; // enable can filtercan_filter.SlaveStartFilterBank= 14;// only meaningful in dual can modeHAL_CAN_ConfigFilter(hcan, &can_filter);// init can filterHAL_CAN_Start(hcan);// start can1HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO1_MSG_PENDING); // enable can1 rx interruptHAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO1_FULL); // enable can1 rx interruptHAL_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_t StdId,uint32_t ExtId,uint32_t IDE,uint32_t RTR,uint32_t DLC){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通讯,使用时不要盲目。