目录
[7.1.2 设置一个事件](#7.1.2 设置一个事件)
[7.1.3报警回调Alarm Callback](#7.1.3报警回调Alarm Callback)
[7.1.4 增加计数器值](#7.1.4 增加计数器值)
[7.2.1 绝对Alarms](#7.2.1 绝对Alarms)
[7.2.2 相对Alarm](#7.2.2 相对Alarm)
[7.4 删除Alarms](#7.4 删除Alarms)
[7.7 小结](#7.7 小结)
前言
本系列文章将以RTA-OS为例详细介绍AUTOSAR OS标准及概念,并分享实际使用的一些案例,本文为符合AUTOSAR标准的RTA-OS--Alarm介绍。
OS相关文章
4.1 【OS基础】符合AUTOSAR标准的RTA-OS-功能简介
4.2 【OS基础】符合AUTOSAR标准的RTA-OS-Task详解
4.3 【OS基础】符合AUTOSAR标准的RTA-OS-Interrupts详解
4.4 【OS基础】符合AUTOSAR标准的RTA-OS-Resources详解
4.5 【OS基础】符合AUTOSAR标准的RTA-OS-Event详解
4.6 【OS基础】符合AUTOSAR标准的RTA-OS-Counters详解
2.2 【OS】AUTOSAR架构下QM Application如何访问ASIL Application
2.6 【OS】RH850U2A芯片平台Spinlock的底层实现
2.7 【OS】AUTOSAR架构下MCAL Modules软件分区问题分析
2.9 【OS】AUTOSAR Os是如何启动第一个Task的
2.10 【OS】AUTOSAR OS系统调用产生Trap的过程详解
注:本文章引用了一些第三方工具和文档,若有侵权,请联系作者删除!
正文
7.报警Alarms
可以使用ISR构建以不同速率激活任务的系统。然而,对于复杂的系统,这可能会变得低效和不切实际。警报器(Alarms)提供了一种更方便、更便携的调度系统方式。
该报警机构由以下两部分组成:
1.一个计数器counter.
2.一个或多个报警器连接到计数器上。
报警(Alarm)指定在达到特定计数器值时要执行的操作。系统中的每个计数器都可以附加任意数量的报警(Alarm)。
当计数器的值等于附加到计数器的警报的值时,就说警报已经过期。在到期时,RTA-OS将执行与告警相关联的操作。该操作可以是激活任务(OsAlarmActiveTask)、执行警报回调例程(OsAlarmCallback)、设置事件(OsAlarmSetEvent)或增加计数器值(OsAlarmIncrementCounter)。
报警到期值可以相对于实际计数器值或定义为绝对值。如果报警失效定义为相对于实际计数器,则称为相对报警(Relative alarm)。如果将其定义为绝对值,则称为绝对警报(Absolute Alarm)。
警报可以配置为过期一次。一旦过期的警报被称为单次警报(Single-shot Alarm)。
还可以将警报指定为定期过期。这种类型的警报被称为循环警报(cyclic alarm)。您可以在第7.2节中找到更多关于循环警报的信息。
7.1配置Alarms
一个Alarm需要配置三个配置项:
1.名字 - 系统中的每个报警都需要分配一个唯一的名称。对于其他操作系统对象,这是将代码中在运行时引用警报的名称。
2.关联的计数器counter - 报警在配置时静态绑定到计数器。警报的任何设置都是根据相关计数器的滴答(ticks)数来完成的。
3.报警到期后的动作。
创建的每个报警最多包含4个动作:
1.激活一个任务
Figure 7.1: Activating a Task with an Alarm
2.触发一个事件(event)。
3.执行一个callback函数。
4.增加一个计数器(counter)的值。
如果需要激活多个任务、设置多个事件、进行多个回调或在到期时增加多个计数器,则需要具有相同到期值的多个警报。(调度表提供了一种替代机制,允许同时激活多个任务和/或设置多个事件。后面将在在调度表章节中阅读关于调度表的内容)。
7.1.1激活一个任务
警报最常见的动作是激活一个任务。这是构建具有定期激活任务的系统的基础------可以为每个任务创建一个警报,然后通过软件代码将警报设置为在所需的时间段内发生。图7.1显示了如何配置告警以激活任务。
7.1.2 设置一个事件
报警可以为指定的任务设置一个事件。当事件设置为警报时,它具有与使用SetEvent() API调用设置时相同的属性。这意味着用户需要同时指定事件和要为其设置事件的任务。图7.2显示了如何为告警设置事件操作。
Figure 7.2: Setting an Event for a Task with an Alarm
7.1.3报警回调Alarm Callback
每个报警都可以有一个关联的回调函数。回调只是一个C函数,在告警到期时被调用。
集成指导 :根据AUTOSAR操作系统标准,报警回调只允许在可伸缩性级别1(Scalability Class 1)中进行。这是因为这些回调在操作系统级别上运行,因此可能会干扰定时保护 ,并在内存保护方案中出现一个漏洞。
**Portability Note:**RTA-OS允许放松这个限制,允许在General➔Optimizations➔RTA-OS中使用配置选项在所有可伸缩性类中允许警报回调。
图7.3显示了如何为报警配置回调函数。
Figure 7.3: Configuring a Callback Routine for an Alarm
每个回调函数都必须使用ALARMCALLBACK() 宏来编写,如示例7.1所示。
cpp
ALARMCALLBACK(UserProvidedAlarmCallback) {
/* Callback code. */
}
集成指导:回调例程在操作系统级别运行,这意味着2类中断被禁用。因此,应该是尽可能保持回调例程的精简,以最小化任务和ISR在运行时遭受的阻塞量。
用户可以在回调中进行的唯一使用的RTA-OS API调用是SuspendAllInterrupts() and ResumeAllInterrupts().
7.1.4 增加计数器值
警报增加软件计数器允许您从单个ISR级联多个计数器。由Alarm驱动(Ticked)的计数器Counter继承了警报的周期。所以,如果你有一个每5毫秒发生一次的警报,你可以使用警报来驱动一个秒级的Counter, 这个Counter的一个tick是5ms. 图7.4显示了在RTA-OS中是如何配置的。
Figure 7.4: Cascading counter increments from an alarm
示例7.2显示了如何从中断中驱动1毫秒计数器。在1毫秒计数器上注册的每五次中断将导致告警过期,并增加级联计数器5秒:
cpp
#include <Os.h>
ISR(MillisecondInterrupt){
CLEAR_PENDING_INTERRUPT();
Os_IncrementCounter(Counter1ms);
/* Every 5th call internally performs Os_IncrementCounter(Counter5ms) */
}
Example 7.2: Cascading Counters
级联计数器的滴答率(tick rate)必须是驱动报警的计数器的整数倍。用户可以使用多级级别的级联来配置系统。但是,如果您试在级联中配置一个循环系统或尝试增加硬件计数器,RTA-OS将生成错误。
集成指导:级联计数器的定时特性是相对于级联中第一个计数器的定时特性来定义的。因此,级联中最早的计数器决定了定义所有其他计数器的基本滴答率。如果您更改了最早计数器的标记率,则应用程序的整个计时行为将相应地缩放。
7.2设置Alarms
OS提供两个API用来设置Alarms:
SetAbsAlarm(AlarmID, start, cycle);
将报警设置为在计数器值下次到达时开始时失效。如果底层计数器在调用SetAbsAlarm时已经开始,那么在本次计数器周期内不会发生警报。
SetRelAlarm(AlarmID, increment, cycle);
增量式Alarm,即设置该Alarm后计数满足就执行该Alarm.
在这两个API调用中,周期值设置为0表示警报是单次触发警报,这意味着它在被取消之前只会过期一次。循环值大于零定义循环报警。这意味着在第一次到期后,它将继续使每个周期过期。设置非零周期值可以轻松地配置具有周期变化周期的周期性警报。
参数选着
如果被激活的任务是BCC1或ECC1/2,则将不会有排队的激活。这意味着,如果开始值或增量值非常短,或者开始值非常接近当前的计数器值,那么这可能会导致不希望产生的副作用。当先前激活的实例仍在执行时,警报将尝试激活该任务。激活将会丢失,并将引发一个E_OS_LIMIT错误。必须确保有足够的时间完成任务,然后才能导致任务重新触发任务。
7.2.1 绝对Alarms
单次触发
绝对报警指定报警失效的底层计数器的绝对值。单次触发绝对警报用于监控预定义的阈值非常有用------警报可以配置为在超过阈值时过期。用户可能希望计算在运行时获取的数据采样中发生的错误数量,然后在错误数量达到危险级别时触发恢复操作。这一点如示例7.3所示。
cpp
/* Expire when counter value reaches 42. */
SetAbsAlarm(DangerLevelReached, 42, 0);
Example 7.3: Absolute single shot alarm
示例7.3如图7.5所示。
Figure 7.5: Illustration of an Absolute Single Shot Alarm
当我们需要设置一个超时,然后等待一个固定的超时时间,然后在超时发生时采取动作时,单次触发报警很有用。
循环触发
如果绝对告报警指定了一个非零周期值,则它将首先在指定的起始点终止,然后在此后的每个周期终止。这一点如示例7.4所示。
Figure 7.6: Illustration of the Absolute Cyclic Alarm
cpp
/* Expire when counter value reaches 10 and then every 20 ticks thereafter
*/
SetAbsAlarm(Alarm1, 10, 20);
Example 7.4: Absolute cyclic alarm
对于绝对告警,零刻度的绝对起始值与任何其他值相同------这意味着当计数器达到零值时告警失效。
例如,如果当前计数器值为零,则将不会看到警报过期,直到计数器值的最大值+1数量发生。另一方面,如果计数器的值已经处于最大等值,那么就会看到警报在计数器的下一个tick上过期。
设置Alarm为过去值
设置为过去式Alarm时,需要等待MAXALLOWEDVALUE+1个Tick后才能到期。
注意:一个常见的错误是设置绝对Alarm在OS 起动后Counter为0时发生,这样的Alarm不会发生,因为0已经是过去发生的。
Figure 7.7: Setting an alarm in the past
将绝对循环Alarm同步到计数器包装
将Alarm设置为在已知的同步点定期发生对于实时系统非常重要。但是,在AUTOSAR操作系统中,不可能将绝对Alarm设置为每次基线计数器环绕时定期发生。
如果需要此类功能,则必须提供每次Alarm过期时重置绝对单次Alarm的代码。例如,如果Task1连接到Alarm1,则Task1的主体将需要在激活任务时重置Alarm,如下示例所示。
cpp
TASK(Task1) {
/* Single-shot alarm reset at top dead center = 0 = 360 degrees. */
SetAbsAlarm(Alarm1, 0, 0);
/* User code. */
TerminateTask();
}
Example 7.5: Resetting an Alarm when a Task is Activated
7.2.2 相对Alarm
单次触发
相对报警指定报警失效的底层计数器的绝对值。当用户想在运行时超时某些活动时,单次触发相对报警很有用。例如,用户可能希望等待一个外部事件,然后在该事件未发生时激活该任务。
Figure 7.8: Illustration of a Relative Single Shot Alarm
示例7.6显示了如何设置绝对单枪报警。
cpp
/* Timeout 42 ticks from now */
SetRelAlarm(Timeout, 42, 0);
Example 7.6: Relative single shot alarm
当用户需要设置一个超时,等待一个固定的超时时间,然后在超时发生时采取行动时,单次触发报警我们很有用。
在AUTOSAR操作系统中,禁止在SetRelAlarm()中使用零作为增量。如果您使用零作为增量,则将返回一个E_OS_VALUE错误。
循环触发
示例7.7显示了一个相对警报,在10次响后响一次,然后每20次响一次。
cpp
/* Expire after 10 ticks, then every 20 ticks. */
SetRelAlarm(Alarm1, 10, 20);
Example 7.7: Relative cyclic alarm
在图7.9中,可以看到如何可视化此报警。
Figure 7.9: Illustration of a Relative Cyclic Alarm
7.3 自启动Alarms
虽然可通过在主函数中调用SetRelAlarm() 或 SetAbsAlarm() API来起动Alarm,但最简单的调用周期Alarms方式是在STARTOS()后自启动Alarm。
自启动Alarm需声明其为绝对还是相对Alarm,counter及周期值。
Figure 7.10: Auto-starting Alarms
RTA-OS确保软件计数器在StartOS()期间初始化为零(硬件计数器将设置为您自己的应用程序初始化代码配置的值)。因此,如果对绝对Alarm使用零刻度开始时间,则必须小心,因为在Alarm启动时,零刻度已经发生。虽然Alarm将启动,但在相关计数器变为零之前,Alarm不会过期。在一个16位计数器上,每毫秒Tick增加一次,你需要等待超过65秒,然后在一个32位计数器上等待不到48天。指定Alarm在第一次(或以后)勾选时开始表示初始到期将在计数器的下一次勾选时发生。如果需要Alarm彼此同步(即Alarm之间的相对过期时间必须间隔预先设定的刻度数),则自动启动绝对Alarm非常有用。
7.4 删除 Alarms
可以通过调用CancelAlarm() API来删除Alarm,例如,可能需要取消Alarm以停止正在执行的特定任务。可以使用SetAbsAlarm()或SetRelAlarm() API调用重新启动Alarm。
7.5 确认何时会发生Alarm
当用户需要确认Alarm何时会发生,如对于绝对Alarm,为了避免已经达到的值被设置时可以通过调用Get Alarm() API获取。该调用返回指定Alarm到期前剩余的Tick数。如果未设置Alarm,则API调用将返回值E_OS_NOFUNC,到期的刻度数未定义。建议在使用结果之前检查调用的返回值。示例7.8显示了API调用的使用情况。
TickType TimeToExpiry;TickType SafetyMargin = 100;StatusType IsValid;IsValid = GetAlarm(Alarm1, &TimeToExpiry);if (IsValid != E_OS_NOFUNC) { if (TimeToExpiry <= SafetyMargin) { Log(InsideSafetyMargin); }}
Example 7.8: Getting the time to expiry
在根据调用返回的Tick值出运行时决策时,尤其是在底层计数器具有高分辨率的情况下,应特别小心。与使用GetCounterValue() 读取计数器值一样,在获取该值和使用该值进行计算之间可能会发生抢占。这意味着可能会读取(很长)到期时间,但随后会被抢占,以便在Alarm到期前不久(甚至在Alarm到期后)恢复。
7.6非周期Alarms
周期报警只对循环行为有用。在许多系统中,例如那些需要定期执行任务来轮询数据源的系统中,这是非常理想的。但是,用户可能需要对在运行时需要更改的系统进行编程。例如,正在计算一个发动机轴转速,并使用它来编程火花或喷射定时的持续时间。
需要使用单次触发警报对具有警报的非周期行为进行编程,并由激活的任务设置为下一个到期值。
在示例7.9中,一个任务每毫秒运行一次,并轮询一个记录曲轴旋转度的计数器。该任务将计算曲柄的位置和速度。速度用于确定火花定时的持续时间。火花启动,警报设置为在SparkTiming ticks到期后。
cpp
TASK(MillisecondTask) {
...
GetElapsedCounterValue(ShaftEncoder,&Position,&DegreesRotation);
RevsPerMinute = (DegreesRotation/360) * 1000 * 60;
SparkTiming = Lookup(RevsPerMinute);
if (Position == 90) {
StartSpark();
SetRelAlarm(TimeCounter, SparkTiming, 0); /* Activates SparkOff on
expiry */ }
}
...
TerminateTask()
}
TASK(SparkOff){
StopSpark();
TerminateTask();
}
Example 7.9: Aperiodic Alarm Example
7.7 小 结
-
Alarms是基于counter设置的,同一个Counter可设置多个Alarms;
-
每个Alarm执行的操作可能为:执行某个Task、设置个Event、执行某回调函数或增加一个计数器;
-
Alarm可设置绝对或相对counter值;
-
Alarm可配置为自启动模式。
-
实际应用中一般采用调度表来调度,Alarms应用相对比较少。
参考文档:
[1] RTA-OS V6.1.3 User Guide
[2] Specification of Operating System AUTOSAR Release 4.2.2