leater‎ > ‎

Software Structure

The project is arranged into two directories off the root; src and inc. You can probably figure out what is in each. Files which are common (e.g. Configuration files, make files etc) are kept in the root. By default (I.e. When using LPCxpresso) the application is built in either the Debug or a Release directory. If something in here is not clear, or you want to send a comment, feel free to send Feedback... 

Program Flow


The program starts off with the standard CORTEX-M0 initialisation flows; setting up the C environment for initialised variables etc. After reset control automatically passes to the ResetISR in cr_startup_lpc8xx.c to perform these functions. This file also contains the various interrupt vectors and the default fault handlers when there is an execution problem in your program. When this has been completed, control passes to main in main.c...this is the point at which your debugger will land you as the 'first step' of your program. The C environemnt is initialised and you're ready to roll.

The first stage of the user-level program is to configure the chip in SystemCoreClockUpdate in system_LPC8xx.c...by default embedded processors come up with very conservative settings, so it's the job of this routine to set up the CPU more appropriately for the environment it's to be used in.

Once the chip is sane we can start to initialise each of the independent subsystems. They all have a xxxInit function, so we call those in turn. Of course, some functions will depend on other functions already being initialised (you can't flash LEDs until you've got timers, for example) so the order in which these routines is called is pretty important...if you get it wrong the normal consequence is to land up in the HardFault_Handler in cr_startup_lpc8xx.c, which is the CORTEX's not too friendly way of telling you you've got it wrong (a memory access to a non existent location, or trying to execute an instruction that isn't defined, will both land you into this sin-bin).

Main Loop

Once everything is initialised then we enter into the main loop. The entire system is event driven, which means that nothing happens unless there's an event - these are processed as interrupts (in this system the interrupts are from the UART, SPI and Timer). All interrupts post an event back to the 'base layer' via the flags system.... In addition, the CORTEX has a nice feature that it will sleep until an interrupt arrives with the _WFI instruction, and save power while it's doing it...so the overall sequence is that we just perform a _WFI until the flags are non-zero, then we process the flags that are set before going back to sleep again. Of course, one event can cascade into several others, so there's usually a flurry of activity in response to an initial flag being set...you can trace these flags if you want to see how the system is really behaving.

In an environment with an operating system we might typically model each of the separate functions as a task or thread....that's more difficult when you've only got a very limited amount of resource to play with, so we take a much more lightweight, collaborative approach. Any set flag results in the appropriate routine being called in the appropriate module directly from the loop in main - most of the flags have a 1:1 mapping with routines, the only exception is the timers, which are explained in their own dedicated section.

Individual Modules

The main modules in the system are described in the following sections - not every module is described, just the ones with the more complex functionality. Externally avilable routines in a module are preceeded by the module name (e.g. timerDispatch, ledSetState etc.).

Timers (timers.c)

Timers are an extremely important part of any realtime system. In this system any module can define a timer of type timerType. This should have a static scope, otherwise bad things happen. The timer needs to be initialised before it can be used. Once it's been initialised then it can be set to any desired number of mS. In a larger system you would typically include the address of the function to be called in the timer, but the M0 is pretty inefficient with function pointers and such practices don't jive we'll with the MISRA-C guidelines, so instead the individual routine to be called is hard-coded in timer dispatch.

Internally timers are represented as a linked list with the next one to mature being stored in timerHead. The M0 interval timer will generate an interrupt when this time has elapsed, and a flag will be set. At the same time the interval timer is restarted (to accommodate the time spent servicing the timeout). This flag will result in timerDispatch being called, the timer event serviced and the next timeout being scheduled.

The rest of the timers module is concerned with the manipulation of the linked list and the removal/addition of timers. One particular gotcha is that there is no protection on the reading of the timer value, so we have to read it more than once to make sure we've got a stable reading before we can use the returned value.

Flags (flags.c)

The flags handler is not a big lump of code, but it's the nearest thing to an operating system functionality that there is. Flags are posted from either an interrupt or the base level, but are only processed in the base level. Concurrency guards are provided by means of disabling and enabling interrupts at the appropriate times.

Brownouts (bod.c)

When the voltage supply to the CPU is too low, this is known as a brownout event...this might be because the power is in the process of going off, or just because the battery is low. The only negative consequence of brownouts in this application occurs if we are writing to the flash when the power fails...that can result in corrupted flash. To avoid this we set an indication when a brownout has been detected...no new flash process wil be started while that indication is set. After 200mS the brownout indication in cleared (that assumes we've not lost power completely by then, of course!). Power fails relatively slowly because of the capacitors in the system so there is plenty of time for a flash write in progress to complete while power is slowly fading, so this technique prevents flash corruption.

Command handler (command.c)

The command handler is responsible for the interaction with the user. It receives and processes command requests, sets variables and can report process black to the user. There is nothing particularly complex in this module - just a reasonable amount of string handling in a constrained environment.

Logging (log.c)

I wanted to be able to log to non volatile store while the system was running, but with only 16K of flash avilable in total we can't afford to be profligate with memory, so the majority of the logging module is concerned with crushing up log records to take the minimum amount of space.  The log module also contains the code to implement the non-volatile store for system configuration.

It's slightly unusual to use flash for configuration, it's more normal to use EEPROM, but EEPROM costs money when it's not integrated on the chip and provided we're not writing to it continuously (we're not) then it will last perfectly well.

PID Controller (pid.c)

This module implements the actual PID algorithm. Its implementation is pretty straightforward and doesn't need any special explaination except to emphasise that it's all done using integer math...floating point doesn't have much of a place on a small CPU like this.

Profiles (profile.c)

The profile manager executes the selected profile, one step at a time, optionally reporting as it goes.  It operates by changing the setpoint in the state machine according to the requirements of the step in progress,

State Machine (statemachine.c)

This is something of a misnomer now as most of its functionality has now been completely subsumed into other parts of the system. It is responsible for maintaining the set point that has been requested and also for 'bunching up' individual readings for reporting into the log.