我有一个 ESP32/Arduino 应用程序,可以驱动 LED 矩阵等。它使用SimpleFSM来实现处理用户交互、传感器输入等的状态机。有时LED矩阵显示静态“图标”,有时显示动画。这两个用例在代码级别概念上有所不同:
FreeRTOS 实现渲染部分的惯用方法是什么?
最初,我想在需要渲染新内容时开始一项新任务,但这只会带来痛苦。在新任务开始之前,必须先终止旧/上一个任务。此外,任务应该是长期运行的,最好是无限的。
因此,FreeRTOS 的方式似乎是启动一个无限运行的渲染任务。当状态发生变化时,主任务会告诉它停止当前正在做的事情并开始做其他事情。如何交换任务的“工作单元”?当任务渲染静态图标时,如何防止任务完成 - 除了一遍又一遍地渲染它?渲染任务是否应该使用它自己的小状态机?
渲染任务是否应该使用它自己的小状态机?
是的,[单个]渲染任务应该保持其自己的状态。
渲染任务应该有一个轮询的请求/工作队列。
主要任务是进行用户交互。如果它决定更改显示的内容,则会将请求排队到渲染任务。
渲染任务轮询请求队列。如果有新请求到达,它将启动该操作。否则,它将继续处理当前/现有的工作请求。
这是一些伪代码。将队列相关函数替换为 FreeRTOS 队列等效函数:
typedef enum {
ACTION_IDLE,
ACTION_ZERO,
ACTION_ONE,
ACTION_ANIM_CIRCLE,
ACTION_ANIM_SQUARE,
ACTION_ANIM_TRIANGLE,
} action_t;
typedef struct {
action_t req_action;
int req_state;
// ...
} request_t;
void
render_task(void)
{
request_t *reqcur = NULL;
request_t *reqnew = NULL;
while (1) {
// new work to do?
reqnew = dequeue_request_nowait(&render_task_work);
// yes, release old and setup new work
if (reqnew != NULL) {
// release old request block to free queue
if (reqcur != NULL)
enqueue_request(&render_task_free,reqcur);
// setup new request
reqcur = reqnew;
init = 1;
}
else
init = 0;
// handle current work
if (reqcur != NULL) {
switch (reqcur->req_action) {
case ACTION_IDLE:
break;
case ACTION_ZERO:
if (init)
display_zeros();
reqcur->req_action = ACTION_IDLE;
break;
case ACTION_ONE:
if (init)
display_ones();
reqcur->req_action = ACTION_IDLE;
break;
case ACTION_ANIM_CIRCLE:
case ACTION_ANIM_SQUARE:
case ACTION_ANIM_TRIANGLE:
if (init)
reset_animation(reqcur);
display_animation_frame(reqcur);
advance_animation_state(reqcur);
break;
}
}
// prevent infinitely fast rendering ...
// NOTE: this could be done via a timeout on a _blocking_ dequeue
// operation above
wait_for_frame_start(timeout);
}
}
上面是工作线程的线程池的简单版本。有关一些背景知识,请参阅我的答案:在 C 中,将数据存储在线程中的局部变量中是否类似于创建本地副本? AKA 这个线程池同步有意义吗?