微控制器中的小型多任务实验

在先前的一篇文章中,作者试图指出,在对微控制器进行编程时,如果使用实时操作系统的次数过多,并且所有必需操作的综合循环次数太少,那么简单的任务切换将非常有用(他说,就像Count de La Fer)。 更确切地说,不是太少,而是太困惑了。


在随后的注释中, 计划使用基于环形缓冲区(FIFO)的队列和为此专门分配的单独任务简化对多个任务共享的资源的访问。 将不相关的动作分散在不同的任务中,我们有权期望使用更可见的代码。 如果同时获得一些便利和简单,那为什么不试试呢?


显然,微控制器并非旨在解决用户的任何可能任务。 然后,也许,在许多情况下,这样的任务切换器将被证明是足够的。 简而言之,一个小的实验不太可能会受到伤害。 因此,为了不被毫无根据,你谦虚的仆人决定写点东西并测试他的涂鸦。


我必须说,在微控制器中,与通用计算机相比,将时间视为重要和固定的要求更为普遍。 在第一种情况下,超出框架等同于不可操作性;在第二种情况下,这只会导致等待时间的增加,如果神经井然有序,这是完全可以接受的。 甚至有两个术语“软实时”和“硬实时”。


让我提醒您,我们在谈论带有Cortex-M3,4,7内核的控制器。 今天是一个非常普通的家庭。 在以下示例中,我们使用了STM32F3DISCOVERY板的一部分STM32F303微控制器。


该开关是单个汇编程序文件。
汇编程序不怕作者,但是相反,它激发了人们希望达到最大速度的希望。


最初,计划了开关操作的最简单逻辑,如图1所示,其中包含八个任务。



在此方案中,任务将其时间部分一一占用,并且只能给出剩余的滴答声,如果需要,可以跳过一些滴答声。 这种逻辑被证明是好的,因为可以使量子尺寸变小。 而这恰恰是为了不紧急提出刚刚发生中断的任务,并提出然后降低其优先级的要求。 刚收到的数据包将静静等待200-300微秒,直到其任务收到它的滴答声为止。 而且,如果我们有一个以216 MHz的频率运行的Cortex-M7,那么20毫秒一刻是很合理的,因为切换所需时间不到半微秒。 上面示例中的任何任务都不会迟于140微秒。


但是,随着任务数量的增加,即使时间量的大小非常小,所需任务的活动开始的延迟也可能不再令人愉快。 基于此,并且还考虑到只有一小部分任务确实需要很强的实时性,因此决定略微修改开关的逻辑。 如图2所示。



现在,我们仅选择接收整个量子的任务的一部分,其余仅选择一个刻度,然后在游戏中轮流执行。 在这种情况下,初始化子例程接收一个输入参数,即位置编号,从该位置开始,所有任务的权限都会受到影响,并且将共享一个刻度。 同时,旧方案仍然可用,为此只需将参数值设置为零或任务总数即可。 交换成本仅通过一些汇编程序指令就增加了。


使用两个类似的方案来允许访问共享资源。 前一个注释中提到了第一个,它使用几个FIFO(或消息产生者数量的循环缓冲区)和一个单独的匹配任务。 它旨在与外界通信,不需要生成消息的任务的期望。 仅需要确保队列不拥挤。


第二种方案还使用单独的任务来允许访问,但是引入了期望,因为它在两个方向上都管理内部资源。 这些动作不能与时间联系在一起。 图3显示了第二个电路的组件。



其中的主要元素是根据所需任务数量的请求缓冲区和一个访问指示器。 此设计的操作非常简单。 左侧的任务将访问请求发送到专门为其分配的位置(例如,任务2将1写入请求2)​​。 任务-调度程序选择允许的人员,并将所选任务的编号写在解决标志中。 已获得许可的任务将执行其操作,并写入对请求访问结束的符号,值0xFF。 调度程序看到请求已清除,然后重置权限标志,重置先前的请求并重置来自另一个任务的请求。


此处可以查看IAR下的两个测试项目以及所用STM32F3DISCOVERY板的说明。 在第一个项目中,ATS303仅检查其性能并进行调试。 安装在板上的所有LED都很方便。 没有人受伤。


BTS303的第二稿测试了上述两个资源分配选项。 在其中,任务1和2生成操作员接收的测试消息。 为了与操作员进行通信,我必须添加一条带有TTL COM端口的围巾,如下图所示。



操作员使用终端仿真器。 我认为读者可以为软管的颜色辩解。 看起来像这样。



要开始整个系统的运行,在解决中断之前,需要在零任务main()的主体中进行准备,这些步骤如下所示。


void main_start_task_switcher(U8 border); U8 task_run_and_return_task_number((U32)t1_task); U8 task_run_and_return_task_number((U32)t2_task); U8 task_run_and_return_task_number((U32)t3_human_link); U8 task_run_and_return_task_number((U32)t4_human_answer); U8 task_run_and_return_task_number((U32)task_5); U8 task_run_and_return_task_number((U32)task_6); U8 task_run_and_return_task_number((U32)task_7); 

在这些行中,开关首先启动,然后依次启动其余的七个任务。


这是工作所需的最少通话次数。


  void task_wake_up_action(U8 taskNumber); 

此调用用于用户硬件计时器的中断中。 任务本身带来的挑战说明了一切。


  void release_me_and_set_sleep_steps(U32 ticks); U8 get_my_number(void); 

所有这些功能都在汇编开关文件中。 还有其他一些功能可用于测试,但不是必需的。


在BTS303项目中,任务3从外部接收操作员的命令,并向他发送来自任务4的答案。任务4从任务3接收操作员的命令,并以可能的答案执行命令。 任务3还从任务1和2接收消息,并通过UART将其发送到终端仿真器(例如,腻子)。


任务0(主要)执行一些辅助工作,例如,检查每个任务的堆叠区域中不受影响的单词数。 操作员可以请求此信息,并获得有关堆栈使用的想法。 最初,为每个任务分配512字节(128个字)的堆栈区域,并且有必要监视(至少在调试阶段)这些区域不会接近溢出。


任务5和6对一些常见的浮点变量进行计算。 为此,他们要求从任务7访问它。


在测试项目中可以看到另一个附加功能。 它的设计目的是使您可以在滴答数到期之后,而不是在经过指定的时间之后唤醒任务,它看起来像这样。


  void wake_me_up_after_milliSeconds(U32 timeMS); 

为了实现它,还需要一个额外的硬件计时器,该计时器也在测试用例中实现。


如您所见,所有必要呼叫的列表都位于一页上。

Source: https://habr.com/ru/post/zh-CN461121/


All Articles