当编写比“闪烁灯”更复杂的MK代码时,开发人员将面临“超级循环加中断”风格的线性编程固有的局限性。 处理中断需要速度和简洁性,这会导致在代码中添加标志,并使项目样式“带有中断和标志的超循环”。
如果系统的复杂性增加,则相互依赖标志的数量将成倍增加,并且该项目将迅速变成可读性差且易于管理的“意大利面代码”。
实时操作系统的使用有助于摆脱“ pasta代码”,并将灵活性和可管理性返回给复杂的MK项目。
已经开发了几种协作式实时操作系统,并在AVR微控制器中非常流行。 但是,它们都是用C或汇编语言编写的,因此不适合在BASCOM AVR环境中对MK进行编程的人员,这使它们失去了编写严重应用程序的有用工具。
为了纠正此缺点,我为BASCOM AVR编程环境开发了一个简单的RTOS,引起了读者的注意。

对于许多人来说,熟悉的MK编程风格就是所谓的
超循环 。 在这种情况下,代码由一组函数,过程和描述符(常量,变量)(可能是库函数)(通常称为“背景代码”)以及包围在
do-loop结构中的大型无限循环组成。 在启动时,首先对MK本身的设备和外部设备进行初始化,设置变量的常量和初始值,然后将控制权转移到该无限超循环。
超循环的简单性显而易见。 MK执行的大多数任务,因为一种方式或另一种周期性。 缺点也很明显:如果某些设备或信号需要立即做出反应,则MK将在周期周转之前立即提供。 如果信号持续时间短于循环周期,则将跳过该信号。
在下面的示例中,我们要检查按钮是否被
按下 :
do
显然,如果“某些代码”的工作时间足够长,则MK可能不会注意到短按按钮。
幸运的是,MK配备了可以解决此问题的中断系统:所有关键信号都可以“挂”在中断上,并且可以为每个中断编写处理程序。 因此出现了下一个层次:
带有中断的
超级循环 。
下面的示例显示了具有超循环和处理按钮单击的中断的程序结构:
on button button_isr
但是,使用中断会带来一个新问题:中断处理程序代码应尽可能快且短一些; 内部中断,MK功能受到限制。 由于AVR MK没有分级中断系统,因此另一个中断不能在中断内部发生-此时它们已被硬件禁用。 因此,中断应尽快执行,否则其他中断(可能更重要的中断)将被跳过而不进行处理。
中断记忆实际上,在中断内部,MK能够在特殊寄存器中记录另一个中断的事实,以便稍后对其进行处理。 但是,此中断不能立即处理。
因此,我们不能在中断处理程序中写一些复杂的东西,尤其是如果该代码应该有延迟的话-因为直到延迟起作用,MK才会返回主程序(超级循环),而对其他中断却无所作为。
因此,在中断处理程序内部,您通常只需要用一个标志(每个事件自己拥有)来标志事件的事实,然后检查并处理超循环内的标志即可。 当然,这会延长事件响应时间,但是至少我们不会错过一些重要的事情。
因此,出现了下一级的复杂性-
具有中断和标志的超
循环 。
显示以下代码:
on button button_isr
MK的许多程序都受此限制。 但是,此类程序通常或多或少简单。 如果您编写更复杂的内容,那么标志的数量将像滚雪球一样开始增加,并且代码变得越来越混乱和难以理解。 此外,在上面的示例中,延迟问题尚未解决。 当然,您可以在计时器上“挂起”单独的中断,并且在其中...还可以控制各种标志。 但是,这使程序变得非常丑陋,相互依赖标志的数量呈指数增长,甚至开发人员本人也很难很快找到这样的“ pasta代码”。 在开发新项目的过程中,尝试发现错误或修改代码通常变得同等重要。
如何解决“ Pasta Code”的问题,使其更具可读性和可管理性?
操作系统 (OS)可以解决。 在其中,MK应该实现的功能分为任务,其操作由OS控制。
MK的操作系统类型
MK的操作系统可以分为两大类:拥挤的OS和协作的OS。 在任何这些操作系统中,任务都是由称为
调度程序的特殊过程控制的。 在
拥挤的操作系统中
,调度程序可以随时独立地将执行从一个任务切换到另一个任务,为每个任务分配一定数量的时钟周期(可能会有所不同,具体取决于任务的优先级)。 总体上讲,这种方法很好用,使您完全不必看任务的内容:您至少可以编写任务代码
1: goto 1
-仍将执行其余任务(包括此任务)。 但是,抢占式OS需要大量资源(处理器内存和时钟周期),因为每个开关都应完全保存要禁用的任务的上下文并加载简历的上下文。 这里的上下文指的是机器寄存器和堆栈的内容(BASCOM使用两个堆栈-硬件用于子程序的返回地址,而软件用于参数传递)。 这样的负载不仅需要大量的处理器周期,而且每个任务的上下文都需要存储一段时间才能工作。 在最初面向多任务的“大型”处理器中,这些功能通常在硬件中受支持,并且它们具有更多的资源。 在AVR MK中,不支持多任务处理的硬件(一切都需要“手动”完成),并且可用内存很小。 因此,尽管存在替换OS,但它们不太适合简单的MK。
另一件事是
合作操作系统 。 任务本身在此处控制将控制权转移到调度程序的时间,从而允许他启动其他任务。 此外,执行此操作需要执行此处的任务-否则代码执行将停止。 一方面,这种方法似乎降低了整体可靠性:如果任务挂起,它将永远不会调用调度程序,并且整个系统将停止响应。 另一方面,线性代码或超级循环在这方面没有更好的选择-因为它们冻结的风险完全相同。
但是,协作OS具有重要的优势。 由于程序员在此处设置了自己进行切换的时间,因此不可能突然发生,例如,当任务正在使用某些资源或正在计算算术表达式时。 因此,在大多数情况下,在协作式OS中,您可以执行以下操作而无需维护上下文。 这大大节省了处理器时间和内存,因此看起来更适合在MK AVR上实施。
BASCOM AVR中的任务切换
为了在BASCOM AVR环境中实现任务切换,每个任务代码(均以常规过程实现)必须在某个位置调用调度程序-也以常规过程实现。
想象一下,我们有两个任务,每个任务在其代码的某个位置由调度程序调用。
sub task1() do
假设任务1已执行,让我们看看执行“调度程序调用”时堆栈上发生了什么:
返回主代码的地址(2个字节)
堆栈顶部->返回任务1的地址,该任务调用了调度程序(2个字节)
堆栈的顶部将指向任务1中指令的地址,该地址紧跟调度程序调用(在本例中为
循环指令)之后。
在最简单的情况下,调度程序的目标是将控制权转移给任务2。问题是如何做到这一点? (假设调度程序已经知道任务2的地址)。
为此,调度程序应从堆栈(以及要记住的位置)中拉出返回任务1的地址,并将任务2的地址放在堆栈上的该位置,然后发出return命令。 处理器将从堆栈中提取地址,而不是返回到任务1,而是继续执行任务2。
反过来,当任务2调用调度程序时,我们也拉出堆栈并保存可以返回到任务2的地址,然后将先前保存的任务1地址加载到堆栈上。给出
return命令,我们将进入任务1的延续点。
结果,我们陷入了混乱:
任务1->调度程序->任务2->调度程序->任务1 ....
还不错! 而且有效。 但是,当然,这对于实际可用的操作系统是不够的。 毕竟,并非总是如此,也不是所有任务都应该起作用—例如,它们可以
期望一些东西(延迟时间到期,某些信号的出现等)。 因此,任务应具有
状态 (“工作”,“就绪”,“预期”等)。 另外,最好将任务分配给
priority 。 然后,如果准备执行多个任务,则调度程序将继续执行优先级最高的任务。
水族RTOS
为了实现所描述的思想,开发了协作OS AQUA RTOS,它为任务提供了必要的服务,并允许在BASCOM AVR环境中实现协作多任务。
有关BASCOM AVR中的过程模式的重要通知在开始描述AUQA RTOS之前,应注意,BASCOM AVR环境支持两种类型的寻址过程。 这由config子模式= new | 旧的。
在指定旧选项的情况下,首先,编译器将线性编译所有代码,而不管它是否在某个地方使用,其次,没有以子名称/ end sub样式设计的参数的过程将被视为过程。 ,其名称风格为:/ return。 这允许我们使用bylabel修饰符将过程的地址作为标签传递给另一个过程,作为参数。 这也适用于以子名称/结束子样式的样式设计的过程(您需要将过程名称作为标签传递)。
同时,submode = old模式强加了一些限制:任务过程一定不能包含参数; 通过$ include连接的文件代码线性包含在常规项目中,因此,应在连接的文件中提供绕行功能-使用goto和标签从头到尾进行操作。
因此,在AQUA RTOS中,用户必须仅使用task_name样式的旧过程表示法:/ return用于任务,或者使用更通用的子名称/ end sub,在其代码开头添加修饰符submode = old,并绕过包含的文件转到标签/包含文件代码/标签:。
AQUA RTOS任务状态
为AQUA RTOS中的任务定义了以下状态:
OSTS_UNDEFINE OSTS_READY OSTS_RUN OSTS_DELAY OSTS_STOP OSTS_WAIT OSTS_PAUSE OSTS_RESTART
如果任务尚未初始化,则将其分配为状态
OSTS_UNDEFINE 。
初始化后,任务的状态为
OSTS_STOP 。
如果任务
准备好执行 ,则将其分配为状态
OSTS_READY 。
当前正在运行的任务的状态为
OSTS_RUN 。
从中,她可以进入状态
OSTS_STOP,OSTS_READY,OSTS_DELAY,OSTS_WAIT,OSTS_PAUSE 。
状态
OSTS_DELAY具有完成
延迟的任务。
状态
OSTS_WAIT分配给正在
等待信号量,事件或消息的任务 (下面将详细介绍)。
OSTS_STOP和
OSTS_PAUSED状态之间有什么区别?
如果由于某种原因该任务接收到
OSTS_STOP的状态,
则将从其入口点(即接收到
OSTS_READY的状态)执行该任务的后续恢复。 从一开始。 从
OSTS_PAUSE的状态
开始,任务将在挂起的位置继续工作。
任务状态管理
OS本身都可以通过调用OS服务来自动管理任务以及用户。 有几种任务管理服务(所有OS服务的名称
均以OS_前缀开头):
OS_InitTask(task_label, task_prio) OS_Stop() OS_StopTask(task_label) OS_Pause() OS_PauseTask(task_label) OS_Resume() OS_ResumeTask(task_label) OS_Restart()
它们每个都有两个选项:
OS_service和
OS_serviceTask (
OS_InitTask服务除外,后者只有一个选项;
OS_Init服务初始化OS本身)。
OS_service和
OS_serviceTask有什么
区别 ? 第一种方法对导致任务的任务本身起作用; 第二个允许您将指向另一个任务的指针设置为参数,从而从一个任务管理另一个任务。
关于OS_Resume除OS_Resume和OS_ResumeTask外,所有任务管理服务都将在处理后自动调用任务管理器。 相反,OS_Resume *服务仅将任务状态设置为OSTS_READY。 仅当显式调用调度程序时,才会处理此状态。
优先级和任务队列
如上所述,在实际系统中,某些任务可能更重要,而其他任务可能是次要的。 因此,操作系统的一个有用功能是能够分配任务优先级。 在这种情况下,如果同时有多个
现成的任务,则操作系统将首先选择优先级最高的任务。 如果
所有现成的任务具有相同的优先级,则操作系统将按照称为“轮播”或循环的顺序将它们循环执行。
在AQUA RTOS中,通过调用
OS_InitTask服务
初始化任务时将优先级分配给任务,该服务
接收任务
的地址作为第一个参数,并
接收从1到15的数字作为第二个参数,
数字越小优先级越高 。 在OS操作期间,不提供分配给任务的优先级的更改。
延误
在每个任务中,延迟都独立于其他任务处理。
因此,在OS解决一项任务的延迟时,可以执行其他任务。
为组织延迟提供了服务
OS_Delay | OS_DelayTask 。 参数是任务
延迟的毫秒数。 由于参数的维数为
dword ,因此最大延迟为4294967295 ms,或大约120小时,对于大多数应用程序来说似乎已经足够。 调用延迟服务后,将自动调用调度程序,该调度程序将在延迟期间将控制权转移到其他任务。
信号量
AQUA RTOS中的信号量类似于可用于任务的标志和变量。 它们有两种类型-二进制和可数。 第一个只有两种状态:空闲和关闭。 第二个是字节计数器(当前版本的AQUA RTOS中的信号量计数服务尚未实现(我是一个懒惰的驴子,因此下面所述的内容仅适用于二进制信号量)。
信号量和简单标志之间的区别在于,可以使任务
等待释放指定的信号量。 在某些方面,信号量的使用确实类似于铁路:到达信号量后,组合(任务)将检查信号量,如果信号量未打开,它将等待使能信号进一步传播。 此时,其他火车(任务)可以继续行驶(运行)。
在这种情况下,所有黑件都分配给调度员。 一旦任务被告知等待信号量,控制权就会自动转移到调度程序,并且他可以启动其他任务-直到释放指定的信号量为止。 一旦信号量的状态更改为
free ,调度程序就会将等待该信号量的所有任务分配为状态
就绪 (
OSTS_READY ),并将按照优先级和优先级的顺序执行它们。
AQUA RTOS总共提供16个二进制信号量(原则上可以通过更改任务控制单元中变量的尺寸来增加此数目,因为在内部它们是作为位标志实现的)。
二进制信号量通过以下服务工作:
hBSem OS_CreateBSemaphore() OS_WaitBSemaphore(hBSem) OS_WaitBSemaphoreTask(task_label, hBSem) OS_BusyBSemaphore(hBSem) OS_FreeBSemaphore(hBSem)
在使用信号量之前,必须先
创建一个信号量。 这可以通过调用
OS_CreateBSemaphore服务来完成,该服务返回创建的
hBSem信号量的唯一字节标识符(句柄),或者通过用户定义的处理程序引发错误
OSERR_BSEM_MAX_REACHED ,指示已达到最大数量的二进制信号量。
您可以通过将接收到的标识符作为参数传递给其他信号量服务来使用它。
服务
OS_WaitBSemaphore | OS_WaitBSemaphoreTask将(当前|指定的)任务置于一种状态,以
在 hBSem 信号繁忙时
等待该信号
的释放 ,然后将控制权转移到调度程序,以便它可以启动其他任务。 如果该信号量是空闲的,则不会发生控制转移,并且该任务将继续进行。
OS_BusyBSemaphore和
OS_FreeBSemaphore服务分别将
hBSem信号量设置为
忙或
空闲 。
没有提供销毁信号以简化OS并减少代码量的行为。 因此,所有创建的信号量都是静态的。
大事记
除了信号量之外,任务还可以由事件驱动。 可以指示一个任务
预期某个事件 ,而另一个任务(以及后台代码)可以
指示该事件。 同时,所有等待此事件的任务都将接收
准备执行的状态(
OSTS_READY ),并由调度程序将其设置为按优先级和优先级顺序执行。
任务可以响应哪些事件? 好吧,例如:
- 中断
- 发生错误;
- 释放资源(有时为此使用信号量更为方便);
- 更改I / O线的状态或按键盘上的键;
- 通过RS-232接收或发送字符;
- 从应用程序的一部分到另一部分的信息传递(另请参阅消息)。
事件系统是通过以下服务实现的:
hEvent OS_CreateEvent() OS_WaitEvent(hEvent) OS_WaitEventTask(task_label, hEvent) OS_WaitEventTO(hEvent, dwTimeout) OS_SignalEvent(hEvent)
使用事件之前,您需要
创建它 。 这是通过调用
OS_CreateEvent函数完成的,该函数为
hEvent事件返回唯一的字节标识符(句柄),或者通过用户定义的处理程序引发
OSERR_EVENT_MAX_REACHED错误,表明已达到可在OS中生成的事件数量的限制(最多255个不同事件)。
若要使任务等待
hEvent事件,请在其代码中调用
OS_WaitEvent ,并将事件句柄作为参数传递。 调用此服务后,控制权将自动转移到调度程序。
与信号量服务不同,事件服务提供了一个选项来等待带有
超时的事件。 为此,请使用
OS_WaitEventTO服务。 在这里的第二个参数可以指定任务可以预期事件发生的毫秒数。 如果指定的时间已到期,则任务将收到
准备好执行状态
,就好像事件已发生一样,并且将由调度程序设置为按优先级和优先级顺序继续执行。 该任务可以发现这不是事件,而是超时,任务可以通过检查
OS_TIMEOUT全局标志来进行检查。
OS_SignalEvent , . , ,
, .
, : , – .
:
hTopic OS_CreateMessage() OS_WaitMessage(hTopic) OS_WaitMessageTask(task_label, hTopic) OS_WaitMessageTO(hTopic, dwTimeout) OS_SendMessage(hTopic, wMessage) word_ptr OS_GetMessage(hTopic) word_ptr OS_PeekMessage(hTopic) string OS_GetMessageString(hTopic) string OS_PeekMessageString(hTopic)
,
.
OS_CreateMessage ,
hTopic ,
OSERR_TOPIC_MAX_REACHED , , , .
hTopic ,
OS_WaitMessage , . . ,
hTopic .
OS_WaitMessageTO OS_WaitEventTO .
OS_SendMessage . , , –
word . ,
, , , .
, BASCOM
varptr , , :
strMessage = "Hello, world!" OS_SendMessage hTopic, varptr (strMessage)
OS_WaitMessage , , , , — . .
word , , , .
OS_GetMessage ,
OS_PeekMessage .
, ,
OS_GetMessageString OS_PeekMessageString , , , .
AQUA RTOS
TIMER0 . , ( ) . , .. . 1 .
AQUA RTOS
, .
OS_SIM = TRUE | FALSE , .
,
OS_MAX_TASK , . , ( ), . , .
, .AQUA RTOS .
OS_Init . . , – . , , – .
( ) – , . , - .
, AQUA RTOS :
OS_Init my_err_trap
, :
OS_InitTask task_1, 1 OS_InitTask task_2 , 1
, , Arduino Nano V3. - (, test), bas-:
D4 D5 Arduino ( , - ). 100...500
GND . . 2 0,66 .
. , ( , aliases), – , – .
«», « » (, – - , , ; ).
OS_ResumeTask .
, . ? , ! . , , ,
end .
. ,
do – loop . – , , – , .
OS_Delay . , .
OS_SIM = TRUE , , , .
, , , « », . , « », .
, (,
task_1 ), (
end )
task_1 , ,
return , – ,
task_1 (
do task_1 ).
task_1 , ,
OS_delay , , , .
, ,
task_1 ( ,
OS_delay , ..
loop ), , « », ,
task_2 .
task_2 (
do task_2 )
return , – ,
task_2 .
task_2 , ,
OS_delay , , , .
, ,
task_1 ( ,
OS_delay , ..
loop ), , « », ,
task_2 . ,
task_1 , , . (
loop task_1 ), .
task_1 loop , « 1 – – 2 – » .
.
:
task 1
task 2 is waiting messages…
task 1
task 1
task 1
task 1 is sending message to task 2
task 1
message recieved: Hello, task 2!
task 2 is waiting messages…
task 1
task 1
...
, . 1
task 1 , , . 2
task 2 is waiting messages... ,
hTopic , , 1.
task 1 . , , 2 , 1
incr , .
task_1_cnt 1 , , –
loop task 1 . , , 2 , . .
:
AQUA RTOS
, . ; , , . , : , 95…97°; (, GSM-), .
« + + » , . , .
:
- – ControlHeater()
- – ShowGoods()
- / – AcceptMoney()
- – ScanKeys()
- – MakeChange()
- – ReleaseCoffee()
- – Alarm()
.
ControlHeater() , . , , . . 5.
ShowGoods() . , - . 8, .
ScanKeys() , . 3, 40 .
AcceptMoney() . ,
ScanKeys(), 20 .
MakeChange () .
ReleaseCoffee() 10.
ReleaseCoffee() , . 2.
– ,
Alarm() – 1, , .
, . , EEPROM , .
RTOS : ( , ) – .
,
ReleaseCoffee() :
sub ReleaseCoffee() do OS_WaitMessage bCoffeeSelection wItem = OS_GetMessage(bCoffeeSelection) Release wItem loop end sub
ReleaseCoffee bCoffeeSelection , ( , ). ,
ReleaseCoffee() , , ( )
wItem OS_GetMessage .
ReleaseCoffee() , :
dim bCoffeeSelection as byte bCoffeeSelection = OS_CreateMessage()
,
ShowGoods() .
ReleaseCoffee() , . :
dim bGoodsReliased as byte bGoodsReliased = OS_CreateEvent()
ReleaseCoffee() Release wItem bGoodsReliased :
OS_SignalEvent bGoodsReliased
, , , , .
OS_Init :
OS_Init Mailfuncion
– , :
sub Mailfuncion (bCoffeeErr) print "Mailfunction! Error #: "; bCoffeeErr if isErrCritical (bCoffeeErr) = 1 then CallService(bCoffeeErr) end if end sub
( - : , GSM- ..), , , .
, , .. , . ,
OS_InitTask :
OS_InitTask ControlHeater , 5 OS_InitTask ShowGoods , 8 OS_InitTask AcceptMoney , 3 OS_InitTask ScanKeys , 3 OS_InitTask MakeChange, 10 OS_InitTask ReleaseCoffee , 2 OS_InitTask Alarm , 1
, , , , . . ,
OS_ResumeTask « »:
OS_ResumeTask ControlHeater OS_ResumeTask ShowGoods OS_ResumeTask AcceptMoney OS_ResumeTask ScanKeys OS_ResumeTask MakeChange OS_ResumeTask ReleaseCoffee OS_ResumeTask Alarm
, ; «» .
OS_ResumeTask ( ), . , , , .
, . :
OS_Sheduler
end – .
:
, , .
OS_SendMessage() , /. . , , , , .
AQUA RTOS
1.05Q: AQUA?A: , , « », , . , , , , « » WiFi-. , , , EEPROM , , - . . – « », , . , . AQUA.
Q: ?A: . , , , , , . , . , , , , , , -, . , . , - ( ? – ) . .