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 those you want before you build the application. MPLAB® X Integrated Development Environment (IDE) 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
Therefore, 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 this function, as well as 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. Some of Microchip’s demos will do just that. Conversely, 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.
system_config.h
- Defines all static build options
- Is included by all libraries
system_init.c
- Processes configuration bits
- Initializes all libraries and applications
system_tasks.c
- Calls all polled system logic
- Maintains system state
system_interrupt.c
- Implements all interrupt vector stubs
- Calls all Interrupt Service Routines (ISRs)
You might have also noticed that we snuck in one more way that your system logic can be driven — by interrupts (instead of just being polled). However, 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). Therefore, 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_Task 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 in order 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 are used. Thus, the system is completely configurable.