STM32 HAL FreeRTOS CAN队列发送

STM32 HAL库CAN使用队列发送

前言

为了解决STM32CAN同时发送多条数据可能出现丢包的现象,故采取队列形式发送CAN数据;

一、STM32CubeMX配置

1.FreeRTOS新建发送队列

在这里插入图片描述

2.创建一个专门CAN出队的任务

在这里插入图片描述

二、Keil5

1.定义发送消息结构体

// 定义CAN消息结构体
	typedef struct
	{
		uint32_t id;
		uint8_t data[8];
		uint8_t len;
		uint8_t isExtended; // 标记是否为扩展帧
	} CanMessage_t;

2.CAN消息入队

extern osMessageQueueId_t CanTxQueueHandle;
/**
 * @brief  CAN数据入队
 * @param  can 		CAN句柄
 * @param  id 				发送ID
 * @param  *data 			发送数据
 * @param  len				数据长度
 * @param  isExtended 数据类型:CAN_ID_EXT   CAN_ID_STD
 * @retval void
 */
void SendCanMessage(uint32_t id, uint8_t *data, uint8_t len, uint8_t isExtended)
{
	CanMessage_t txMessage;
	txMessage.id = id;
	txMessage.len = len;
	txMessage.isExtended = isExtended;
	memcpy(txMessage.data, data, len);
	osMessageQueuePut(CanTxQueueHandle, &txMessage, NULL, 0);
}

3.CAN消息出队

/**
 * @brief  CAN2数据出队
 * @retval void
 */
CanMessage_t txMessage;
void DeQueueCanMessage(void)
{
	if (osMessageQueueGet(CanTxQueueHandle, &txMessage, NULL, 0) == osOK)
	{
		// 从队列中接收消息
		CAN_TxHeaderTypeDef txHeader;
		txHeader.ExtId = txMessage.isExtended ? txMessage.id : 0; // 如果是标准帧,则扩展帧ID为0
		txHeader.RTR = CAN_RTR_DATA;
		txHeader.IDE = txMessage.isExtended ? CAN_ID_EXT : CAN_ID_STD; // 根据isExtended标志设置帧格式
		txHeader.DLC = txMessage.len;
		txHeader.TransmitGlobalTime = DISABLE;
		uint32_t TxMailbox;
		while (HAL_CAN_AddTxMessage(&hcan2, &txHeader, txMessage.data, &TxMailbox) != HAL_OK);
		// 等待发送成功
		while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan2) != 3);
	}
}

4.函数声明

// CAN2数据入队
void SendCanMessage(uint32_t id, uint8_t *data, uint8_t len, uint8_t isExtended);
// CAN2数据出队
void DeQueueCanMessage(void);

5.Freertos调用

/* USER CODE BEGIN Header_StartTask02 */
/**
 * @brief Function implementing the myTask02 thread.
 * @param argument: Not used
 * @retval None
 */
/* USER CODE END Header_StartTask02 */
void StartTask02(void *argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for (;;)
  {
    /* CAN2发送消息出队 */
    DeQueueCanMessage();
    osDelay(3);
  }
  /* USER CODE END StartTask02 */
}

3.CAN发送入队示例

//扩展帧发送
uint8_t CANSendData[8];
SendCanMessage(0x1122220A, CANSendData, 8, CAN_ID_EXT);
//标准帧发送修改最后一个标志位即可
<think>我们正在讨论的是CAN通信中`HAL_CAN_AddTxMessage`函数的行为。用户想知道在调用`__HAL_CAN_ENABLE_IT`之后,`HAL_CAN_AddTxMessage`是否会变成非阻塞模式。 首先,我们需要明确: 1. `HAL_CAN_AddTxMessage`函数本身的设计是阻塞还是非阻塞? 2. `__HAL_CAN_ENABLE_IT`宏的作用是什么? 3. 两者结合后对函数行为的影响。 根据STM32 HAL库的设计: - `HAL_CAN_AddTxMessage`函数默认是阻塞的,它会等待直到有一个空的邮箱(mailbox)可用,然后将消息放入邮箱。如果所有邮箱都忙,它会等待直到超时(超时时间由`hcan->Init.Timeout`设定)[^4]。 - `__HAL_CAN_ENABLE_IT`是一个宏,用于使能特定的CAN中断(例如发送邮箱空中断、接收FIFO非空中断等)。它只是设置CAN中断使能寄存器,并不会改变`HAL_CAN_AddTxMessage`的阻塞行为。 但是,如果我们使能了发送中断(例如`CAN_IT_TX_MAILBOX_EMPTY`),那么当发送邮箱变为空(即发送完成)时,会产生中断。在中断服务程序中,我们可以处理发送完成事件,例如启动下一次发送。但这并不直接影响`HAL_CAN_AddTxMessage`的阻塞特性。 那么,如何让`HAL_CAN_AddTxMessage`变成非阻塞呢? 实际上,`HAL_CAN_AddTxMessage`函数内部有一个等待邮箱可用的循环。如果邮箱不可用,它会一直等待(阻塞)直到超时。因此,即使使能了中断,这个函数仍然是阻塞的,只不过阻塞的时间可能很短(如果邮箱很快释放)或者达到超时时间(如果邮箱一直忙)。 然而,我们可以通过以下方式实现非阻塞发送: 1. 在调用`HAL_CAN_AddTxMessage`之前,先检查是否有空的邮箱。我们可以使用`HAL_CAN_GetTxMailboxesFreeLevel`函数来获取当前可用的邮箱数量。如果大于0,则调用`HAL_CAN_AddTxMessage`,此时它会立即返回(因为有空邮箱,所以不会阻塞)。如果没有空邮箱,我们可以选择稍后再试(比如在中断回调中重试)或者丢弃消息。 2. 使用中断方式:当发送完成(邮箱空)时,在发送完成中断回调函数中启动下一次发送。这样,我们可以将消息放入一个队列,然后在中断中从队列取出消息发送。这样,主程序只需将消息加入队列即可,不会阻塞。 因此,直接调用`__HAL_CAN_ENABLE_IT`并不会改变`HAL_CAN_AddTxMessage`的阻塞行为。但是,我们可以通过中断和队列机制来实现非阻塞的发送流程。 总结: - `__HAL_CAN_ENABLE_IT`只是使能中断,不会改变`HAL_CAN_AddTxMessage`的阻塞特性。 - 默认情况下,`HAL_CAN_AddTxMessage`是阻塞的(等待邮箱可用,直到超时)。 - 要实现非阻塞发送,需要结合中断和消息队列,避免在邮箱不可用时等待。 参考引用[4]中提到了使用`HAL_CAN_ActivateNotification`来启用中断。实际上,HAL库推荐使用`HAL_CAN_ActivateNotification`而不是直接使用`__HAL_CAN_ENABLE_IT`,因为前者会设置回调函数,并且管理中断使能状态。 步骤示例: 1. 初始化CAN并配置过滤器(如引用[4]所示)。 2. 激活通知(中断): ```c HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY); ``` 3. 在需要发送消息时,先检查是否有空闲邮箱: ```c if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) > 0) { // 有空闲邮箱,可以立即发送 if (HAL_CAN_AddTxMessage(&hcan, &tx_header, data, &mailbox) != HAL_OK) { // 发送失败处理 } } else { // 没有空闲邮箱,将消息加入队列,等待发送完成中断 enqueue(&tx_queue, data); // 自定义队列操作 } ``` 4. 在发送完成中断回调函数中,从队列取出消息并发送: ```c void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) { // 检查队列中是否有待发送消息 if (!is_empty(&tx_queue)) { uint8_t *data = dequeue(&tx_queue); // 发送队列中的消息 HAL_CAN_AddTxMessage(hcan, &tx_header, data, &mailbox); } } ``` 这样,我们就实现了非阻塞的发送流程。 注意:`HAL_CAN_AddTxMessage`函数本身仍然可能阻塞,但由于我们在有空闲邮箱时才调用它,所以它不会等待(立即返回),从而实现了非阻塞的效果。 因此,回答用户的问题:调用`__HAL_CAN_ENABLE_IT`并不会让`HAL_CAN_AddTxMessage`变成非阻塞模式,但通过结合中断和队列,我们可以实现非阻塞的发送流程。 相关问题: 1. 如何配置STM32CAN中断以实现非阻塞通信? 2. 在STM32 HAL库中,如何正确使用CAN发送完成中断? 3. STM32 CAN通信中,如何避免发送函数阻塞主程序?</think>在STM32中调用`__HAL_CAN_ENABLE_IT`**不会**直接将`HAL_CAN_AddTxMessage`变为非阻塞模式。这两个函数的作用是分离的,需要结合使用才能实现非阻塞通信: ### 关键机制解析 1. **`HAL_CAN_AddTxMessage`的默认行为** 该函数**本质是阻塞式**的: ```c HAL_StatusTypeDef HAL_CAN_AddTxMessage( CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox ) ``` - 内部通过轮询等待空闲邮箱,直到超时(默认时间由`hcan->Init.TimeOut`定义)[^4] - 在此期间会占用CPU,导致其他外设(如UART)被阻塞 2. **`__HAL_CAN_ENABLE_IT`的作用** ```c // 示例:使能发送邮箱空中断 __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY); ``` - 仅**配置硬件中断使能位**,不改变函数行为 - 需配合中断回调函数实现异步操作 - 不会自动解除`HAL_CAN_AddTxMessage`的阻塞特性 --- ### 实现非阻塞CAN发送的步骤 #### 1. 启用中断并注册回调 ```c // 使能发送完成中断 __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY); // 注册中断回调函数 void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) { // 此处处理发送完成事件 // 例如:释放资源、启动下一次发送等 } ``` #### 2. 修改发送流程 ```c // 非阻塞发送示例 uint32_t mailbox; if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) > 0) { // 检查空闲邮箱 HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &mailbox); // 此处立即返回,不等待发送完成 } else { // 邮箱全忙,缓存数据稍后发送 } ``` #### 3. 完整工作流程 ```mermaid sequenceDiagram participant App as 应用程序 participant CAN as CAN控制器 participant ISR as 中断服务 App->>CAN: 1. 调用HAL_CAN_AddTxMessage CAN-->>App: 2. 立即返回HAL_OK (有空闲邮箱时) CAN->>ISR: 3. 硬件触发发送完成中断 ISR->>App: 4. 执行TxMailboxXCompleteCallback ``` --- ### 阻塞 vs 非阻塞对比 | 特性 | 阻塞模式 | 非阻塞模式 | |------|----------|------------| | 调用`HAL_CAN_AddTxMessage` | 等待直到发送完成/超时 | 立即返回(有空闲邮箱时) | | CPU占用 | 高(轮询等待) | 低(中断驱动) | | 需要`__HAL_CAN_ENABLE_IT` | 否 | 是(必须配合中断) | | 多外设协同 | 易导致阻塞 | 可与其他中断并行工作 | | 典型应用场景 | 单任务简单系统 | 多任务/实时系统 | > **关键结论**:单纯调用`__HAL_CAN_ENABLE_IT`不会改变`HAL_CAN_AddTxMessage`的阻塞特性,必须结合**中断回调机制**和**邮箱状态检查**才能实现真正的非阻塞操作[^4]。 --- ### 验证非阻塞是否生效的方法 1. **在UART接收中断中翻转GPIO** ```c void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 收到UART数据时翻转LED } ``` 2. **同时进行CAN密集发送** ```c while(1) { HAL_CAN_AddTxMessage(...); // 连续发送 HAL_Delay(1); } ``` 3. **观察结果**: - 阻塞模式下LED会停止闪烁(UART中断被阻塞) - 正确实现非阻塞时LED保持稳定闪烁 --- ### 相关优化建议 1. **使用DMA传输** 结合`HAL_CAN_Transmit_DMA()`进一步降低CPU负载 2. **设置合理中断优先级** ```c HAL_NVIC_SetPriority(CAN1_TX_IRQn, 2, 0); // 低于UART中断优先级 HAL_NVIC_EnableIRQ(CAN1_TX_IRQn); ``` 3. **邮箱状态监控** 使用`HAL_CAN_GetTxMailboxesFreeLevel()`避免在邮箱全忙时调用发送函数 通过上述方法,可解决CAN通信阻塞其他外设(如UART)的问题,实现高效的多外设协同工作[^1][^4]。 --- ### 相关问题 1. 如何在STM32中配置CAN和UART的DMA传输以避免CPU阻塞? 2. STM32CAN发送中断优先级应如何设置才能不影响关键外设? 3. 如何检测STM32 CAN总线的实时负载率? 4. FreeRTOS下如何优化CAN和UART通信的任务调度?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值