Building Real-Time, Multi-Tasking Embedded Applications Without an OS Kernel

Total Page:16

File Type:pdf, Size:1020Kb

Building Real-Time, Multi-Tasking Embedded Applications Without an OS Kernel

pseudOS™

Building real-time, multi-tasking embedded applications without an OS kernel.

Bruce Boatner, EE

Preface

Draft 1 101231 1 Your project starts off well enough – a simple and elegant solution to a well-defined set of features and functions. You are half-way to your first test release when the changes start coming in. That important customer who’s promised to buy thousands of your new product has started thinking about all the new features that “would be nice to have”, and your company’s sales guy, not wanting to risk losing the big deal, has promised them the moon. “Sure – our guys are real smart – they can do anything!” we says, with a grin.

Suddenly things don’t look so pretty. Your design is now encumbered with all kinds of timing issues, unwieldy inter-dependencies and pretzel logic, as you valiantly attempt to incorporate the new functionality into the program while still meeting the required system response times and project time lines. Your “simple and elegant” solution is showing the potential of turning into a nightmare.

You remember someone once having said “I never could have done that without an RTOS”. Even if you could get your current system to work, you know the person who will eventually end up maintaining it will be cursing you under his or her breath, knowing that any change they make might wind up producing a myriad of unintended consequences. That’s not exactly the legacy you hoped to leave in your wake.

You are now faced with the unpleasant prospect of researching, identifying and acquiring an appropriate RTOS, and re-architecting the entire project in the OS environment. As if you needed more pressure, you are aware that many projects and careers have not survived the schedule and budget impacts of such a major course correction.

Inevitably, then, these situations seem to come down to a choice between designing a standard, single-threaded, relatively simple architecture, or using a multi-tasking RTOS. There appear to be no other options, with the “No Man’s Land” in between the two choices being fraught with spaghetti code monsters and un-decipherable C creatures.

But what if this were not the case? What if there was a third choice? pseudOS is designed to fit into this gap. It is not intended to replace the RTOS, but rather to emulate many of its behaviors. There are times when using an RTOS is absolutely the correct choice, especially in complex systems involving the coordinated efforts of multiple developers, but many times the majority of the features of an RTOS are not used. Closer analysis shows that simple multi-tasking functionality forms the basis for choosing an RTOS the majority of the time. pseudOS is intended for programmers who want another choice, or who are starting to lose control of their applications due to the reasons described above. Since pseudOS is a design methodology, any existing single-threaded application can be “retro-fitted” to the multi-threaded pseudOS philosophy by following a simple set of guidelines. This usually involves breaking down existing functions into smaller pieces, organizing them into groups of actions that support state functionality, and executing these action-objects under a finite state machine control system. The result is a system that is logical, easy to maintain, and highly responsive.

Draft 1 101231 2 Introduction

The idea of pseudOS came while exploring the possibilities of using large numbers of inexpensive micro-controllers to emulate neurons in Artificial Neural Network (ANN) designs. Generic neurons take input signals from other neurons, and based on this activity and a system of weighted values, produce an appropriate output. The “intelligence”, if you can call it that, comes from the configuration of the weights at its inputs.

Since most micro-controllers, or Systems on Chip (SoC) can typically manage a large number of analog inputs and have both digital and PWM outputs (PWM is easily be converted to analog voltage for inputs to other neurons), it seemed like a natural fit. A simple bus – I2C or SPI - could be used to train the system and establish the weights. Of course it would be desirable to pack as many neuron emulators as possible onto a single SOC part, so an RTOS appeared to be a logical part of the equation.

One interesting thing about neural networks is that they are not designed to produce a pre- determined set of outputs from a known set of inputs – they are designed to learn to produce appropriate behavior through training, with a healthy dose of noise and uncertainty thrown in for good measure.

Another interesting feature of neurons and neuron clusters, is that they tend to do one thing very well, like recognizing a pattern, and actually have no clue what the overall goal of the system is, or how the results of their efforts are being used. All they know is that they get an “atta boy” when they do things right, and that’s good enough for them. It is the system, at some higher level, that sends out the “atta-boys” when the organism has found something yummy to eat, or escaped from something that considered it something yummy to eat.

The organism is state-oriented – it may be in a state of hunger, or a state of panic. It is also interrupt and priority-driven. It might be configured in an optimal state for finding food, when it is interrupted by something appearing on the scene which causes it to enter a state of panic, the priority being survival. At that point all neural activity is switched to the necessities of flight.

Let’s examine how a state change might occur in the organism. As we implied before, the individual neurons, neuron clusters and processing sub-systems are organized in a hierarchical manner. Their “raison d' être” is to provide accurate information to the next level higher-up in an efficient and timely manner (the right answer late is the wrong answer). Their reward for this is an “atta-boy”, or not getting eaten. Perhaps if things are not done as well as expected, there might be a mild electric shock involved.

So let’s look at this massive array of neural clusters that is at the disposal of the organism at any given moment. Neurons are cheap; when the organism is growing, it produces them by the billions. If it is a matter of survival to be able to switch states instantly, would it make sense when so much is at stake to depend on the same set of neural that were specialized in sniffing out food to suddenly become experts at survival tactics?

Draft 1 101231 3 Probably not. Just as we have soldiers, dentists and plumbers that are trained and equipped to support specific needs of our society, it would make more sense that specialized neural configurations could be switched in and out quickly when a significant state change is called for. Remember, neurons are cheap and there are lots of them. And you wouldn’t call a plumber when you have a tooth ache, or muster the dentists if we just got invaded by Uruguay.

In the pseudOS these concepts have direct corollaries. Functions are equivalent to neurons (neuron clusters, actually). They are small, simple and efficient – and there are lots of them. Functions are considered “action-objects”. Like neurons, they respond to the outputs of other functions and they also depend on their own internal state (as a neuron maintains its weights). When active, they are cycled repeatedly and frequently. Many times they may do nothing more than checking an input bit and exiting. When they do something, it may be as simple as setting a flag, putting a character in a buffer, or incrementing a global variable.

Like the organic model, pseudOS is state oriented. States are created by switching on and off sets of “action-objects” to produce the desired behavior. Near parallel-processing responses can be produced by running large numbers of small “action-objects” as quickly as possible. The “action-objects” are not run continuously, but a cyclical or pulsed manner. This establishes a low duty-cycle where the processor is free to respond to non-deterministic events and interrupts.

Studies have shown that the brain processes data in a cyclical manner, which can cause adverse responses from strobe lights and other stimuli that closely match the natural rate. Without a “system clock”, randomly wired parallel systems can become a non-deterministic free-for-all, where variable signal path lengths cause race conditions, instability and inevitable chaos.

The cyclical nature of organic computational systems is not the same as the “system clock” of a man-made state machine, however. It is more like a wave-front traveling through the computational clusters, orchestrating hierarchical processing by controlling the order of sequential sub-functions. Thus it is an interesting mix of parallel processing accomplished by sequential execution. It is this process that we attempt to emulate in pseudOS applications.

Before delving deeper into what pseudOS is (and isn’t), let’s take a quick look at some of the RTOS varieties you might encounter in your research.

Proprietary RTOS

These products range from mission-critical applications like aircraft and engine control systems, medical equipment, etc., to small embedded systems that might manage a power tool or microwave oven. Most of these products, since they tend towards being rather expensive, come fully featured with extensive API’s (Application Program Interfaces). You can tell something about the complexity of these offerings by the length of their user manuals, which are typically many hundreds of pages in length. “Creeping Featurism” is common, and the size and scope of these products is impressive, the cost of which is reflected not only in the initial ‘seat’ purchase price, but also complex distribution licensing fee structures. Although its size can be regulated by loading in only the features that are required for the application, the kernel size of OS is also an important constraint on severely resource-limited processors.

Draft 1 101231 4 However, one must also take into consideration “you get what you pay for”. Purchasers of proprietary software packages are typically provided excellent customer service access, the value of which cannot be underestimated in a time-critical development process. The seasoned engineering manager could certainly be considered diligent for concluding that the success of a project is worth the insurance of having a professional available by phone. Overcoming a single “show stopper” in a timely manner can easily justify the ticket price.

Open Source RTOS

The GPL (General Public License), through the Free Software Foundation, is becoming a popular means of acquiring and distributing software. Linux is perhaps the “poster child” of the GPL model, and we will discuss embedded Linux in a moment.

Two examples of small-footprint RTOS systems are freeRTOS and PICos18, which can run on micro-controllers with limited resources. This is also an ideal environment for pseudOS, since any RTOS imposes significant resource requirements before the first line of application code is ever written.

Perhaps the most significant example of a resource-hungry RTOS is embedded Linux. The original multi-process Linux OS incorporated over 125 million lines of code, with some “skinny” versions touting a “mere” 45 million LOC. It is not unusual for a minimal implementation of an embedded Linux system to host a kernel of 5-6 KB. Embedded Linux is a streamlined version, but it is still Linux, and carries with it all of the characteristics of its big brothers, including the need to mount a disk file system, program paging, boot-loaders, run-time code image expansion, etc. Embedded Linux is typically hosted on very powerful micro-controllers and processors, incorporating Memory Management Units (MMUs) to accommodate the heavy memory usage requirements, and very high clock speeds necessary to accommodate the hundreds or thousands of cycles necessary to perform a simple context switch.

Linux, like Unix, is a powerful multi-user, multi-processor, multi-tasking OS, with every bell and whistle known to man. It is ideal for the engineering program development environment, but why in the world would anyone in their right mind consider it as a solution to an embedded problem?

First, Linux is “free”. However, the concept of open source software contains some subtle and potentially insidious implications to the commercial product developer. You will hear the term “GPL infection”, meaning the potential for incorporating GPL code mixed with proprietary code requiring the developer to “open source” program materials containing trade secrets and other intellectual property. Simply observing the extent to which discussions of how incorporating GPL software affects or does not affect commercial enterprise and intellectual property says a great about how uncertain the legal precedents are in this area. My question is – if you could avoid this conundrum altogether, why would you ever bother with it in the first place?

Secondly, there is an understandable desire in the industry to converge on a standard in the world of embedded devices. I am certainly sympathetic to this idea as a response to the bewildering

Draft 1 101231 5 array of closed-source proprietary solutions and tool chains, but unfortunately I do not believe that Linux is the answer. It simply appears to have been the only logical choice available at a time when the engineering community was looking for alternatives.

What is pseudOS?

“pseudOS is a design methodology that emulates multi-tasking functionality at the application level.”

Let’s break down this description down into a high-level overview.

1. pseudOS is a design philosophy. Specifically it exploits finite state machine concepts to provide multi-tasking functionality, using standard ANSI C programming practices.

2. pseudOS provides functionality at the application level. An RTOS kernel controls the execution of program code based on priorities, events and resource availabilities, whereas pseudOS provides a basic-level multi-tasking functionality, upon which additional control features of the RTOS can be emulated as required.

3. pseudOS is a tool used to produce applications that meet prescribed program specifications regarding behavior patterns and response times. In a black-box analysis, emphasis is placed on what the application does, not how it does it.

Guidelines for pseudOS design

1. Functions are structured to perform single, well-defined “actions”. They may be regarded as “action-objects”, and typically contain local state using static variables. Taking this approach means there will be a larger number of functions that may be typical in the standard application. Although it is tempting to consider these “action-object” functions as tasks, this terminology is avoided to reduce confusion. Also, there is no real attempt to limit the number of “actions” in pseudOS, whereas in the RTOS environment there is typically an upper limit to the number of tasks that can be reasonably managed.

2. pseudOS is state-oriented, and applications are structured using a finite state machine approach. If you have not used FSM techniques, you will find that the ideas are very straightforward and easy to grasp. Not only are FSM design concepts intuitive, but it is the high level of determinism – never having to guess what state you are in, and what state you will be in next based on certain events – that make the methodology one of the most attractive and powerful tools available to the designer.

3. pseudOS states are action-based. A state is determined by what behaviors the system will exhibit, and these behaviors are controlled by the “actions” designated for that state. For each state, an “action list” contains the “actions” that will be executed to produce its desired functions and responses. The “action” functions are executed in continuous round-robin fashion until the state changes and a new “action list” becomes active.

Draft 1 101231 6 4. State changes are initiated by events processed by the “action” functions. pseudOS is not a pre-emptive system, meaning code execution is not scheduled by an outside resource (RTOS kernel) because there is not one present in the system. In this regard it is closer to a “cooperative” approach in which a function (task) can defer functionality to another function (task). However, this is a misleading analogy since the “cooperative” philosophy is also an RTOS concept, meaning it requires a scheduler which is part of an OS kernel.

5. The basic functional mode of pseudOS is an equal priority, round-robin execution process. This provides the basic multi-tasking capabilities that are frequently adequate to meet the requirements of an RTOS-like application. There is still a great deal of flexibility even within the perceived limitations of the RR approach, using flags and inter-action semaphores, system variables, counters and timers, interrupt service routines, action list execution orders, etc. If behavior specifically related to priorities or resource management is required, these can be emulated in a number of ways “on top of” the RR approach. Even in the minimal RTOS implementation, a set of baseline functionality is present, whether or not it is utilized. In pseudOS, functional code is introduced only as needed, maximizing the usage of scarce resources.

An Overview of the Projects

This book is organized to present the concepts of pseudOS in a progression of increasingly complex programs that illustrate the various levels of RTOS functionality that can be introduced at the application level.

Project 1 is a four-bit counter with a flashing display that is independent of the count rate. The count rate can be adjusted via input from a potentiometer. This application operates as a single state with three action-objects: a 4-bit binary counter, a count-flasher and an A/D input that sets the count rate.

Project 2 incorporates the counter/flasher/count-rate functionality of Project 1, and adds a zig- zag display and an in-and-out display. A push-button switches the display modes in a round- robin fashion. The potentiometer adjusts the count rate in counter/flasher mode, and the cyclic rates of the zig-zag and in-out dispays. This project is a three-state application (the three display modes), and six action-objects with the addition of these functions: switch monitor, zig-zag display driver and in-out display driver. The A/D input functions is modified to set the cyclic rate in addition to the count frequency.

Project 3 builds on Project 2 by adding two more push-buttons and two more states: a low 2-bit manual counter and a high 2-bit manual counter. The two new display modes selected by the buttons increment a binary count on the upper or lower two LEDs. The new buttons select the display mode on the first press, then increment the binary count on subsequent presses of the same button. The project now has 5 states with the addition of the two new display modes, and ten action-objects with the addition of two new switch monitors and two new display drivers.

Project 4 adds a more realism to the picture by presenting an application that monitors two photocells and controls a servo to direct a “virtual” camera to a detected source of movement.

Draft 1 101231 7 Rather than using Passive Infrared (PIR) modules, we will use our own filtering algorithms to interpret changes of light falling on the photocells to determine where best to aim the surveillance camera. Buttons are used to select LED or servo outputs, or both.

Project 5 demonstrates a multi-tasking environment with critical control functionality simulated with various display patterns on the LEDs, and the ability to support user interface functions, including the LCD module, without any perceptible impact on the high-priority functionality.

Overview of Project 5 Functionality:

1. Simulate high-priority process-control activity by continuously outputting patterns to the LEDs, as in previous projects. The patterns (flash/count, zig-zag and in-out), will automatically change states every 5 seconds. The flashing rate for the counting state, and the sequence rate in the other two states, are controlled by the potentiometer RA0.

2. Output the number of days since initialization, day of the week, time of day, and current temperature. Update once a second. Also display min and max temps measured since reset/initialization.

3. Piezo (PWM) buzzer: tick once a minute and chime on the hour. Play a sequence of notes when the database is reset and initialized. Do all of this without noticeable affecting the LED output process simulation.

4. Maintain a database of daily maximum and minimum temperatures. Create a new max/min record every day at mid-night. The temperature log database will be maintained in the external EEPROM and will not be erased on power-down or reset.

5. Allow user to review daily temperature logging records by stepping through the records forwards or backwards. Allow reset of database (initialization) with a specific key sequence. Play a musical sequence of notes when the database is reset.

6. Export a database report via the RS-232 port, so that it can be viewed and printed on a PC.

Draft 1 101231 8 Hardware Platforms & C Compilers

Projects 1 and 2 can be executed on a simple bread-board circuit or an inexpensive off-the-shelf production board. Fig 1 illustrates a test circuit - for convenience and to provide a specific example, we have illustrated the relevant portions of the MicroChip Low Pin Count (LPC) Demo Board and have included programs to run on the board with either the PICkit 2 programmer (which does not allow debugging), or the ICD 2 In Circuit Debugger with a debug header (PN# AC162061). For Projects 1 through 4, the code is compiled with Hi-Tech Software PICC Standard Compiler.

Fig. 1: Demo Circuit for Projects 1 & 2 (LPC Demo Board)

Project 3 requires two additional momentary switches that can easily be added to the breadboard circuit, or the prototyping area of the LPC Demo Board. Fig 2 shows the modified circuit.

Project 4 requires two photo-resistor inputs to available A/D inputs, and PWM output to control the servo, as illustrated in Fig. 3.

Project 5 is built on an unmodified MicroChip PICDEM 2 Plus Demo Board. The code is compiled on the MicroChip MPLAB MCC18 Compiler.

Draft 1 101231 9 Fig 2: Test Circuit for Project 3

Fig 3: Test Circuit for Project 4

Draft 1 101231 10 /----- Program analysis sections deleted -----/

Draft 1 101231 11

Fig. 1: Project 1 State Diagram

Fig 2: Project 2 State Diagram

Draft 1 101231 12 Fig 3: Project 3 State Diagram

We can also document the state machine specifications in table form:

Project 3 State Table:

In State Press Next State Action: 0 SW1 1 State Change 0 SW2 2 State Change 0 SW3 3 State Change 1 SW1 1 Increment Counter 1 SW2 2 State Change 1 SW3 0 State Change 2 SW1 1 State Change 2 SW2 2 Increment Counter 2 SW3 0 State Change 3 SW1 1 State Change 3 SW2 2 State Change 3 SW3 4 State Change 4 SW1 1 State Change 4 SW2 2 State Change 4 SW3 0 State Change

Draft 1 101231 13 Fig 4: Project 4 Data Flow

V-Delta - Serial Filters - 5V Step Input

2.5 2

2 r e t l i F

1.5 -

1

r e

t 1 l i F

=

V 0.5

0 1 2 3 4 5 6 7 8 9 10 Number of filter cycles

Fig 5: Step Function [Filter1- Filter2] Response

Draft 1 101231 14 Prioritization and resource management

High-priority interrupts:  Very fast response  Used for critical events or unique conditions outside of direct state machine control  Configures high-priority action-objects to monitor flags set by ISRs Avoid:  Any function calls due to minimal context saving  H/W resource access to avoid possible resource management conflicts

Low-priority interrupts:  Controls high-priority state machine scheduling (timer clock tick)  Supports function calls with full context saving  Can only be interrupted very briefly by high-priority interrupts Avoid:  Function calls that may interfere with (delay) system clock  Function calls that may cause reentrancy in the call chain

High-priority state machine:  Controls timing of all system events  Synchronizes critical-response action-objects  Monitors critical and fast-response hardware  Responds to flags set in high-priority ISRs  Schedules execution of lower-priority functions  Small action-objects optimized for maximize speed Avoid:  Reentrant function calls  Inter-dependencies with lower-order state machines  Using delays of any sort

Low-priority state machine:  Runs all idle/background non-critical tasks, user I/O, display, comm., etc.  Pre-empted by interrupt-driven high-priority state machine(s)  OK to use delays Avoid:  Inter-dependencies with high-order state machines  Using run-to-completion - use time-slice wherever possible

How to avoid resource contention

A fully-preemptive RTOS creates the problem of resource contention, because critical hardware- access procedures can be interrupted and replaced instantaneously with another function that may need access to the same resources. An example of a resource would be the I2C bus, which uses a standard query-response protocol for communication between the master and slave device.

Draft 1 101231 15 Let’s say that two different tasks both use an external I2C EEPROM to store data. Task 1 has initiated communications with the EEPROM when Task 2, which is also ready to fetch data, is scheduled to run. Even though Task 2 may be a higher priority, if it were to attempt to access the EEPROM, the bus would be forced into an unknown state due to interference. This could easily hang the entire system.

RTOS systems address these problems with the use of semaphores and resource management. Task 2 would be put into a WAIT state until the EEPROM resource was released by Task 1, at which time Task 2 would become READY. The OS kernel would schedule it into the RUN state as soon as possible. There are other problems, such as priority inversions and dead-locks that are also introduced as side-effects to these approaches. In the above example, a higher-priority task is blocked by a lower priority task, due to the resource issue.

The pseudOS action-lists that contain individual action-object collections for each state provide a logical means of organizing the application to avoid resource contention. Since the action- objects run in a designated sequence, there is a deterministic aspect to their execution that is not present in the pseudo-random pre-emptive task switching of the RTOS. This makes it easy enough to ensure that routine system housekeeping maintains all potential conflict areas in a known state.

In pseudOS it is also possible to organize the higher and lower-level state machines so that no resource contention is possible. The lower-priority state machine is subject to pre-emption by the high-priority state machine, but it is much easier to ensure that interference will occur between tasks by segregating them into foreground and background classes of functionality.

1. Design action-objects so that the action-lists defining each state avoid inter-dependencies and resource.

2. Design hardware manager functions through which action-objects communicate with a given resource via request and output queues.

Action-objects vs. Functions

All action-objects are functions, but not all functions are action-objects. An action-object is a function that is designed to have the following features relevant to the pseudOS environment:

 Preferably very small in size.  Very fast execution times  Designed to run efficiently, performing a specific task  Designed to run iteratively, rather than run-to-completion  Designed to run as an independent part of an integral whole  Contributes to the overall structure and intelligence of the system without having specific computational ability or organization on a stand-alone basis  Designed to be as generic as possible for use in as many combinations as possible

Draft 1 101231 16 In traditional state machines, individual states are characterized by specific functions whose functionality represents the behavior of the separate states. In pseudOS, states are typically represented by combinations of action-objects acting like neural clusters and emulating parallel processing. However, the pseudOS state machines can have as few as one function per state, depending on the requirements.

For example if a low-priority state machine needs to present a menu and query the user for an extended series of inputs, it would make more sense to simply write a run-to-completion function, and allow a high-priority interrupt-scheduled state machine to continue handling all of the critical requirements of the system in the background.

A simple example of the differences between a typical function or object-based design and pseudOS can be illustrated by the role the typical switch monitoring action-object plays relative to other functionality in the system. For example the SW2 monitor in Project 5: void A8_sw_2(void ){ static char prev_sw_state = OPEN;

if(SW2_CLOSE){ if(prev_sw_state == OPEN){ // only respond to a change prev_sw_state = CLOSED; // housekeeping gSw_2_flag = 1; // set global var } } else prev_sw_state = OPEN; }

In just a few lines of code, the action-object checks the OPEN/CLOSED state of SW2. It does not, however, simply set a global variable based on the current state of the switch – it filters the information to make it more useful for the other action-items that it is serving, by reporting only new switch closure events.

What if two action-objects are reliant on the SW2 information? This is typically not the case, since the state machine design is deterministic, and the switch closure would be relevant only to the user interface context active at the time. However, if it does become necessary to signal two or more action-objects, the best way would be to use separate flags.

Borrowing a concept from our friends in the RTOS world, we could use the flags as “events” designed to trigger specific “tasks”. Instead of setting the single gSw_2_flag, we could report to the individual action-objects. However, if we target specific action-objects by name with flags such as gSw_2_B4_view_recs, the variables become application-specific and lose their generic nature. To solve this problem we can use more generic flag variable names, relating to classes of activities that typically require switch input, e.g. gSw_2_menu_sel, gSw_2_set_time, gSw_2_state_chg. These flags are specific enough to target appropriate action-objects, but generic enough to be transportable.

Draft 1 101231 17 In this case, the A8_sw_2( ) action-object could trigger a range of responses on detection of a new event. It does not care who uses the information, nor does it need to know what state is currently active and what flags need to be set – it just sets them all. Of course this can lead to stale information being present when a new set of action-objects are switched in. However, by simply comparing gCur_state vs. gPrev_state with local state variables, an action-object can initialize its input environment the first time through execution following a state change.

These approaches allow action-objects to operate independently without concern of inter- dependencies and application-specific details. Designing the action-objects in the most generic and abstract manner possible allows them to be recycled throughout a wide range of states and applications.

Dial-an-application

Using action-objects, it is possible to use a 4- 8- or 16-position switch to derive completely separate applications from the same computational device. This would be done by selection at the state machine level, the system being comprised on n state machine systems, each of which having high- and low-priority state machine pairs if needed. Throughout the complement of the selectable applications, there is a possibility of a large overlap in the use of generic action- objects, thus providing a significant economy of resources to produce a wide range of selectable functionality.

Draft 1 101231 18 APPENDIX A: Pointers to Functions

This is a brief tutorial on declaring and using pointers to functions and creating arrays of pointers to functions. Prototypes for examples used below are:

void blink_LED1(void); void blink_LED2(void);

Declaring pointers to the individual functions: void (*ptr_1)(void) = blink_LED1; void (*ptr_2)(void) = blink_LED2;

Usage (calling the functions): ptr_1( ); // execute blink_LED1( ) ptr_2( ); // execute blink_LED2( )

Creating an array of pointers to functions: typedef void (*Fptr)(void); Fptr blinky[] = {blink_LED1, blink_LED2};

Or if you prefer not using the typedef, you can define the array directly: void (*blinky[])(void) = {blink_LED1, blink_LED2};

In either case, the usage is: blinky[i]( ); // call a void function with no arguments

You can also create a pointer to a function with arguments and returning a value, like this one: int blink_LED(char led_num, char off_on);

Declaring the pointer blink to the above function: int (*blink)(char, char) = blink_LED;

(Note - the form: int *blink(char, char); // wrong – no parens to define precedence …declares a function that returns a pointer to an integer.)

Using the pointer to a function with arguments, that also returns an integer: err = blink(led_num,off_on);

Usage if the pointer to the above function is indexed in an array: err = f_array[i](led_num,off_on);

Note that in the previous example, the function is anonymous and is referenced by index only, without a name. However, the argument types must match the function being called. Therefore it is a good idea to have a standardized argument list and return type for all of the functions listed in the array.

Draft 1 101231 19

Recommended publications