【问题标题】:How to handle timeout in FreeRTOS - wake up task from interrupt before vTaskDelay expires?如何处理 FreeRTOS 中的超时 - 在 vTaskDelay 到期之前从中断中唤醒任务?
【发布时间】:2022-01-26 19:51:27
【问题描述】:

我可以在 vTaskDelay 过期之前唤醒任务吗?

我有这样的代码:

在任务(hnd_uart_task)代码中:

transmit_frame();
vTaskDelay(100);  // task should wait 100 ticks or be woken up by uart ISR
parse_response();

UART 中断:

// if byte was received
BaseType_t xYieldRequired = xTaskResumeFromISR(hnd_uart_task);
portYIELD_FROM_ISR(xYieldRequired);

【问题讨论】:

    标签: task delay interrupt freertos


    【解决方案1】:

    您可以使用带超时的任务通知来代替vTaskDelay()

    USART 中断:

    // if byte was received
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    vTaskNotifyGiveFromISR(hnd_uart_task, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    

    任务代码:

    transmit_frame();
    ulTaskNotifyTake(pdTRUE, 100);
    parse_response();
    

    ulTaskNotifyTake(pdTRUE, 100) 在收到来自 ISR 的任务通知或 100 个滴答超时时间过后返回。

    但正如@Jacek Ślimok 指出的那样,逐字节 解析可能不是一个好主意。确切的方法取决于使用的协议。但一般来说,您设置 DMA 或中断以用传入字节填充接收缓冲区。例如,在解析 Modbus 帧时,可以使用空闲线路检测硬件中断,并通知解析 RX 缓冲区的任务。

    【讨论】:

    • 谢谢。我将尝试使用 ulTask​​NotifyTake(...)。我正在研究 Modbus,我已经完成了解析(我正在将代码从 PIC18 移植到 STM32F7)。对于字符超时,我使用的是内置 USART RTO 中断,但不幸的是,这个超时在字节之间起作用,而不是在根本没有接收到字节时。
    • @Kamil ,尽管 Modbus 规范允许字节之间存在一些间隙(是否为 1.5 个字符时间?),但我忽略了字节之间的这些间隙。我从来没有遇到过在字节之间设置间隙的串行发送器(免责声明:我的现场经验是有限的)。所以我实现了 RTO 中断来检测帧之间的 3.5 个字符时间间隔。一些 STM32 部件(如 F407)甚至没有 RTO 功能,我对它们使用空闲检测。 1 个字符时间超出规格,但似乎可以正常工作。
    • 我试过了,它奏效了。我有非常简单的 modbus 主传输实现。我知道某些设备中没有实现 RTO。根据 1.5 char静音-我也从未实现过这个。 Modbus 在传输方面是一个有点尴尬的协议。
    【解决方案2】:

    不,因为这不是 vTaskDelay 的用途。

    与您最接近的解决方案是创建一个信号量,您尝试以 100 毫秒的延迟在任务中获取该信号量,并从 ISR 提供​​该信号量。这样,任务将阻塞最多 100 毫秒,等待给出信号量,之后它将解除阻塞并继续执行。如果早点给出,它会早点解除阻塞,这就是我假设你想要的。

    但是,根据您所写的内容,我假设您想要实现以下目标:

    • 通过 UART 发送一些数据
    • 等待回复
    • 收到响应后,立即处理响应

    在这种情况下,在同一个任务中同时进行阻塞和解析会很困难(你真的不想在 ISR 中进行任何类型的解析)。因此,我建议以下两个任务的“布局”,一个中断和两个任务之间的一个共享smepahore:

    1. “高级”任务(我们称之为ApplicationTask)可以执行以下操作:
    • 构造整个帧并请求通过 UART 发送它们(将它们添加到某种队列中)。这种“构建整个帧”并将它们发送到其他任务通常会被包装到一些函数中。
    • 将阻止等待响应
    • 将接收已解析的数据(包含已解析数据的全帧或对象/结构)
    1. “字节级”任务(我们称之为ByteTask),它可以执行以下操作:
    • 有一个传输数据队列(帧队列或原始字节队列)
    • 有一个接收数据的队列
    • 将“待传输数据”队列中的数据“推送”到 UART 中
    • 解析出现在“已接收数据”队列中的 UART 数据并提供信号量以解除对 ApplicationTask 的阻塞
    1. UART 中断:
    • 只传输 ByteTask 要求传输的数据
    • 将接收到的数据推送到ByteTask接收队列
    1. ApplicationTaskByteTask 之间的共享信号量:
    • 每当 ApplicationTask 想要“等待接收响应”时,它都会尝试获取此信号量。最大阻塞时间可以用作“响应接收超时”。
    • 每当 ByteTask 接收并解析足够的数据以决定“是的,这是一个完整的响应”时,它就会给出这个信号量。

    以上是一个超级简单的示例,它应该很容易在您开发应用程序时扩展到更多任务。你可以得到比这更花哨的东西(例如 ByteTask 同时处理多个 UART,有一个用于阻塞多个任务的信号量池,做一些更复杂的消息调度等),但是希望上面的示例可以让您更好地了解如何处理此类问题。

    【讨论】:

    • 感谢您的详细解释。我想知道你为什么不像@Tagli 那样建议ulTaskNotifyTake(pdTRUE, 100)。我认为这将使用更少的资源。
    猜你喜欢
    • 2014-12-03
    • 1970-01-01
    • 2013-02-10
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多