Embedded Software Parts
An embedded application can be broken down into a number of different sections:
- Build Configuration Options are static definitions that select fixed parameters and selections that are known at build time. These items can be captured in one or more configuration files.
- There is always a certain amount of code required to initialize the peripherals and the overall system. This code will be different depending on which processor, board, and libraries are used. Likewise, you will probably need to initialize your application logic.
- Finally the application, peripherals, and system level logic that defines the behavior of your overall system must be developed.
Segmenting Code to Enable Configurability
Embedded code can be segmented into parts to enable configurability. First, let’s factor out the build-time configuration constants. If we do that, we can have different build configurations for the same project. Examples of different build configurations might include:
- Engineering prototype configuration (includes debug code)
- Production configuration
- Full featured (expensive) product version
- Cost reduced product version
Maintaining separate configurations can be as simple as maintaining different configuration files and selecting the ones you want before you build the application. MPLAB® X provides an easy way to do this using project configurations. You can think of a project configuration as a project within a project. Each project configuration can exclude specific files from the build process. You can even choose different devices and use different compiler versions and optimizations between configurations.
If we also factor out the peripheral and system support into libraries that we at Microchip can implement ahead of time, then your application only has to implement the logic for the desired application behavior. However, since we can't know ahead of time which libraries, peripherals and other hardware you may want to use, there will always have to be some code that needs to be implemented as part of the specific configuration.
This allows Microchip to write peripheral, middleware, and any number of other libraries that make your life easier without having to know the specific processor, peripherals or configuration of libraries that you plan to use. It also allows you to:
- minimize the amount of code that you must implement
- focus on your application code instead of the processor peripherals
System Configuration Files
So, what we end up with is a set of files that make up a specific configuration of a system or application. The top-level “main” function can be extremely simple, and both it and your application logic can stay exactly the same from one configuration to another. If you want, you can have multiple configurations that use the same basic application logic. Indeed, some of Microchip’s demos will do just that. Or, you can have only one single configuration. It’s completely up to you. However, one benefit of factoring out the configuration-specific code like this is that Microchip can provide helpful development tools that make it easier to create the configuration that is right for you.
- Defines all static build options
- Is included by all libraries
- Processor configuration bits
- Initializes all libraries & applications
- Calls all polled system logic
- Maintains system state
- Implements all interrupt vector stubs
- Calls all Interrupt Service Routines (ISRs)
You might also have noticed that we snuck in one more way that your system logic can be driven – by interrupts (instead of just being polled). But, since different processor families implement their interrupt vectors (their “raw” ISR functions) in sometimes wildly different ways, these ISRs cannot easily be implemented in libraries without requiring different libraries for different part families (even if the peripheral module is exactly the same). So, the raw ISR functions must be implemented as part of the system configuration in a file called “system_interrupt.c”. However, the “raw” ISR stub can call a library routine that implements the actual logic necessary to make the peripheral run correctly.
System Configuration Example
Here is a brief example of what a set of system configuration files might look like:
When the program is built, any static configuration items are defined in “system_config.h” and all library and application code includes the same build options.
After the system starts running, a very simple main function calls a system-wide initialization routine (SYS_Initialize) which is implemented in “system_init.c” as part of the configuration definition. The “SYS_Initialize” function must call the initialization routine for every library or application module in the system.
After that, the main function simply drops into the usual system-wide “super” loop and calls a system-wide “tasks” function (SYS_Tasks) which must also be implemented in “system_tasks.c” as part of the system’s configuration definition. The “SYS_Tasks” function must call any polled library or application code necessary to keep the entire system running.
If the system has any interrupt-driven code, the “raw” interrupt vector stubs are implemented in “system_interrupt.c”as part of the system configuration. Each vector stub must then either implement or call the appropriate library’s interrupt routine.
Since all library or application code can be isolated from all configuration code, neither the libraries nor the application(s) have to change to change a system configuration. That configuration can include static build options, initialization code (and thus, the choice of which libraries or apps are initialized), and any polled or interrupt-driven libraries or apps chosen to run in the system, as well as which processor and board is used. Thus, the system is completely configurable.
To view the following video full screen, click on the video title (this will view on YouTube).