在STM32F7上实现CAN总线收发队列
下面我将提供一个完整的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或扩展ID
uint8_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);
// 启动CAN
HAL_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总线通信框架,您可以根据具体应用需求进行调整和扩展。