从多任务说起.
前序日子,都在进行核心代码的开发。终于最近进入了 UI 界面的开发了。
虽然叫 UI, 但是其实并没有窗口。也没有命令行。
而是几个 LED 灯 + 一个旋钮 + 一个红外遥控器。
这些也算是能和用户交互了,所以,算UI没什么问题吧?
一旦进入 UI 的开发领域,马上就遇到单片机的一个掣肘了: 没有操作系统。
为什么UI需要操作系统呢?
因为 UI 是由多个子任务构成的。
libvfd 里, SVPWM 的生成是受时钟中断驱动的。
但是,V/F 控制,则是一个独立的循环。V/F 控制,收到外部的控制请求后, 会根据电机的加减速特性,来逐步的提高 svpwm 模块的电压和频率。
还有,遥控接收,编码器处理。这些代码,都需要自己的一个控制循环。
也就是意味着,这个变频器的内部,有多个独立的 “控制流”。
独立的控制流,就是线程。
但是单片机没有操作系统,如何使用线程呢?
用户自己实现的线程
其实不需要操作系统,用户也可以自己实现“控制流上下文切换”。也就是用户态线程。矣?等等,单片机没有操作系统,更没有用户态和内核态之分。不过也可以借用这个术语嘛。
那么在单片机里,所谓的用户态线程,指的其实是 c++ 协程。特别是 C++20 的协程。
其实,在单片机里,所谓协程,主要的上下文切换手段,就是 co_await delay(ms);
只要当前人物想 sleep 一段时间,就让出 cpu 时间。然后交给协程的列队管理器去调度下一个协程。
因为协程的运行是不会被抢占的。只能是主动的 delay 放弃。所以,这种多任务的方式,就叫 协作式多任务
实现 yield
目前单片机的编译器版本参差不齐,co_await 对编译器的版本要求太高。 加上 co_await 背后的机制也不容易理解。
所以,目前我使用的是 asio 作者发明的 stackless coro.
stackless coro 对系统的要求 = 0 。他自身是表现为一个 函数对象。
每次调用的时候,他都会从上次 return 的地方继续。
具体原理可以参考 asio 爸爸的文章 http://blog.think-async.com/2009/07/wife-says-i-cant-believe-it-works.html
那么,对单片机系统工程本身的需求是什么呢?
其实就是写了一个 Executor.
这个 Executor 不需要调用 OS 的 epoll_wait, 也不调用 GetIoCompletionPort, 更不会使用 select. 而是简单的忙等待,死循环。
正如操作系统的pid 0也是在一个死循环里调用CPU的 WAIT 指令。
这个简单的 Executor 就可以驱动 asio 的 stackless coroutine 了。
等日后单片机厂的编译器提上来了,就可以直接使用 asio 的 co_spawn 了。反正 asio 的 cp_spawn 其实也只依赖 Executor, 而不是依赖 asio 的 io_service.
Comments