STM32F7xx —— CAN通信


STM32F7xx —— CAN通信
目录
STM32F7xx —— CAN通信
一、CAN基础
二、几个重要的CAN函数
三、几个重要的结构
四、接口设计


一、CAN基础 差分信号:显性电平对应逻辑0,CAN_H和CAN_L差为2.5V;隐形电平对应逻辑1,CAN_H和CAN_L差为0V。
CAN总线的开始和结束都有一个120Ω的终端电阻。
数据帧:标准帧11位,扩展帧29位。
其他的一些理论知识就不再赘述了,可以参考维基百科对于CAN的描述。
STM32F7xx的bxCAN主要特点:支持CAN2.0A和CAN2.0B,波特率高达1Mbps,支持时间触发,具有3个发送邮箱,2个接收邮箱,可变的过滤器组等。

二、几个重要的CAN函数

HAL_StatusTypeDef HAL_CAN_Init(CAN_HandleTypeDef* hcan); // CAN初始化HAL_StatusTypeDef HAL_CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout); // CAN发送HAL_StatusTypeDef HAL_CAN_Receive(CAN_HandleTypeDef *hcan, uint8_t FIFONumber, uint32_t Timeout); // CAN接收

三、几个重要的结构
// CAN操作句柄 包含CAN基地址(CAN1/CAN2/CAN3) 初始化结构 发送接收结构体 其余三个是过程变量 typedef struct { CAN_TypeDef*Instance; /*!< Register base address*/CAN_InitTypeDefInit; /*!< CAN required parameters*/CanTxMsgTypeDef*pTxMsg; /*!< Pointer to transmit structure*/CanRxMsgTypeDef*pRxMsg; /*!< Pointer to reception structure */__IO HAL_CAN_StateTypeDefState; /*!< CAN communication state*/HAL_LockTypeDefLock; /*!< CAN locking object*/__IO uint32_tErrorCode; /*!< CAN Error code*/}CAN_HandleTypeDef;

// CAN配置结构体 // 前5个参数来设置 CAN_BTR —— 波特率 // 后6个参数用来设置 CAN_MCR —— 通信相关的控制位 typedef struct { uint32_t Prescaler; /*!< Specifies the length of a time quantum. This parameter must be a number between Min_Data = https://www.it610.com/article/1 and Max_Data = 1024 */uint32_t Mode; /*!< Specifies the CAN operating mode. This parameter can be a value of @ref CAN_operating_mode */uint32_t SJW; /*!< Specifies the maximum number of time quanta the CAN hardware is allowed to lengthen or shorten a bit to perform resynchronization. This parameter can be a value of @ref CAN_synchronisation_jump_width */uint32_t BS1; /*!< Specifies the number of time quanta in Bit Segment 1. This parameter can be a value of @ref CAN_time_quantum_in_bit_segment_1 */uint32_t BS2; /*!< Specifies the number of time quanta in Bit Segment 2. This parameter can be a value of @ref CAN_time_quantum_in_bit_segment_2 */uint32_t TTCM; /*!< Enable or disable the time triggered communication mode. This parameter can be set to ENABLE or DISABLE. */uint32_t ABOM; /*!< Enable or disable the automatic bus-off management. This parameter can be set to ENABLE or DISABLE */uint32_t AWUM; /*!< Enable or disable the automatic wake-up mode. This parameter can be set to ENABLE or DISABLE */uint32_t NART; /*!< Enable or disable the non-automatic retransmission mode. This parameter can be set to ENABLE or DISABLE */uint32_t RFLM; /*!< Enable or disable the receive FIFO Locked mode. This parameter can be set to ENABLE or DISABLE */uint32_t TXFP; /*!< Enable or disable the transmit FIFO priority. This parameter can be set to ENABLE or DISABLE */ }CAN_InitTypeDef;

// 过滤器设置 typedef struct { uint32_t FilterIdHigh; /*!< Specifies the filter identification number (MSBs for a 32-bit configuration, first one for a 16-bit configuration). This parameter must be a number between Min_Data = https://www.it610.com/article/0x0000 and Max_Data = 0xFFFF */uint32_t FilterIdLow; /*!< Specifies the filter identification number (LSBs for a 32-bit configuration, second one for a 16-bit configuration). This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */uint32_t FilterMaskIdHigh; /*!< Specifies the filter mask number or identification number, according to the mode (MSBs for a 32-bit configuration, first one for a 16-bit configuration). This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */uint32_t FilterMaskIdLow; /*!< Specifies the filter mask number or identification number, according to the mode (LSBs for a 32-bit configuration, second one for a 16-bit configuration). This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */uint32_t FilterFIFOAssignment; /*!< Specifies the FIFO (0 or 1) which will be assigned to the filter. This parameter can be a value of @ref CAN_filter_FIFO */uint32_t FilterNumber; /*!< Specifies the filter which will be initialized. This parameter must be a number between Min_Data = 0 and Max_Data = 27 */uint32_t FilterMode; /*!< Specifies the filter mode to be initialized. This parameter can be a value of @ref CAN_filter_mode */uint32_t FilterScale; /*!< Specifies the filter scale. This parameter can be a value of @ref CAN_filter_scale */uint32_t FilterActivation; /*!< Enable or disable the filter. This parameter can be set to ENABLE or DISABLE. */uint32_t BankNumber; /*!< Select the start slave bank filter. This parameter must be a number between Min_Data = 0 and Max_Data = 28 */}CAN_FilterConfTypeDef;

// 模式我们使用普通模式 #define CAN_MODE_NORMAL((uint32_t)0x00000000U)/*!< Normal mode*/ #define CAN_MODE_LOOPBACK((uint32_t)CAN_BTR_LBKM)/*!< Loopback mode */ #define CAN_MODE_SILENT((uint32_t)CAN_BTR_SILM)/*!< Silent mode*/ #define CAN_MODE_SILENT_LOOPBACK((uint32_t)(CAN_BTR_LBKM | CAN_BTR_SILM))/*!< Loopback combined with silent mode */

// 标准帧 扩展帧 #define CAN_ID_STD((uint32_t)0x00000000U)/*!< Standard Id */ #define CAN_ID_EXT((uint32_t)0x00000004U)/*!< Extended Id */

// 数据帧 远程帧 #define CAN_RTR_DATA((uint32_t)0x00000000U)/*!< Data frame */ #define CAN_RTR_REMOTE((uint32_t)0x00000002U)/*!< Remote frame */

// CAN中断使能 __HAL_CAN_ENABLE_IT(__HANDLE__, __INTERRUPT__)

// 接收中断 #define CAN_IT_FMP0((uint32_t)CAN_IER_FMPIE0)/*!< FIFO 0 message pending interrupt */ #define CAN_IT_FF0((uint32_t)CAN_IER_FFIE0)/*!< FIFO 0 full interrupt*/ #define CAN_IT_FOV0((uint32_t)CAN_IER_FOVIE0)/*!< FIFO 0 overrun interrupt*/ #define CAN_IT_FMP1((uint32_t)CAN_IER_FMPIE1)/*!< FIFO 1 message pending interrupt */ #define CAN_IT_FF1((uint32_t)CAN_IER_FFIE1)/*!< FIFO 1 full interrupt*/ #define CAN_IT_FOV1((uint32_t)CAN_IER_FOVIE1)/*!< FIFO 1 overrun interrupt*/

四、接口设计 与串口类似,使用中断接收。先封装单路CAN需要的几个小接口,再顶一个列表,最后使用统一的接口扫描这个列表。
typedef enum { CAN_CHANNEL_NONE, CAN_CHANNEL_1, CAN_CHANNEL_2, CAN_CHANNEL_NUM } can_channel_t; #define CAN1_CHANNELCAN1 #define CAN1_PREEMPT_PRIOCAN1_RX_PRIORITY #define CAN1_RX_IRQCAN1_RX0_IRQn #define CAN1_RX_IRQ_FUNCCAN1_RX0_IRQHandler #define CAN1_CLK_ENABLE()__HAL_RCC_CAN1_CLK_ENABLE() #define CAN1_TX_PORTGPIOA #define CAN1_TX_PINGPIO_PIN_12 #define CAN1_TX_AFGPIO_AF9_CAN1 #define CAN1_TX_CONFIG()GPIOConfigExt(CAN1_TX_PORT, CAN1_TX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN1_TX_AF) #define CAN1_RX_PORTGPIOA #define CAN1_RX_PINGPIO_PIN_11 #define CAN1_RX_AFGPIO_AF9_CAN1 #define CAN1_RX_CONFIG()GPIOConfigExt(CAN1_RX_PORT, CAN1_RX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN1_RX_AF)#define CAN2_CHANNELCAN2 #define CAN2_PREEMPT_PRIOCAN2_RX_PRIORITY #define CAN2_RX_IRQCAN2_RX0_IRQn #define CAN2_RX_IRQ_FUNCCAN2_RX0_IRQHandler #define CAN2_CLK_ENABLE()__HAL_RCC_CAN2_CLK_ENABLE() #define CAN2_TX_PORTGPIOB #define CAN2_TX_PINGPIO_PIN_6 #define CAN2_TX_AFGPIO_AF9_CAN2 #define CAN2_TX_CONFIG()GPIOConfigExt(CAN2_TX_PORT, CAN2_TX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN2_TX_AF) #define CAN2_RX_PORTGPIOB #define CAN2_RX_PINGPIO_PIN_5 #define CAN2_RX_AFGPIO_AF9_CAN2 #define CAN2_RX_CONFIG()GPIOConfigExt(CAN2_RX_PORT, CAN2_RX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN2_RX_AF)

// 抽象出一个CAN设备结构体 typedef struct { CAN_HandleTypeDef handle; // CAN操作句柄 CanTxMsgTypeDef tx; // CAN发送 CanRxMsgTypeDef rx; // CAN接收can_queue_t recv; // 接收队列} can_dev_t;

// 将每路CAN封装成几个小函数 static can_dev_t can1_dev, can2_dev; static void can1_var_init(void) { can1_dev.recv = can1_queue_recv; CanQueueInit(&can1_dev.recv); }static void can1_gpio_init(void) { CAN1_TX_CONFIG(); CAN1_RX_CONFIG(); }// 波特率 = Fpclk1 / ((ts1+ts2+3) * brp)Fpclk1 = 54M static void can1_mode_init(void) { CAN1_CLK_ENABLE(); can1_dev.handle.Instance = CAN1_CHANNEL; can1_dev.handle.pTxMsg = &can1_dev.tx; can1_dev.handle.pRxMsg = &can1_dev.rx; can1_dev.handle.Init.Prescaler = 6; can1_dev.handle.Init.Mode = CAN_MODE_NORMAL; can1_dev.handle.Init.SJW = CAN_SJW_1TQ; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ can1_dev.handle.Init.BS1 = CAN_BS1_11TQ; // tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ can1_dev.handle.Init.BS2 = CAN_BS2_6TQ; // tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ can1_dev.handle.Init.TTCM = DISABLE; // 非时间触发通信模式 can1_dev.handle.Init.ABOM = ENABLE; // 软件自动离线管理 can1_dev.handle.Init.AWUM = DISABLE; // 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) can1_dev.handle.Init.NART = ENABLE; // 禁止报文自动传送 can1_dev.handle.Init.RFLM = DISABLE; // 报文不锁定,新的覆盖旧的 can1_dev.handle.Init.TXFP = DISABLE; // 优先级由报文标识符决定 HAL_CAN_Init(&can1_dev.handle); }static void can1_filter_init(void) { CAN_FilterConfTypeDeffilter; filter.FilterNumber= 0; // 过滤器0 filter.FilterMode= CAN_FILTERMODE_IDMASK; filter.FilterScale= CAN_FILTERSCALE_32BIT; filter.FilterIdHigh= 0; filter.FilterIdLow= 0; filter.FilterMaskIdHigh = 0; filter.FilterMaskIdLow= 0; filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 过滤器0关联到FIFO0 filter.FilterActivation = ENABLE; //激活滤波器0 //filter.BankNumber=14; HAL_CAN_ConfigFilter(&can1_dev.handle, &filter); }static void can1_nvic_init(void) { __HAL_CAN_ENABLE_IT(&can1_dev.handle, CAN_IT_FMP0); //FIFO0消息挂号中断允许. HAL_NVIC_SetPriority(CAN1_RX_IRQ, CAN1_RX_PRIORITY, 0); HAL_NVIC_EnableIRQ(CAN1_RX_IRQ); }static void can2_var_init(void) { can2_dev.recv = can2_queue_recv; CanQueueInit(&can2_dev.recv); }static void can2_gpio_init(void) { CAN2_TX_CONFIG(); CAN2_RX_CONFIG(); }// 波特率 = Fpclk1 / ((ts1+ts2+3) * brp)Fpclk1 = 54M static void can2_mode_init(void) { CAN2_CLK_ENABLE(); can2_dev.handle.Instance = CAN2_CHANNEL; can2_dev.handle.Init.Prescaler = 6; can2_dev.handle.Init.Mode = CAN_MODE_LOOPBACK; can2_dev.handle.Init.SJW = CAN_SJW_1TQ; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ can2_dev.handle.Init.BS1 = CAN_BS1_11TQ; // tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ can2_dev.handle.Init.BS2 = CAN_BS2_6TQ; // tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ can2_dev.handle.Init.TTCM = DISABLE; // 非时间触发通信模式 can2_dev.handle.Init.ABOM = ENABLE; // 软件自动离线管理 can2_dev.handle.Init.AWUM = DISABLE; // 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) can2_dev.handle.Init.NART = ENABLE; // 禁止报文自动传送 can2_dev.handle.Init.RFLM = DISABLE; // 报文不锁定,新的覆盖旧的 can2_dev.handle.Init.TXFP = DISABLE; // 优先级由报文标识符决定 HAL_CAN_Init(&can2_dev.handle); }static void can2_filter_init(void) { CAN_FilterConfTypeDefCAN_FilterInitStructure; CAN_FilterInitStructure.FilterNumber= 14; CAN_FilterInitStructure.FilterMode= CAN_FILTERMODE_IDMASK; CAN_FilterInitStructure.FilterScale= CAN_FILTERSCALE_32BIT; CAN_FilterInitStructure.FilterIdHigh= 0; CAN_FilterInitStructure.FilterIdLow= 0; CAN_FilterInitStructure.FilterMaskIdHigh = 0; CAN_FilterInitStructure.FilterMaskIdLow= 0; CAN_FilterInitStructure.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 过滤器0关联到FIFO0 CAN_FilterInitStructure.FilterActivation = ENABLE; //激活滤波器0 //CAN_FilterInitStructure.BankNumber=14; HAL_CAN_ConfigFilter(&can2_dev.handle, &CAN_FilterInitStructure); }static void can2_nvic_init(void) { __HAL_CAN_ENABLE_IT(&can2_dev.handle, CAN_IT_FMP0); //FIFO0消息挂号中断允许1 HAL_NVIC_SetPriority(CAN2_RX_IRQ, CAN2_RX_PRIORITY, 0); HAL_NVIC_EnableIRQ(CAN2_RX_IRQ); }

// 每路CAN都有的几个操作,使用列表包含,然后统一扫描处理 typedef struct { uint8_t channel; can_dev_t *dev; void (* var_init_cb)(void); void (* gpio_init_cb)(void); void (* mode_init_cb)(void); void (* filter_init_cb)(void); void (* nvic_init_cb)(void); } can_config_t; static const can_config_t can_configs[] = { {CAN_CHANNEL_1, &can1_dev, can1_var_init, can1_gpio_init, can1_mode_init, can1_filter_init, can1_nvic_init}, {CAN_CHANNEL_2, &can2_dev, can2_var_init, can2_gpio_init, can2_mode_init, can2_filter_init, can2_nvic_init}, }; static can_dev_t *can_dev_get(uint8_t channel) { uint8_t i; for(i = 0; i < ARRAY_SIZE(can_configs); ++i) { if(channel == can_configs[i].channel) { return can_configs[i].dev; } }return 0; }static void can_var_init(uint8_t channel) { uint8_t i; for(i = 0; i < ARRAY_SIZE(can_configs); ++i) { if(channel == can_configs[i].channel) { can_configs[i].var_init_cb(); return; } } }static void can_gpio_init(uint8_t channel) { uint8_t i; for(i = 0; i < ARRAY_SIZE(can_configs); ++i) { if(channel == can_configs[i].channel) { can_configs[i].gpio_init_cb(); return; } } }static void can_mode_init(uint8_t channel) { uint8_t i; for(i = 0; i < ARRAY_SIZE(can_configs); ++i) { if(channel == can_configs[i].channel) { can_configs[i].mode_init_cb(); return; } } }static void can_filter_init(uint8_t channel) { uint8_t i; for(i = 0; i < ARRAY_SIZE(can_configs); ++i) { if(channel == can_configs[i].channel) { can_configs[i].filter_init_cb(); return; } } }static void can_nvic_init(uint8_t channel) { uint8_t i; for(i = 0; i < ARRAY_SIZE(can_configs); ++i) { if(channel == can_configs[i].channel) { can_configs[i].nvic_init_cb(); return; } } }

// 对外的接口 初始化 发送和接收 void CanInit(uint8_t channel) { can_var_init(channel); can_gpio_init(channel); can_mode_init(channel); can_filter_init(channel); can_nvic_init(channel); }void CanSend(uint8_t channel, can_frame_t *frame) { can_dev_t *dev = can_dev_get(channel); if(dev == 0) { return; }dev->handle.pTxMsg->StdId = frame->StdId; dev->handle.pTxMsg->IDE= frame->IDE; dev->handle.pTxMsg->RTR= frame->RTR; dev->handle.pTxMsg->DLC= frame->DLC; memcpy(dev->handle.pTxMsg->Data, frame->Data, frame->DLC); HAL_CAN_Transmit(&dev->handle, 10); }uint8_t CanRecv(uint8_t channel, can_frame_t *frame) { can_dev_t *dev = can_dev_get(channel); if(dev == 0) { return 0; }return CanQueueRead(&can1_dev.recv, frame); }

// CAN中断 void CAN1_RX_IRQ_FUNC(void) { HAL_CAN_IRQHandler(&can1_dev.handle); }void CAN2_RX_IRQ_FUNC(void) { HAL_CAN_IRQHandler(&can2_dev.handle); }void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *hcan) { if(hcan == (&can1_dev.handle)) { //CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开 __HAL_CAN_ENABLE_IT(&can1_dev.handle, CAN_IT_FMP0); //重新开启FIF00消息挂号中断 CanQueueWrite(&can1_dev.recv, can1_dev.handle.pRxMsg); } else if(hcan == (&can2_dev.handle)) { //CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开 __HAL_CAN_ENABLE_IT(&can2_dev.handle, CAN_IT_FMP0); //重新开启FIF00消息挂号中断 CanQueueWrite(&can2_dev.recv, can2_dev.handle.pRxMsg); } }

之所以使用列表的形式,是为了方便增加和删除CAN通道和使对外的接口更统一,不会出现CAN1Init() CAN2Init(),个人习惯问题。
CAN队列可参考:CAN队列


【STM32F7xx —— CAN通信】

    推荐阅读