下面我将提供一个完整的STM32F7 CAN总线通信实现方案,包含中断驱动的收发队列管理。
1. CAN总线配置与队列定义
can_bus.h
#ifndef __CAN_BUS_H
#define __CAN_BUS_H#include "stm32f7xx_hal.h"
#include "queue.h"// CAN消息结构体
typedef struct {uint32_t id; // 标准ID或扩展IDuint8_t data[8]; // 数据域uint8_t len; // 数据长度uint8_t format; // 0-标准帧, 1-扩展帧uint8_t type; // 0-数据帧, 1-远程帧
} CAN_Msg_t;// CAN队列配置
#define CAN_TX_QUEUE_SIZE 16
#define CAN_RX_QUEUE_SIZE 32// CAN总线初始化
void CAN_Init(CAN_HandleTypeDef *hcan);// 消息发送/接收接口
bool CAN_SendMsg(CAN_HandleTypeDef *hcan, CAN_Msg_t *msg);
bool CAN_GetMsg(CAN_Msg_t *msg);// 中断处理函数
void CAN_IRQHandler(CAN_HandleTypeDef *hcan);#endif
can_bus.c
#include "can_bus.h"// CAN发送接收队列
static Queue_t can_tx_queue;
static Queue_t can_rx_queue;// CAN初始化
void CAN_Init(CAN_HandleTypeDef *hcan) {// 初始化队列Queue_Init(&can_tx_queue);Queue_Init(&can_rx_queue);// CAN过滤器配置 (示例配置)CAN_FilterTypeDef filter;filter.FilterBank = 0;filter.FilterMode = CAN_FILTERMODE_IDMASK;filter.FilterScale = CAN_FILTERSCALE_32BIT;filter.FilterIdHigh = 0x0000;filter.FilterIdLow = 0x0000;filter.FilterMaskIdHigh = 0x0000;filter.FilterMaskIdLow = 0x0000;filter.FilterFIFOAssignment = CAN_RX_FIFO0;filter.FilterActivation = ENABLE;filter.SlaveStartFilterBank = 14;HAL_CAN_ConfigFilter(hcan, &filter);// 启动CANHAL_CAN_Start(hcan);// 激活CAN中断HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY);
}// 发送CAN消息 (非阻塞)
bool CAN_SendMsg(CAN_HandleTypeDef *hcan, CAN_Msg_t *msg) {// 尝试直接发送if (HAL_CAN_GetTxMailboxesFreeLevel(hcan) > 0) {CAN_TxHeaderTypeDef tx_header;tx_header.StdId = msg->format ? 0 : msg->id;tx_header.ExtId = msg->format ? msg->id : 0;tx_header.IDE = msg->format ? CAN_ID_EXT : CAN_ID_STD;tx_header.RTR = msg->type ? CAN_RTR_REMOTE : CAN_RTR_DATA;tx_header.DLC = msg->len;tx_header.TransmitGlobalTime = DISABLE;uint32_t mailbox;if (HAL_CAN_AddTxMessage(hcan, &tx_header, msg->data, &mailbox) == HAL_OK) {return true;}}// 发送邮箱已满,放入队列return Queue_SendToBackFromISR(&can_tx_queue, msg);
}// 接收CAN消息 (非阻塞)
bool CAN_GetMsg(CAN_Msg_t *msg) {return Queue_Receive(&can_rx_queue, msg);
}// CAN中断处理
void CAN_IRQHandler(CAN_HandleTypeDef *hcan) {// RX FIFO0中断处理if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_FOV0) || __HAL_CAN_GET_FLAG(hcan, CAN_FLAG_FF0)) {__HAL_CAN_CLEAR_FLAG(hcan, CAN_FLAG_FOV0 | CAN_FLAG_FF0);}if (__HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_RX_FIFO0_MSG_PENDING)) {CAN_RxHeaderTypeDef rx_header;CAN_Msg_t msg;if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, msg.data) == HAL_OK) {msg.id = rx_header.IDE == CAN_ID_STD ? rx_header.StdId : rx_header.ExtId;msg.len = rx_header.DLC;msg.format = rx_header.IDE == CAN_ID_EXT;msg.type = rx_header.RTR == CAN_RTR_REMOTE;Queue_SendToBackFromISR(&can_rx_queue, &msg);}}// TX邮箱空中断处理if (__HAL_CAN_GET_IT_SOURCE(hcan, CAN_IT_TX_MAILBOX_EMPTY)) {// 检查并发送队列中的消息while (HAL_CAN_GetTxMailboxesFreeLevel(hcan) > 0) {CAN_Msg_t msg;if (Queue_Receive(&can_tx_queue, &msg)) {CAN_SendMsg(hcan, &msg); // 递归调用,但不会无限递归} else {break;}}}
}
2. 主程序集成示例
main.c
#include "stm32f7xx_hal.h"
#include "can_bus.h"CAN_HandleTypeDef hcan1;int main(void) {HAL_Init();SystemClock_Config();// CAN初始化hcan1.Instance = CAN1;hcan1.Init.Prescaler = 6;hcan1.Init.Mode = CAN_MODE_NORMAL;hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan1.Init.TimeSeg1 = CAN_BS1_13TQ;hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;hcan1.Init.TimeTriggeredMode = DISABLE;hcan1.Init.AutoBusOff = DISABLE;hcan1.Init.AutoWakeUp = DISABLE;hcan1.Init.AutoRetransmission = ENABLE;hcan1.Init.ReceiveFifoLocked = DISABLE;hcan1.Init.TransmitFifoPriority = DISABLE;HAL_CAN_Init(&hcan1);CAN_Init(&hcan1);while (1) {// 接收处理CAN_Msg_t rx_msg;if (CAN_GetMsg(&rx_msg)) {// 处理接收到的CAN消息// ...}// 示例:周期发送测试消息static uint32_t last_tick = 0;if (HAL_GetTick() - last_tick > 1000) {last_tick = HAL_GetTick();CAN_Msg_t tx_msg;tx_msg.id = 0x123;tx_msg.len = 8;tx_msg.format = 0; // 标准帧tx_msg.type = 0; // 数据帧for (int i = 0; i < 8; i++) {tx_msg.data[i] = i;}CAN_SendMsg(&hcan1, &tx_msg);}// 其他任务...}
}// CAN中断服务函数
void CAN1_RX0_IRQHandler(void) {CAN_IRQHandler(&hcan1);
}void CAN1_TX_IRQHandler(void) {CAN_IRQHandler(&hcan1);
}
3. 关键实现细节说明
双队列设计:
发送队列(can_tx_queue):缓存待发送消息
接收队列(can_rx_queue):缓存接收到的消息
中断驱动机制:
接收中断:当FIFO收到消息时触发,将消息存入接收队列
发送中断:当发送邮箱空闲时触发,从发送队列取出消息发送
流量控制:
当CAN控制器发送邮箱满时,消息暂存到发送队列
当接收队列满时,新消息将被丢弃(可根据需求修改)
性能优化:
直接使用HAL库提供的邮箱状态检查
在中断中只做必要的最小操作
扩展性考虑:
支持标准帧和扩展帧
支持数据帧和远程帧
可轻松扩展为多CAN总线支持
4. 实际应用建议
- 错误处理增强:
// 在CAN_Init中添加错误中断使能
HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING |CAN_IT_TX_MAILBOX_EMPTY |CAN_IT_ERROR |CAN_IT_BUSOFF |CAN_IT_LAST_ERROR_CODE);
- 诊断功能:
// 添加CAN状态监控
typedef struct {uint32_t tx_count;uint32_t rx_count;uint32_t tx_queue_full;uint32_t rx_queue_full;uint32_t errors;
} CAN_Stats_t;// 在中断处理中更新统计信息
- 多帧传输支持:
// 对于长消息,可以实现分段/重组逻辑
#define CAN_MTU 8 // CAN最大传输单元typedef struct {uint32_t id;uint16_t total_len;uint16_t offset;uint8_t data[64]; // 假设最大支持64字节数据
} CAN_LongMsg_t;
这个实现提供了完整的CAN总线通信框架,您可以根据具体应用需求进行调整和扩展。