Chapter 5: Alarms
5.2 Using Alarms
Alarms are OSEK/VDX objects that are associated with counters. When defining alarms, each alarm is statically assigned to one counter and one task; however, multiple alarms can be assigned to a given counter. Whenever a counter is incremented, the currently active alarms assigned to that counter are compared to the counter value. If the values are equal, the alarm is triggered and it can take one of two possible actions: activate a task or set an event for a task. I have already explained how to activate a task, and I will discuss setting an event in Chapter 6.
For the card game application, I define three alarms at this time: a periodic alarm to sam-ple the keypad, a periodic alarm to samsam-ple the shuffle switch, and a shuffling alarm that trig-gers a random shuffling of the cards after a set number of pulses from the shuffling switch.
The OIL configuration for these three alarm objects is shown in Listing 5.4.
Listing 5.4 Alarm definition.
break;
}
TerminateTask();
}
/**************************************************************************/
/* Alarms */
/**************************************************************************/
ALARM SampleKeypad {
COUNTER = SYSTEM_COUNTER;
ACTION = ACTIVATETASK{
TASK = IOSampleKeypad;
};
};
ALARM SampleShuffleSwitchAlarm { COUNTER = SYSTEM_COUNTER;
ACTION = ACTIVATETASK{
TASK = IOSampleShuffleSwitch;
};
};
ALARM ShuffleAlarm {
COUNTER = SHUFFLE_COUNTER;
Using Alarms
57
Within an alarm object definition, the standard requires two attributes: COUNTER and ACTION. The COUNTER attribute defines the counter associated with this alarm when active. The name used in this attribute must match the name of a counter object defined elsewhere in the configuration file. The ACTION attribute can be either ACTIVATETASK or SETEVENT. Depending on the value of this attribute, either one or two references to other OIL objects must be defined. If the action is ACTIVATETASK, then one reference, TASK, is required. This references the name of a task to activate, which is also defined in this configuration file. If the action is SETEVENT, then two references are required: TASK and EVENT.EVENT is a valid event for the TASK reference as defined elsewhere in the configuration file. In the OSEKWorks v4.0 implementa-tion, the TASK and EVENT references appear as separate attributes, instead of being associated directly with one ACTION attribute as references. This might affect portability of the OIL file and should be fixed in a later version. The code on the accompanying CD includes the OIL configuration file output from OSEKWorks and will be different from the listing here.
Now that the alarms and counters are defined in the configuration file, I have to update the application to use these alarms. The first item to update is the task previously defined to sample the keypad, IOSampleKeypad (Listing 5.5). This is now be a preemptible task with a higher priority, so I need to remove the delay loop used to allow higher priority tasks to run.
Listing 5.5 Keyboard sampling task.
ACTION = ACTIVATETASK{
TASK = ShuffleCards;
};
};
TASK(IOSampleKeypad) {
static BOOLEAN keyState = FALSE;
char tempKey;
tempKey = HWGetValue(&KEYPAD);
if(tempKey == lastKey){
if(keyCount++ == KEY_DEBOUNCE_TIME){
--keyCount;
keyValue = tempKey;
} } else{
keyCount = 0;
lastKey = tempKey;
}
if(keyState==FALSE){
if(keyValue != 0){
58
Chapter 5: AlarmsBecause this task is now triggered by the expiration of an alarm, you must start the alarm at some point with the use of a special initialization task that is run immediately after the OS starts. The purpose of this task, which is not defined in the standards or included in an imple-mentation, is to start the alarms that the application must have running on startup. These are typically periodic alarms that kick off periodic tasks required by the application. The OSEK/VDX standard does not have an autostart configuration for alarms, as it does with tasks, because the state of the counters is uncertain. The task I created to autostart alarms is InitAlarms (Listing 5.6, in init.c).
Listing 5.6 Alarm autostart task.
keyState = TRUE;
ActivateTask(ProcessKeyPress);
} } else{
if(keyValue == 0){
keyState = FALSE;
} }
TerminateTask();
}
TASK(InitAlarms) {
InitAlarmType const *list = AlarmAutostartList;
UINT32 currentAppModeMask = ConvertAppMode(APP_MODE_MASK);
while(list->appmodemask != 0x00000000){
if((list->appmodemask & currentAppModeMask)!= 0){
if(list->alarmtype == ALARM_REL){
SetRelAlarm(list->alarm,list->start,list->cycle);
} else{
SetAbsAlarm(list->alarm,list->start,list->cycle);
} } list++;
}
TerminateTask();
}
Using Alarms
59
InitAlarms is configured as an autostart task in the OIL configuration file with the highest possible priority. In addition, no other tasks are allowed to have the same priority. This ensures that this task is the first run after the OS starts.
InitAlarms first obtains the current APPMODE and then determines which alarms it needs to start. First, it translates the APPMODE into a bit mask. The AppModeType is not explicitly defined in the OSEK/VDX standard. In one implementation I have seen, AppModeType is a scalar value realized with the use of enumeration. In another implementation, it is a pointer to a structure defining the APPMODE. Consequently, I have created a conversion routine ConvertAppMode(), which encapsulates the format of the APPMODE type. This routine obtains the current APPMODE usingGetActiveApplicationMode() and converts it to either a number or a bitmap, which it returns.
When calling ConvertAppMode(), the requested type of return, either APP_MODE_MASK or APP_MODE_VALUE, is passed to the routine as a flag. The return value is always UINT32. If a number is returned, it can be used efficiently in a switch statement; if a bit mask is returned, it can be used efficiently in a table lookup. The details of this routine are found in the source code on the accompanying CD in os.h and os.c. The os files were created to hold any imple-mentation-specific glue routines that might be required to port an application from one OSEK/VDX implementation to another.
In InitAlarms, I traverse a null-terminated list of structures that define the alarms to be started. For each alarm, the structure defines the type and periodicity, the starting value, and a mask of the APPMODEs in which the alarm must be started. The NULL value is the mask of APPMODEs, because there has to be at least one mode in which an alarm starts, or it should not be in the list. The type definition for the startup alarm structure InitAlarmType is shown in Listing 5.7.
Listing 5.7 Alarm autostart definition structure.
For each entry in the table, the APPMODE mask in the currentAppModeMask local variable is compared to an enable mask, structure member appmodemask, for each alarm. If the alarm is
typedef enum AlarmFunctionTypetag { ALARM_REL,
ALARM_ABS
}AlarmFunctionType;
typedef struct InitAlarmTypetag { AlarmType alarm;
AlarmFunctionType alarmtype;
TickType start;
TickType cycle;
UINT32 appmodemask;
}InitAlarmType;
60
Chapter 5: Alarmsto autostart in the current APPMODE, one of two API service routines are invoked: SetRelA-larm() or SetAbsAlarm(), depending on the alarmtype structure member. The C function prototypes for each service are defined below.
StatusType SetRelAlarm(AlarmType alarm, TickType increment, TickType cycle);
StatusType SetAbsAlarm(AlarmType alarm, TickType start, TickType cycle);
SetRelAlarm() starts a relative alarm, which is an alarm that is set to expire at a value rela-tive to the current counter value. When this service is invoked, the alarm is set to the current value of the counter that is attached to this alarm, plus the value of increment. This incre-ment must not be set to a value less than that in the MINCYCLE attribute of the COUNTER object in the OIL configuration file. If the increment is set to 0, the behavior of this service is defined by the implementation. Because this behavior is undefined by the standard, to enable maxi-mum portability I recommend that increment never be set to 0 by the application.
The cycle parameter is used to create a periodic alarm. If cycle is set to a value other than0, the alarm is restarted immediately after expiration, with the value of cycle added to the previous value of alarm. This allows a periodic alarm to be defined and to start at a future time defined by increment that might be far greater than the period (cycle), to enable, for example, a large delay that allows the power supply system to settle. If cycle is 0, the alarm is canceled after it expires. They cycle parameter must not be less than the MINCYCLE attribute of theCOUNTER object.
SetAbsAlarm() starts an absolute alarm, which is defined as an alarm set to expire at an absolute value of the counter attached to the alarm. When invoked, the alarm value is set to the value of start. If cycle is not 0, the alarm restarts immediately after expiration, with the new comparison value equal to the old alarm value plus the cycle value. This type of alarm is typically used for a cyclic input, such as the crank angle of an engine. For example, a task that needs to fire fuel injectors at 10 degrees of crank angle for the first fuel injector and then sym-metrically for each additional fuel injector would set the start value at 10 and cycle at 90 for an 8 cylinder engine.
Both API services have the same return values.
• If the alarm is set properly, the service returns E_OK.
• If the alarm is already in use, the service returns E_OS_STATE.
• In the extended status mode, if the alarm value passed is invalid, the service returns E_OS_
ID.
• In the extended status mode, if the value of increment or start is less than 0 or greater than the MAXALLOWEDVALUE attribute for the counter, the service returns E_OS_VALUE.
• In the extended status mode, if the value of cycle is not equal to 0 and is less than the MINCYCLE or greater than the MAXALLOWEDVALUE attribute for the counter, the service returns E_OS_VALUE.
These services can be called from either the task or interrupt level but must not be called by any of the hook routines.
At this point, the only alarm that needs to start automatically with the OS is the keyboard sampling alarm. This alarm is initiated as a relative alarm starting 500 milliseconds after the OS starts. It then samples the input periodically every 24 milliseconds. The other alarms are only required when the system is actually shuffling cards. These alarms are initiated by the task that is activated when the key that determines that shuffling must occur is pressed.
Using Alarms
61
Now that the application samples and debounces the keypad periodically, it is in the OS-supplied idle loop most of the time. In most OSEK/VDX implementations, it is a do-nothing loop. The application remain in this loop until an interrupt occurs that moves a task from SUSPENDED or WAITING to READY. If a watchdog needs petting in order to keep the system run-ning, the application should provide a low-priority basic task that performs this function.
This should not be a periodic task unless it is the lowest priority (0) task in the system (i.e., it cannot preempt anything). Otherwise, a poorly behaving low-priority task might be masked by the higher priority watchdog task.
To support this in the future, I have reintroduced background, a zero-priority basic task that is just a while(1) loop, for future expansion. Inside this loop, it is possible to
• monitor idle time,
• manage power to put the processor in a low power state between clock ticks, and
• pet the watchdog.
Thebackground task is a placeholder for additional features that might be necessary in the future. The example application, which is not powered by batteries, has no use for power management at this time; however, it could be required in the future, so I recommend that this small task be added in any application.
The next step is to modify ProcessKeyPress (Listing 5.8), found in cardgame.c, the task that is activated whenever a key is pressed.
Listing 5.8 ProcessKeyPress task.
TASK(ProcessKeyPress) {
switch(keyValue) {
case '#':
if(gameState != GAME_SHUFFLING){
DealCard();
} break;
case '*':
if(gameState != GAME_SHUFFLING){
gameState = GAME_SHUFFLING;
SetRelAlarm(SampleShuffleSwitchAlarm,10,10);
ActivateTask(ShuffleCards);
} break;
}
TerminateTask();
}
62
Chapter 5: AlarmsProcessKeyPress performs the following functions.
• When the * key is pressed and the system state is NOT_SHUFFLING, the system begins shuf-fling by starting the alarm that samples the shufshuf-fling switch. The ShuffleCards task is activated, which clears the display and shows the message “SHUFFLING -”.
• When the # key is pressed and the system state is NOT_SHUFFLING, the next card is taken from the deck and displayed using DealCard(). If all lines are filled on the LCD display, the display is scrolled up.
The alarm that samples the input from the shuffling switch is initialized as a periodic alarm that triggers the sampling task (Listing 5.3) after 10 milliseconds and then periodically every 10 milliseconds thereafter.
IOSampleShuffleSwitch() checks the input and determines when a transition from low to high occurs. If a transition does not occur, a counter is decremented if not equal to 0. When the counter reaches 0, shuffling is complete. Because the counter is 0 until the first pulse occurs, the task will wait forever for the user to press the switch. When the switch is pressed and the first transition occurs, the task sets the timeout counter to a configuration value and starts the shuffling alarm. To initialize the alarm, the task checks whether the shuffling alarm defined earlier has been started using the OSEK/VDX API service GetAlarm().
StatusType GetAlarm(AlarmType alarm, TickRefType tick);
In this service, alarm is a name of the alarm to check, and tick is a reference to a valid variable of type TickType. The service places the current remaining number of ticks for the alarm in the variable pointed to by the argument tick. The return value from this service is
• E_OK if the alarm is presently running,
• E_OS_NOFUNC if the alarm is not presently running, or
• E_OS_ID in the extended status mode if alarm is not valid.
This service can be invoked from the task level or the interrupt level and from the hook routinesErrorHook(),PreTaskHook(), and PostTaskHook().
If the alarm has not been started, GetAlarm() starts an absolute alarm that is triggered the first time the shuffle switch counter reaches 5 and every three counts after that. This creates some randomness in the shuffling algorithm because the counter is stopped when the switch is released, and the value of the counter when the switch is first pressed will be a random num-ber between 0 and 9. Consequently, the time until the first alarm expiry after the switch is first pressed until the first shuffling occurs is random. The routine also changes the display to
“SHUFFLING -”.
If the timeout timer has been decremented to zero, the shuffling and sampling alarms are canceled using the CancelAlarm() API service, which cancels an alarm that was previously started — whether single or cyclic — and activates a task to signal that shuffling is complete.
The prototype for this service follows.
StatusType CancelAlarm(AlarmType alarm);
InCancelAlarm(), the alarm parameter is the name of a previously defined alarm that is run-ning. The return value from this service is
• E_OK if the service processes properly,
• E_OS_NOFUNC if the alarm is not in use, or
Using Alarms
63
• E_OS_ID in the extended status mode if alarm is not valid.
This service can be called from either the task or interrupt level and cannot be called by any of the hook routines.
After the shuffling alarm is checked and started, if necessary, the shuffling counter is incre-mented, using the implementation-specific IncrCounter() API service, and the dash that is displayed after “SHUFFLING -” is modified to look like a clock with the use of the characters
“- \ | /”. The API service increments the counter object and checks all the alarms defined for the counter. If any alarms have expired, the OS takes the action defined by the alarm. In this case, if the shuffling alarm has expired, the ShuffleCards task is activated (Listing 5.9, in carddeck.c), and one shuffle of the cards occurs.
Listing 5.9 ShuffleCards task.
Therand() C library function is used to shuffle the deck every three counts of the shuffling switch. This task is set up with a higher priority than the routine that checks the switch input to ensure that the shuffle is a higher priority than the switch. In Chapter 7 on resources, I con-sider the deck to be a resource that can be locked.
TASK(ShuffleCards) {
UINT8 count = (UINT8)(((UINT32)rand() * 100u) / RAND_MAX) + 1;
UINT8 location1, location2,tempcard;
deckStart = cardDeck;
while((count--)>0){
location1 = ((UINT32)rand() * 52u) / RAND_MAX;
if(location1==52) location1 = 51;
location2 = ((UINT32)rand() * 52u) / RAND_MAX;
if(location2==52) location2 = 51;
tempcard = cardDeck[location1];
cardDeck[location1] = cardDeck[location2];
cardDeck[location2] = tempcard;
}
strcpy(displayBuffer,busyDisplay[busyLocation++]);
if(busyLocation == 4) busyLocation = 0;
ActivateTask(OutputDisplay);
TerminateTask();
}