Embedded Control Systems Design/Design Patterns

The Wikibook of


Embedded Control Systems Design


Programming for real-time environments is hard and needs a good understanding of the complete system, hardware as well as software. In the ideal case, every part of the system is well described and easily understandable, but the real world seldom allows this ideal situation. Therefore, the system should be robust against the uncertainties in the system. design patterns are a useful support for all designers: they are generalized solutions to commonly occurring problems, based on experience of what has worked already in the past in a large number of systems. Patterns are also appropriate to create portable code that may be reused and adapted in several applications.

Design patterns always come with a certain context: they are the result of a number of design forces that each pull the design in several directions, and for which the pattern provides a balanced solution. However, the forces in different systems may be so different that also the resulting trade-off gives rise to a different pattern.

Software patterns without need for an operating system

edit

When programming for micro-controller (like a PICmicro), programs will often be written in machine language or low level languages like assembler. Assembler programs tend to be smaller and more efficient with active memory. Nevertheless it's also possible to use higher level languages such as C when the controller has enough resources. The article "Using A High level Programming Language" has a short list of the advantages of using a higher level language over assembler. (Even higher level languages have those same advantages over C, but they are rarely available on small microcontrollers).

In this section we will only discuss design patterns used when programming in assembler. These design patterns seem really basic, but good understanding of them is important. The knowledge of assembler is not needed, but a good help to understand this section.

Subroutines

edit

When you often need to do same operations, you can use subroutines. By putting a value in the register you can give a parameter to the subroutine. The result of the subroutine will be also stored in the register. Here we give a simple example of a subroutine:

Start	movlw	02h		; Put the value 02h in the working register W
	movwf	PORTA		; Move the value from the working register W into memory location PORTA, this will turn on a led
	call 	Delay		; Call subroutine
	movlw	00h		; Put the value 00h in the working register W
	movwf	PORTA		; Move the value from the working register W into memory location PORTA, this will turn off a led
	goto	Start
	
Delay				; Subroutine Delay
Loop1	decfsz	COUNT,1		; decrease value of COUNT, and skip next instruction if zero
	goto	Loop1		; goto label Loop1
	return

Data Tables

edit

Sometime you need a structure where you can store values in. Or you need to do a conversion but it's easier to get values from table. Then the use of a data table is handy.

On a PICmicro, constant data tables (in Flash memory) are a kind of normal subroutine. When the subroutine is called, the value in the working register will be used to determine the amount of instructions the processor has to skip. This is done by increasing the 'Process counter'? The process counter is the address of the instruction that is currently executed. Increasing the counter will make the processor skip the next instructions. A little example, in the data table we have the order in which 5 leds (5 bits) have to blink.

Start	movlw	05h		; Put value 5 in the working register (this is initialization value) 
	movwf	COUNT1		; Assign the value of the working register to COUNT1

Loop1	call	Table		; Look the pattern in the table 
	movwf	PORTA		; Move the pattern of the working register to the led register
	call 	Delay		; Call subroutine
	decf	COUNT1,F	; Decrease, put a byte in the COUNT1 register
	btfsz 	STATUS,Z	; Test if overhead
	goto	Loop1		; No, get next pattern	
	goto	Start		; Yes, run the program from the start again

Delay				; Subroutine delay
Loop2	decfsz	COUNT2,F	; Decrease value of COUNT1, and skip next instruction if zero
	goto	Loop2		; Goto label Loop2
	return

Table   addwf	PCL		; Add value to get pattern
	retlw   01h		; Return pattern 00001b, LED1 - ON
	retlw   02h		; Return pattern 00010b, LED2 - ON
	retlw   03h		; Return pattern 00100b, LED3 - ON
	retlw   04h		; Return pattern 01000b, LED4 - ON
	retlw   05h		; Return pattern 10000b, LED5 - ON
The article PIC "Microcontroller Memory Methods: Tables" has detailed information on various ways to access a table, the "crossing a 256 byte page boundary" gotcha (which always occurs with tables longer than 256 bytes, and sometimes occurs even with very short tables), and work-arounds.
The article "File Select Register" discusses the file select register, which is used for reading and writing arrays of values in RAM.

Interrupts

edit

FIXME: write something about basic interrupt programming

We talk more about interrupt servicing in a later chapter, interrupt servicing.

Watchdog timer

edit

The watchdog timer is an internal timer in the processor. The purpose of this timer is to avoid that an embedded controller gets stuck.

Implementation

edit

The principle of a watchdog timer is simple, it resets the embedded controller after a predefined time. But this only happens if the program didn't reset the watchdog timer first.

Therefore the program should reset the watchdog timer on a predefined time. When the watchdog timer doesn't get reset on time, the program probably got stuck and the micro controller will reset itself. See also: RTOS watchdog timers, Watchdog timer.

Design patterns & Real-time programming for embedded devices with OS

edit

Assembler programs are often hardware specific and not very portable and modular. This makes programming of big complex system rather difficult. This can be solved by using an 'abstraction layer' that handles the processor and the hardware interfacing. This layer is also known as 'the kernel' of a system. The kernel also takes care of the hardware specific calls and task management, which allows to stay focused on the most important task; Making a real-time program. Of course more time and more memory is needed, but it makes the program more flexible and modular. Also the use of higher programming language makes the code also portable and easier to debug.

In the next section we will describe some important design patterns:

Configuration & execution

edit

Aim & Problem

edit

The most important difficulty in real-time programming is knowing that a specified operation will happen on a defined moment. Therefore it's important to know exactly how long each operation will take.

Implementation

edit

This pattern consist of 2 parts. In the first part all operations that have no predictable execution time are done. This includes memory allocation, hardware initialisation and object creation. This part is called the Configuration of the system. When the configuration is completely done, the main loop can be started.

More about: Logical vs. Physical architecture

More about: C++

Consequences

edit
  • All memory allocation should be done _before_ starting.

Asynchronous & Synchronous design pattern

edit

The asynchronous/synchronous design pattern is also known as:

  • interrupt service routine / deferred service routine (ISR/DSR)
  • first-level interrupt handler / second-level interrupt handler (FLIH/SLIH)
  • fast interrupt handlers / slow interrupt handlers
  • top-half of interrupt / bottom-half of interrupt

When data arrives on a input, the processor will execute a predefined interrupt function. Sometimes this data needs to be processed before it can be used. The interrupt function should be kept as short as possible to allow new data to arrive, and to avoid to miss this new data (because of the interrupt lock).

Implementation

edit

In this design pattern the data handling happens in 2 phrases:

  • the interrupt service routine (ISR): This is the function that handles the data when the interrupt comes in. Often it just reads the data on the input and writes it in a buffer. It runs with interrupts disabled, and ends with a "return from interrupt" that enables interrupts.
  • the deferred service routine (DSR): This is a function that runs continuously in the background and that processes the new data. It runs with interrupts enabled, and ends with a normal "return".

Consequences

edit
  • No data lost
  • Sometimes it's possible that more data comes than needed. The DSR function will then only process the needed data.

Event handling

edit

Implementation

edit

Consequences

edit

Monitor

edit

Implementation

edit

Consequences

edit

Finite state machine pattern

edit

This pattern is also known as the State pattern or the FSM pattern

A system often consist of different states or phases, where each state has to complete some actions. Therefore the embedded controller should react differently on inputs depending state of the system.

Implementation

edit

This can be done by programming a FSM, this is a class where the behaviour of the function differs depending of the state of the system.

We discuss finite state machines in more detail in a later chapter, Embedded Control Systems Design/Finite State Machines and Petri Nets

Taskcontext

edit

Implementation

edit

Consequences

edit

Problems of real time programming

edit

Deadlock

edit

A deadlock is a situation wherein two or more competing actions are waiting for the other to finish, and thus neither ever does. It is often seen in a paradox like 'the chicken or the egg'.

In the computing world deadlock refers to a specific condition when two or more processes are each waiting for another to release a resource, or more than two processes are waiting for resources in a circular chain (see Necessary conditions). Deadlock is a common problem in multiprocessing where many processes share a specific type of mutually exclusive resource known as a software, or soft, lock. Computers intended for the time-sharing and/or real-time markets are often equipped with a hardware lock (or hard lock) which guarantees exclusive access to processes, forcing serialization. Deadlocks are particularly troubling because there is no general solution to avoid (soft) deadlocks.

This situation may be likened to two people who are drawing diagrams, with only one pencil and one ruler between them. If one person takes the pencil and the other takes the ruler, a deadlock occurs when the person with the pencil needs the ruler and the person with the ruler needs the pencil, before he can give up the ruler. Both requests can't be satisfied, so a deadlock occurs.

Priority inversion

edit

In scheduling, priority inversion is an antipattern. Priority inversion occurs when a low priority task holds a shared resource that is required by a high priority task. This causes the execution of the high priority task to be blocked until the low priority task has released the resource, effectively "inverting" the relative priorities of the two tasks. If some other medium priority task, that does not depend on the shared resource, attempts to run in the interim, it will take precedence over both the low priority task and the high priority task.

In some cases, priority inversion can occur without causing immediate harm—the delayed execution of the high priority task goes unnoticed, and eventually the low priority task releases the shared resource. However, there are also many situations in which priority inversion can cause serious problems. If the high priority task is left starved of the resources, it might lead to a system malfunction or the triggering of pre-defined corrective measures, such as a watch dog timer resetting the entire system. The trouble experienced by the Mars Pathfinder is a classic example of problems caused by priority inversion in realtime systems.

Priority inversion can also reduce the perceived performance of the system. Low priority tasks usually have a low priority because it is not important for them to finish promptly (for example, they might be a batch job or another non-interactive activity). Similarly, a high priority task has a high priority because it is more likely to be subject to strict time constraints—it may be providing data to an interactive user, or acting subject to realtime response guarantees. Because priority inversion results in the execution of the low priority task blocking the high priority task, it can lead to reduced system responsiveness, or even the violation of response time guarantees.

Immunity aware programming

edit

Embedded Control Systems Design

Further reading

edit

References

edit

Links: http://www.eventhelix.com/RealtimeMantra/Patterns/

Links: http://technology.niagarac.on.ca/staff/mboldin/18F_Instruction_Set/