freertos学习笔记7--个人自用-第11章 队列(queue) 2 中断队列

 关联文章

 FreeRTOS第11章 队列(Queue)学习大纲https://blog.youkuaiyun.com/SeventeenChen/article/details/155699651?sharetype=blogdetail&sharerId=155699651&sharerefer=PC&sharesource=SeventeenChen&spm=1011.2480.3001.8118

 

11.4中断中的读写队列

1. 中断写队列 (xQueueSendFromISR)

这是最常见的场景:硬件产生数据 -> 中断接收 -> 丢进队列 -> 任务处理。 比如:串口收到一个字节、ADC 转换完成、GPIO 捕获到脉冲。

核心代码模板,请务必背下这三步走:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 1. 定义一个变量,用于标记是否需要切换任务
    // 默认是 pdFALSE(不需要切换)
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint8_t rx_data = (uint8_t)(huart->Instance->DR & 0xFF);

    // 2. 使用 FromISR 函数发送
    // 参数3:传入变量的地址 (&xHigherPriorityTaskWoken)
    // 如果队列满了,它会直接返回 errQUEUE_FULL,不会阻塞
    xQueueSendFromISR(xQueue, &rx_data, &xHigherPriorityTaskWoken);

    // 3. 强制上下文切换 (退出中断后直接去跑被唤醒的高优先级任务)
    // 如果 xHigherPriorityTaskWoken 变成了 pdTRUE,这就话会让 CPU “插队”
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

2. 中断读队列 (xQueueReceiveFromISR)

这个场景比较少见,但也有用。 通常用于:硬件需要数据 -> 中断触发 -> 从队列拿数据 -> 喂给硬件。 比如:DAC 输出(定时器中断每隔一段时间从队列拿一个点输出波形)、串口发送中断(发完一个字节,从队列拿下一个接着发)。

void TIM3_IRQHandler(void)
{
    // ... 清除中断标志位等 ...

    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint16_t dac_value;

    // 尝试从队列读数据
    if (xQueueReceiveFromISR(xDACQueue, &dac_value, &xHigherPriorityTaskWoken) == pdPASS) {
        // 读到了,写入硬件寄存器
        HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dac_value);
    } else {
        // 队列空了(波形发完了),可能需要停止定时器或输出默认值
    }

    // 同样需要进行上下文切换检查
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

3. 重要补充与避坑指南 (进阶)

除了基本的读写,在中断中使用队列还有以下几个极其重要的补充点:

11.5关于中断写队列 例子

这里为你提供 3 个最经典、最实用xQueueSendFromISR 使用场景示例。

这三个例子涵盖了:简单数据(按键)外设通信(串口) 以及 结构体打包(定时器采样)。代码均基于 STM32 HAL 库风格,因为这是最常见的开发环境。

场景一:GPIO 外部中断(按键检测)

场景描述: 当用户按下按键时,触发外部中断。中断服务函数立即把“哪个按键被按下了(按键ID)”发送给任务去处理(如播放按键音、切换菜单)。

// 假设这是一个简单的 uint8_t 类型的队列,存按键 ID
extern QueueHandle_t xKeyQueue;

// STM32 的 GPIO 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    // 1. 定义唤醒标志 (必须初始化为 pdFALSE)
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint8_t key_id = 0;

    // 2. 判断是哪个引脚触发的中断
    if (GPIO_Pin == GPIO_PIN_0) {
        key_id = 1; // 1号键
    } else if (GPIO_Pin == GPIO_PIN_13) {
        key_id = 2; // 2号键
    }

    if (key_id != 0) {
        // 3. 【关键】发送到队列
        // 即使队列满了,这里也不会卡死,直接返回 errQUEUE_FULL
        xQueueSendFromISR(xKeyQueue, &key_id, &xHigherPriorityTaskWoken);
    }

    // 4. 【关键】强制上下文切换
    // 如果刚才的发送唤醒了一个高优先级的"按键处理任务"
    // CPU 退出中断后,会直接跳去运行那个任务,而不是回到之前的低优先级任务
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

场景二:串口接收中断(字节流接收)

场景描述: 这是最常用的场景。STM32 串口每收到一个字节,就触发一次中断。我们需要把这个字节丢进队列,让后台任务去解析指令(比如遥控小车的前进后退)。

extern QueueHandle_t xUartRxQueue;
extern UART_HandleTypeDef huart1;

// 串口接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 只处理串口 1
    if (huart->Instance == USART1) {
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        
        // 获取刚才收到的数据 (假设存在寄存器或 DR 中)
        // HAL库通常需要你先开启接收,这里简化演示拿到数据的情况
        uint8_t received_char = (uint8_t)(huart->Instance->DR & 0xFF); 

        // 【关键】发送字符到队列
        xQueueSendFromISR(xUartRxQueue, &received_char, &xHigherPriorityTaskWoken);

        // 如果需要继续接收,通常这里要重新开启中断接收
        // HAL_UART_Receive_IT(&huart1, ...);

        // 【关键】切换调度
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

注意:如果波特率非常高(如 921600),这种“收一个字节进一次中断发一次队列”的方式效率太低,建议配合 DMA + 空闲中断 + 队列(传缓冲区指针)使用。


场景三:定时器中断(发送结构体/传感器数据)

场景描述: 平衡小车中,需要每隔 5ms 精确采集一次 MPU6050 数据或编码器速度。我们在定时器中断里读取数据,打包成结构体,发给 PID 计算任务。

// 1. 定义数据包结构体
typedef struct {
    float angle;
    float speed;
    uint32_t timestamp;
} CarState_t;

extern QueueHandle_t xCarStateQueue;

// 定时器更新中断 (假设 5ms 触发一次)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3) {
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        CarState_t current_state;

        // --- 模拟读取硬件数据 ---
        current_state.angle = MPU6050_GetAngle(); // 读角度
        current_state.speed = Encoder_GetSpeed(); // 读速度
        current_state.timestamp = xTaskGetTickCountFromISR(); // 获取当前时刻

        // 【关键】发送结构体
        // FreeRTOS 会把 current_state 的内容完整的 memcpy 到队列里
        if (xQueueSendFromISR(xCarStateQueue, &current_state, &xHigherPriorityTaskWoken) != pdPASS) {
            // 队列满了!说明 PID 任务处理太慢了,跟不上 5ms 的节奏
            // 这里可以做一个错误计数 Error_Count++;
        }

        // 【关键】立刻去唤醒等待数据的 PID 任务
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

进阶:紧急插队 (xQueueSendToFrontFromISR)

场景描述: 小车正在运行,突然“倾角保护中断”触发了(车倒了)。这时候不管队列里还有多少个“前进”指令排队,必须立刻发送一个“停机”指令,并让接收任务第一个读到它。

void Emergency_Button_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    // 定义一个特殊的停机指令
    Command_t stop_cmd = { .type = CMD_STOP, .value = 0 };

    // 【关键】使用 SendToFront (插队到队头)
    xQueueSendToFrontFromISR(xCmdQueue, &stop_cmd, &xHigherPriorityTaskWoken);

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

总结:通用模板

不管什么中断,套路永远是这三步:

  1. 定义标志BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  2. 发送数据xQueueSendFromISR(..., &xHigherPriorityTaskWoken);

  3. 切换调度portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值