Overview#
The programming model applies only to projects that are generated by Simplicity Studio v5's project configuration tool. As of this release this is only applicable to Bluetooth and Proprietary SDKs. This section explains the details of how a project generated by Simplicity Studio v5's project configuration tool works, and how you integrate your application to it. This covers both new projects and projects that are migrated to the new SDK version.
The programming model covers several important areas such as hardware initialization, software initialization, and periodic action processing. It covers both baremetal and kernel based applications. For instance, in case of a baremetal application, this programming model will allow you to add processing action functions to the main loop.
If you are migrating an existing application to this new version, be aware that this programming model now handles some hardware initializations (hardware clocks, DCDC, etc) and some software initializations (kernel, stacks, etc) that you may have had to do manually with previous versions.
Using this programming model for your application is important as it will ensure proper hardware initialization and proper SDK software modules initalization. Using this programming model will also ensure proper power management, as power management has to be a centralized service (power requirements can come from different requesters). For more information on power management, refer to Power Manager section.
Note: The SDK Programming model is compatible with GSDK 3.0 and greater versions.
Features#
Initialization#
A single function, sl_system_init()
, is provided to you. This function
initializes all the SDK modules that are selected in your project.
Action Processing#
Some of the SDK modules (mostly services and wireless stacks) have actions to process periodically. Functions are also provided to help you with that. The action processing however differs depending on if you are using a kernel or if you have a bare metal application.
Bare Metal#
If your application is bare metal, a function called
sl_system_process_action()
is provided to you. This function contains all the
SDK modules "step" functions that must be called in every iteration of the super
loop. You must call this function from your super loop.
When Using a Kernel (RTOS)#
If you selected a kernel in your project, the SDK modules that have to process
periodic actions will automatically create task(s) during initialization. The
only thing you must do is start the kernel by calling the function
sl_system_kernel_start()
. You should ideally also create any task you might
need before calling this function.
SDK Component Catalog#
The SDK component catalog is a simple header file named
sl_component_catalog.h
that you can include from anywhere in your project.
This file contains #define entries for the different SDK components available
in your project. This allows you to enable/disable portions of code depending
on whether a SDK component is present in your project or not.
Usage Examples#
These are examples of main functions that use the functions described above that you can use as a reference.
Bare Metal#
#include "sl_system_init.h"
#include "sl_system_process_action.h"
#include "sl_component_catalog.h"
#ifdef SL_CATALOG_POWER_MANAGER_PRESENT
#include "sl_power_manager.h"
#endif
int main(void)
{
// Initialize Silicon Labs SDK modules.
sl_system_init();
// TODO: Add required initializations for your application
while (1) {
// TODO: Add required "step" actions for your application
// Process periodic actions of Silicon Labs SDK modules
sl_system_process_action();
#ifdef SL_CATALOG_POWER_MANAGER_PRESENT
// Let the CPU go to sleep if the system allows it.
sl_power_manager_sleep();
#endif
}
}
When Using a Kernel (RTOS)#
#include "sl_system_init.h"
#include "sl_system_kernel.h"
int main(void)
{
// Initialize Silicon Labs SDK modules.
sl_system_init();
// TODO: Add required initializations for your application
// TODO: Create kernel tasks required for your application
// Start the kernel.
sl_system_kernel_start();
}
Initializing your Device#
When generating your project on a Silicon Labs kit, the project configuration
tools will automatically add a component called Device Initialization. This
component provides basic hardware configuration functionalities. These
functions will automatically be called when calling sl_system_init()
.
When generating a project for a custom board, the Device Initialization module can be manually selected.
You can customize these basic configurations by modifying the configurations
available in the Device Initialization
software component. For more information on this
component, see Device Initialization.
Atomic Section General Considerations#
Overview#
The em_core
module provides two types of basic critical sections (interrupt
masking), the critical sections, and the atomic sections. The critical sections
will always mask all the interrupts (except the non-maskable ones) by using the
PRIMASK
register. On the other hand, the atomic sections can mask the
interrupts by using either the PRIMASK
or BASEPRI
register. Both methods are
supported and configurable through the em_core
CORE_ATOMIC_METHOD
define.
By using the PRIMASK
register, the atomic sections will behave like critical
sections.
Using the BASEPRI
register allows you to only mask interrupts based on their
interrupt priority. That is to say, only interrupts with equal or lower
priority (higher number) than the BASEPRI
register value will be masked in
an atomic section.
The default value for the BASEPRI
register used to mask interrupts in an
atomic section is 3. Otherwise, the BASEPRI
value is kept to 0 to allow all
interrupts.
The benefit of the atomic sections over the critical sections is that it won't mask interrupts that you would consider of very high priority and for which you want to have minimal latency. However, the atomic sections can obviously not be used to protect access to resources that would be accessed by these very high priority interrupt's handlers.
*Note that the BASEPRI
register is not available on the devices with a
Cortex-M0+ core. These devices will always use the PRIMASK method for atomic
sections regardless of how you configure them. If Device Init NVIC is present
in your project it will still set the interrupt priorities as mentioned in the
Device Init NVIC module section.
Kernel-aware vs. non kernel-aware interrupt considerations#
The RTOSs currently provided (Micrium OS and FreeRTOS, for example) implement
the principle of kernel aware vs non kernel-aware interrupts. The threshold is
defined by the BASEPRI
register value. That is, when having to protect its
internal resources, the kernel will always mask the interrupts using atomic
sections. This means any very high priority interrupts will be able to
interrupt the kernel. That also means you CANNOT use any kernel API from these
very hight interrupt's handlers (you cannot post a semaphore, for instance).
When using FreeRTOS, interrupts priorities MUST be configured either manually or
automatically using the Device Init NVIC module.
This is because when an MCU supports BASEPRI
, FreeRTOS will use the
configMAX_SYSCALL_INTERRUPT_PRIORITY
setting to configure BASEPRI
to
protect its internal resources from concurrent access.
Device Init NVIC Module#
The Device Init NVIC component initializes all interrupt handler
priorities via the sl_device_init_nvic()
function. The Device Init NVIC
module is part of the Device Init Module and is pulled in automatically in your
project if the Device Init module is selected.
If the Device Init NVIC module is present in your project, the BASEPRI
register will be used by default for the em_core atomic sections (unless the
BASEPRI
register is not available or CORE_ATOMIC_METHOD
is defined outside
em_core. See section
Backward Compatibility with em_core Configuration Method.
The devices with Cortex M3, M4 or M33 core have at least 3 bits in the NVIC register to encode a priority, therefore the priority range is 0-7.
On devices with Cortex-M0+ core, only 2 bits are available. The priority range is therefore 0-3.
The em_core module defines the following values:
Define | Value for Cortex M3, M4 and M33 | Value for Cortex M0+ |
---|---|---|
| 5 | 1 |
| 0 | 0 |
| 7 | 3 |
sl_device_nvic_init()
will therefore initialize all interrupt priorities
(except fault handler) to CORE_INTERRUPT_DEFAULT_PRIORITY
. Since the default
priority is lower than the BASEPRI
value used in atomic sections, every
interrupt is disabled by default during atomic section. After Device Init NVIC
initialization, it is up to your application to call NVIC_SetPriority()
to
change the default priority of an Interrupt Handler according to its needs.
Any modification to an interrupt priority should be done carefully since it can introduce behaviour changes in your application difficult to pinpoint.
Backward Compatibility with em_core Configuration Method#
Before the introduction of the Device Init NVIC module, em_core
used
PRIMASK
by default for atomic sections. To change that behaviour, your
application had to re-define the CORE_ATOMIC_METHOD
configuration through a
-D compiler flag or by creating an emlib_config.h
file with the new
CORE_ATOMIC_METHOD
value (See your device em_core
documentation for more
details). To keep the backward compatibility with this method, a redefinition
of the CORE_ATOMIC_METHOD
define will take precedence over everything else.
This means that even if the Device Init NVIC module is present, if the
CORE_ATOMIC_METHOD
is redefined by the application, it's the value of this
redefinition that will be used by em_core
.