Chapter 4: Tasks
4.4 Scheduling
4.4.3 Mixed Preemptive
The mixed preemptive scheduling policy is invoked in an application in which some tasks have preemption enabled and other tasks have preemption disabled within the OIL configura-tion file. For example, if the tasks defined in this chapter are set up such that IOSampleKeypad is not preemptive and ProcessKeyPress and OutputDisplay are preemptive, then the system is operating in the mixed preemptive mode. Under this policy, the OS determines whether to invoke the scheduler based on the type of preemption enabled for the currently running task.
Typically, an application operates in mixed preemptive mode if most tasks can be preempted safely; however, a few tasks must run to completion without being preempted. An example is a task that must set a series of outputs within a very tight time window — for example, when a serial communication stream is output bit by bit at a very high speed through a single pin on the microcontroller.
4.5 Example Program
If you build the program included on the accompanying CD and program the test setup, the keys you press on the keypad are echoed to the display. When the end of the display is reached, it scrolls up to allow for additional lines. If you press the # key twice, the display is blanked and starts at the top of the display again. To implement the simple functionality, I had to add a number of new modules and a configuration file to support the basic functional-ity of the card game.
4.5.1 Modules
The modules startup.s, initspr.s, cinit.s,init.c, and main.c were not changed in this chapter. The following modules either were changed or added.
debug.c The only change to this module was the modification to ErrorHook() discussed in section 4.3, “Other Task Services.”
48
Chapter 4: Taskscardgame.c This new module holds all of the tasks and functions required to implement the game of blackjack in the example application. At this point, it consists of only one task, Pro-cessKeyPress.
ProcessKeyPress This is the main task, and it is expanded extensively throughout the book. It consists of one switch statement that takes action based on the current state of the system and the value of the key that was pressed. At this time, the only state of the sys-tem is after one # key has been pressed that signals the syssys-tem to await the next key press.
dispdrv.c This new module accesses the display driver on the Axiom board, which consists of a Hitachi HD44780 display driver chip and a 20-column by four-row alphanumeric dis-play. This module uses five functions and one task to control the disdis-play. If a different display is used, this module will have to be modified. The functions are as follows.
void InitDisplay(InitType type) This initialization function is invoked by the initial-ization routine InitSystem() as defined in Chapter 1. It performs three actions. First it resets the display driver and initializes the driver for a particular display. After the driver has been initialized, the special characters for the card suits are programmed into the driver memory. Finally, a welcome message is displayed on the device. This service makes extensive use of the small local service wait(), which is discussed next.
void wait(UINT32 time) This local service provides a time delay that allows the display driver to react to commands from the processor. It simply takes the value passed in the parametertime and decrements at 0. On the MPC555 microcontroller running at 20MHz, it takes approximately 250ns per count.
void PackDisplay(char *string) This function simply packs a display mirror buffer, which is held locally with the information passed in the null-terminated parameter STRING, which can contain special control characters. For example, 0x0A is recognized as the new line character, \n, at which point PackDisplay() continues writing the string at the first character in the next line. This is accomplished using OutputNewLine(), which is discussed later. The value 0x0C is recognized as the form feed character, \f, which essentially blanks the display and begins outputting the rest of the string to the first character in the first row. Any character with a value greater than 240 is interpreted as a special character that is one of the first eight characters in the display driver buffer.
void OutputNewLine(void) This function moves the cursor to the beginning of the next line. If the cursor is already at the bottom of the display, this routine scrolls the display up one line.
void OutputNewDisplay(void) This function writes the local mirror of the display to the display driver. All of the required handshaking is performed by this one routine.
OutputDisplay This task is activated by an application after it has written information to the global variable displayBuffer.OutputDisplay uses the services defined previously to send the application output string to the display. Because there is only one buffer, this is the highest priority task and must complete before any other task that is running and that
Exercises
49
might corrupt the buffer. In later chapters, I make this module more robust by buffering requests for displays.
hw.c This module provides the lowest level device driver to the system hardware. It is intended to interface with simple external devices such as switches, digital outputs, and ana-log inputs. More complex devices, such as the display, would have their own drivers. The hw.c module has just one function, HWGetValue(). If the method of obtaining a key press is changed or a different keypad is used, change this module.
UINT32 HWGetValue(void *hardware) HWGetValue() is a generic function that gets a value from a piece of hardware. Its responsibility is to translate the information from the hardware to the application device drivers. HWGetValue() always returns a 32-bit unsigned value, which the device driver interprets.
The application passes a structure to HWGetValue() that contains all of the information needed to access the piece of hardware. The first member in all hardware structures is calledtype and is always of the same type; consequently, no matter what the size of the structure being referenced, HWGetValue() is able to access the first member and cast the pointer properly for the structure.
The definitions of all hardware structures are in hw.cfg. Presently, the only type of hardware that this application recognizes is a keypad input. The digital port attached to the keypad is read, and the raw value is translated into the ASCII value of the key that was pressed. If no key was pressed, the return value is 0. The keypad device driver, discussed later, is only concerned with an ASCII value and does not have to change if the keypad changes.
keypad.c This module consists of the IOSampleKeypad task.
IOSampleKeypad This task is the keypad device driver and has been discussed extensively throughout this chapter, and I will not discuss it here again.
4.5.2 Configuration Files
One additional configuration file was added to support the hardware driver.
hw.cfg Here, the application programmer defines the characteristics of all the hardware inputs and outputs controlled by the low-level drivers with the use of a macro that encapsu-lates the definition of the structure. The details of these macros can be found in the configura-tion file.
4.6 Exercises
1. Create a routine in PreTaskHook() that counts the number of times a key is pressed since the system has been reset. Hint: One task is activated every time a key is pressed.
2. Create a task that writes a message to the display whenever the * (asterisk) key is pressed.
Modify the ProcessKeyPress task to start your task instead of chaining the OutputDis-play task. Please do not use “Hello World!” as your message :).
50
Chapter 4: Tasks3. Modify the OutputDisplay task to identify which task sent the message. Hint: The highest priority task that is in the READY state would activate OutputDisplay.
4. If you enjoy adding hardware to your system, add an LED that is on whenever a key is pressed. Create two tasks, one that turns the LED on and the other that turns the LED off, and activate these tasks whenever the state of the keypad changes.
4.7 Summary
In this chapter, I introduced the most important concept in the OSEK/VDX OS standard: the task. Tasks are the most used item in the OS. The API services ActivateTask(),ChainTask(), andTerminateTask() are probably the most used services in the OS.
51
5
Chapter 5
Alarms
Now that the concept of a task has been defined, the services available within the OSEK/VDX API can be expanded. The first API service discussed is alarms, which also includes the con-cept of counters. Unlike other real-time OSs, OSEK/VDX does not have a timer concon-cept;
instead, an alarm is defined that covers the functions of a timer and the unique need of an embedded control system to take an action based on the occurrence of a series of events. This is accomplished by creating a counter object that is incremented whenever an event occurs.
The alarm is triggered when the corresponding counter reaches a preset value. This counter can be a free-running timer, or it can be another type of input, such as a series of pulses from a sensor.
This chapter introduces the concepts of counters and alarms and defines the API services that manage these objects. The OSEK/VDX standards do not currently define an API for counters. Only the API for alarms is defined. Consequently, the counter API can be different for each vendor-specific implementation of the OSEK/VDX OS.
5.1 Counters
A counter is an OS object that keeps track of the number of ticks that have occurred. Some counter-specific constants are defined in the OIL configuration file. Because a standardized API for managing counters does not exist, I strongly advise that all counter manipulation be kept in a separate file to allow portability between implementations or target processors.