This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note:It is recommended to download the Kernel 201 Instructor Application & Electron App and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Kernel 201 Instructor Application
The instructor application is designed to be run by the instructor of a Kernel 201 class, or in the case of someone following along this blog post, by a user. The application communicates via Proprietary Wireless with all of the Thunderboard Sense 2s running the Kernel 201 Application in a one-to-many fashion. When the instructor application sends out a command, it acts as a broadcast to all devices listening. Since the Thunderboard Sense 2s are running Dynamic Multiprotocol, it is possible the radio may be operating in Bluetooth mode when the instructor application broadcasts a message. To mitigate this, the instructor application will actually transmit out the same message multiple times in an attempt to ensure all boards receive the message. The instructor application also can receive data from all Kernel 201 Applications. This is why the Kernel 201 Application only transmits data once every 10 seconds, rather than whenever it receives commands.
Note: It is worth noting that even with the multiple broadcasts, it is still possible for a Kernel 201 Application to miss a Proprietary Wireless message. In a real-world application where missing messages is not acceptable, a basic transmit-ack scheme would help or the use of a more complex wireless stack such as SiLab’s Connect Networking Stack. For this application, in order to keep the wireless portion simpler, it is acceptable for messages to be missed.
The entire instructor application acts as a translator between the Electron App and the Proprietary Wireless stack. When a user sends a command in the Electron App, a LAB_MSG_s packet is built up in the Electron App and sent to the instructor application via serial. The instructor application then takes that data and transmits it out using the RAIL API. When data is received via Proprietary Wireless, the instructor application receives the RAIL packet and then sends the data it received via serial to the Electron App. This allows the instructor application to operate without knowing too many details about the command packets.
Proprietary Wireless Task
The Proprietary Wireless task design in the instructor application is slightly different than the Proprietary Wireless task in the Kernel 201 Application. In the instructor application the Proprietary Wireless task only handles the receive events. Transmit events are triggered by the VCOM task in the instructor application.
RAIL interrupt fires and the labPropRadioEventHandler() function is called. This function determines that a packet has been received, tells RAIL to hold the packet and sends the packet pointer to the Proprietary Wireless task via a Task Message Queue to be processed.
The labPropRxTask() runs due to the message being received and calls labPropRadioRxData() to process the RAIL packet.
The function labPropRadioRxData() toggles the LED to signal a packet has been received, then it uses the RAIL Peek function to pull out the data from the packet. The use of the peek function replaces the need for a memcopy or similar to pull data from the packet. After the data has been read from the packet, the RAIL packet is released.
The function labVCOMTx() is called to send the data that was pulled out of the RAIL packet to the Electron App. The function then puts the data into the transmit ring buffer and triggers the VCOM transmit where it sends one byte at a time. After every byte is transmitted, the interrupt fires and if there’s more data in the ring buffer to send, the next byte is sent. If there is no more data to send, the transmit operation stops.
After the data was loaded into the ring buffer, the labVCOMTx() function returns while the transmit is occurring and the Proprietary Wireless task goes back to pending on the Task Message Queue.
VCOM Task
The VCOM task takes in data from the Electron App over serial and transmits it to the Kernel 201 Application via Proprietary Wireless. The data received from the Electron App comes in the format of two sync bytes, two bytes for the Task ID, a LAB_MSG_s packet and then two end sync bytes. A state machine is used in the UART interrupt to handle the received data. Once a full packet has been received, a semaphore is posted to signal to the VCOM task to signal that it is time to transmit the data received. The VCOM task takes the data received and calls labPropTx() where the data is loaded into the RAIL transmit queue. After the data is successfully transmitted, the task goes back to pending waiting for another message to send.
Kernel 201 Instructor Electron App
The Electron App is a cross-platform desktop application that communicates via serial to the Kernel 201 Instructor Application. The app allows you to interact with all Thunderboard Sense 2s running the Kernel 201 application.
To get started, you need to have npm installed on your machine and have the Kernel 201 Instructor Electron App source code from the Kernel 201: Project Resources page.
From the electron app directory run the following commands:
npm install
npm start
The install command will download all necessary packages for the application. The command npm start will run the application. There is also the option to generate executable packages for both Windows and Mac via the following commands:
npm run package-win
OR
npm run package-mac
Windows packages can only be generated on Windows and Mac packages can only be generated on Mac. The advantage of generating packages is you do not need to have npm or node installed on a machine when a package is used.
Using the Kernel 201 Instructor Electron App
The first screen that should appear is the Scan Serial Ports window. Make sure the instructor application board is plugged into your computer and click scan.
After scanning you should see at least one device. On Mac’s the SiLabs boards show up as /dev/tty.usbmodem and on Windows they will show up as COM. There is a filter in place to not show every serial device connected in an effort to make this simpler. If you find that you can not find the device, look at the file serial.js located in the app/js directory and remove the Silicon Labs filter.
Note: If you have the Blue Gecko and the Thunderboard Sense 2 connected to the same computer, both will show up. Be sure to select the right device!
Once connected to the serial device, you will be presented with the window above. To view all Kernel 201 Application devices, click View Devices. To send commands to all Kernel 201 Application devices, click Control Devices.
Under View Devices, any Kernel 201 Application devices should show up. Remember, those boards only transmit their status once every 10 seconds so it may take a little while for them to appear. If you make changes to the board via the WebBluetooth App, those changes should eventually be reflected here as well.
To send commands to the boards, go to the Control Devices window. Here you have the ability to configure the LEDs and then either send it once or send it repeatedly. If you have the WebBluetooth App open and connected to the Kernel 201 Application, as you push changes out via the Electron App you should see the changes reflected in the WebBluetooth App immediately.
Final Thoughts
The instructor application and Electron App provide a simple way to interact with the Kernel 201 Application via Proprietary Wireless. While the response time is very slow compared to Bluetooth and the WebBluetooth App, the Proprietary Wireless provides a way to communicate with more than one board at a time which can be extremely useful.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note:It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
Up until this point, all of the tasks covered in this blog series have been for wireless communication and the system watchdog. This post will cover the two user interface tasks: LEDs and buttons. These tasks are simple interfaces that take advantage of some of the hardware on the Thunderboard Sense 2. In a real-world application, these tasks would be just part of a number of application tasks that may interact with other sensors, user input or even other processors.
LED Task
When the LED Task is first created, there are two sets of LEDs that are initialized. The Thunderboard Sense 2 provides a simple red and green LED controlled by GPIO pins, but it also has RGB LEDs on both sides of the board. The RGB LEDs require the use of a hardware timer to control the color, so they can show any color you wish. To keep the lab simpler, the LEDs only offer red, green and yellow since those colors are available on both the GPIO LED and the RGB LEDs.
The LED Task is structured like all other tasks in the system (except for the watchdog task) in that it pends on a task message queue to perform an action.
The Task Message Queue processes two events and one RTOS error. These events are:
RTOS_ERR_TIMEOUT
LAB_Q_ID_MSG
LAB_Q_ID_LED_TMR
RTOS_ERR_TIMEOUT
As part of the software watchdog, the LED Task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the LED Task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_MSG
When another task in the system wishes to send a command to the LED Task, a lab message must be sent to the LED Task via the Task Message Queue. In most cases, this is either the Bluetooth or Proprietary Wireless task relaying LED control messages.
After a valid command message is successfully processed, an update is sent to both the Bluetooth and Proprietary Wireless tasks to alert them that the LEDs have been changed. The Bluetooth Web App will immediately reflect these changes due to the use of Bluetooth notifications. The Kernel 201 Instructor Application will be delayed on updating the LED status due to the Proprietary Wireless task transmitting on a fixed interval.
LAB_Q_ID_LED_TMR
When the LED Task is sent a command to blink the LED, a software timer is enabled. When the Micrium OS Software Timer expires, a callback function is executed similar to how an interrupt may trigger an interrupt routine. The software timer callback is treated similar to an interrupt function in that the code must be short because the callback function is executing out of the software timer’s callstack. Rather than controlling the LEDs from the callback, a message is sent to the LED Task via the Task Message Queue sending the message LAB_Q_ID_LED_TMR to signal that a software timer timeout occurred. The LED Task then knows based on the signal from the timer callback that it should blink the LEDs.
Button Task
After the Button Task is created, it initializes the two push buttons on the Thunderboard Sense 2. The Button Task is structured like all other tasks in the system (except for the watchdog task) in that it pends on a task message queue to perform an action. The button state is obtained by polling the GPIO state for each button’s GPIO. In low-power systems, interrupt-based buttons would make more sense but in the Kernel 201 Application, it is assumed the board is always powered via USB so low-power is not a concern.
The Button Task’s Task Message Queue processes only one event and one error. If there was a desire to add the ability to change the polling rate of the buttons, the Task Message Queue could be adjusted to accept a lab message to change the polling rate.
RTOS_ERR_TIMEOUT
LAB_Q_ID_BTN_TMR
RTOS_ERR_TIMEOUT
As part of the software watchdog, the Button Task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the Button Task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_BTN_TMR
The Button Task operates by polling the GPIO state for the buttons. The task uses a Micrium OS Software Timer similar to the LED Task. When the timer expires, a callback is executed from the software timer’s call stack. The callback function quickly checks the state of the buttons. The callback function then sends the state of the buttons to the Button Task via the Task Message Queue specifying the button state in the void* parameter.
The Button Task passes the data it receives from the time callback to the function labBtnCheckUpdate(). This function determines if either button state has changed. If one or both buttons have had a state change, the information is sent via the Task Message Queue to both the Bluetooth and Proprietary Wireless tasks. In the Bluetooth Web App the change will be seen immediately due to the use of the Bluetooth notifications. The Kernel 201 Instructor Application will not see the change immediately due to the Proprietary Wireless Task transmitting on a fixed interval. This means if you wish to see a “pushed” state for either button in the Kernel 201 Instructor Application you must hold the button until after the Proprietary Wireless task has transmitted.
Final Thoughts
The LED and Button Tasks are two very simple UI tasks implemented in the Kernel 201 Application. In a real-world application, other more complex tasks that communicate with external sensors/hardware would most likely exist alongside simple tasks such as these. These two tasks provide a solid framework to build a more complex application on top of.
As always, if there are any comments, questions or concerns, feel free to leave them below.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note:It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Note: It is important to understand RAIL API calls are not thread-safe. The design of the Proprietary Wireless task is to ensure multiple transmit or receive RAIL calls are not made simultaneously.
Introduction
The goal of the Proprietary Wireless task in the Kernel 201 Application is to communicate with another Silicon Labs board running the Kernel 201 Instructor Application. In a lab setting there may be many Thunderboard Sense 2s running the Kernel 201 Application and they would all communicate to one Kernel 201 Instructor Application board.
Just like the Bluetooth task, the Proprietary Wireless task will handle only the wireless communication and pass messages to other tasks in the system as needed. The Proprietary Wireless task will handle both transmitting and receiving in one task rather than having them split into two separate tasks.
Kernel 201 Proprietary Wireless Initialization
When the Proprietary Wireless task is started, the first action it needs to perform is to configure the radio. This is a separate configuration than the Bluetooth configuration which has already occurred (Bluetooth task has a higher priority than the Proprietary Wireless task). The configuration of the radio is done in two different locations: the Proprietary Configurator and the labPropRadioInit() function.
The kernel201.isc file holds the Proprietary Wireless Configuration in addition to the Bluetooth Configuration. For the Kernel 201 Application, the default configuration in the Proprietary Configurator is all that is needed. In the screenshot above, the second profile was removed, but it is not necessary to do so.
The configuration performed in labPropRadioInit() is shown above. There is a great multi-part tutorial on RAIL that walks through each of these calls and can be found here: RAIL Tutorial.
The labPropRadioInit() function first initializes the RAIL handle. The same RAIL handle will be used for transmit and receive in the Proprietary Wireless task.
The RAIL_SetTxFifo() function passes in the transmit FIFO buffer required for queueing up data to send. It is not necessary to specify a receive buffer because one is already allocated internally in the RAIL stack.
Based on the railTxPowerConfig above, the PA calibration is enabled (must be done before RAIL_ConfigTxPower()) and the transmit power is configured. After the transmit power has been configured, a call must be made to RAIL_SetTxPower() to reapply the transmit power configuration.
After the radio transmit power has been configured, the channels we will use must be configured. The channelConfigs[0] array comes from the Proprietary Configurator. For the Kernel 201 Application we will use two channels: 0 and 9. Data will be received on channel 0 and transmitted on channel 9.
The call to RAIL_ConfigEvents() configures what callbacks events are enabled. The RAIL_EVENTS_TX_COMPLETION and RAIL_EVENTS_RX_COMPLETION will signal when a transmit has completed and we need to start listening for data again or when we’ve received a packet and need to process it.
The call to RAIL_SetFixedLength() changes the size of the packet we expect to receive from the default of 32 bytes to LAB_PROP_PACKET_SIZE (48 bytes in this case). This is necessary because the default size of a LAB_MSG_s message is 32 bytes and we must include extra data to indicate what task it must be sent to.
After all of the configurations have been made on the RAIL handle, a call is made to RAIL_StartRx() on the LAB_PROP_RX_CHANNEL to begin listening for messages. Because of the way the radio is configured for Bluetooth and Proprietary Wireless, the radio is now shared between the two protocols. Bluetooth has a higher priority than Proprietary so it has the ability to take control of the radio away from the Proprietary stack if it needs to transmit or receive. This is something that must be taken into account when designing the Kernel 201 Instructor Application.
Kernel 201 Proprietary Wireless Task Design
The Proprietary Wireless task loop is structured just like all other tasks in the Kernel 201 Application (except for the watchdog task). When the task is created, a call is made to labPropRadioInit() which goes through the initialization as shown above. After the radio is configured for Proprietary Wireless it waits on the Task Message Queue.
The Task Message Queue processes three events and one RTOS error. These events are:
RTOS_ERR_TIMEOUT
LAB_Q_ID_MSG
LAB_Q_ID_PROP_TMR
LAB_Q_ID_PROP_RX
RTOS_ERR_TIMEOUT
As part of the software watchdog, the Proprietary Wireless task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the Proprietary Wireless task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_MSG
When another task in the system wishes to send data via Proprietary Wireless, a lab message must be sent to the Proprietary Wireless task via the Task Message Queue. In most cases, this is used when a task has completed a request and is updating the remote Kernel 201 Instructor Application. Unlike the Bluetooth task, data is not transmitted immediately. Instead it is stored in a status structure and transmitted on a fixed interval.
LAB_Q_ID_PROP_TMR
The Kernel 201 Application is designed to be used in a lab class situation, with many Thunderboard Sense 2 boards running and only one Kernel 201 Instructor Application running. In order to not overwhelm the Kernel 201 Instructor Application, the Kernel 201 Application transmits status data on a fixed time interval. This is necessary because if the Kernel 201 Application responded to changes immediately, this would mean every time the Instructor Application pushed out an LED change every Kernel 201 Application would respond at the exact same time and messages would be dropped.
When the status data is transmitted, it must fit into the same packet size as the data we receive, specified by LAB_PROP_PACKET_SIZE. Since there may be multiple devices transmitting on the same channel to the Kernel 201 Instructor Application, each device must be differentiated.
The struct above is used to inform the Kernel 201 Instructor Application who sent the data it is receiving. The seq_num value is not used in the current revision of the application on the transmit side, but it is used on the receive side which will be covered in another section of this post.
The LAB_PROP_STATUS_s struct is the second part of the data sent to the Kernel 201 Instructor Application. When lab messages are received from other tasks in the system, the data is stored in this structure and transmitted on a fixed interval. The reason for the separate struct is this data will be peeled away from the LAB_PROP_DATA_TX_s and sent to a desktop application on the Kernel 201 Instructor Application side.
The diagram below shows how the Proprietary Wireless task uses a software timer to trigger transmits. All data that is received from other tasks in the system is stored in a status structure and when the timer expires, the status structure is transmitted.
When the labPropTmr hits the timeout value, the callback labPropTmrCallback() is executed from the OS Timer stack. The labPropTmrCallback() function sends a signal to the Proprietary Wireless task via the Task Message Queue by sending LAB_Q_ID_PROP_TMR in the msg_size field.
The Proprietary Wireless Task Message Queue releases upon receiving a message from the timer callback. It determines based on the lab_q_id that it is a timer callback. The Proprietary Wireless Task then grabs the current status data, writes it to the RAIL Tx FIFO and starts the transmission via RAIL_StartTx(). The RAIL_StartTx() call is a non-blocking call, so it will return as soon as it successfully initiates the transfer. Note: Typically in an RTOS application a guard should be placed around the RAIL_WriteTxFifo() and RAIL_StartTx() to prevent a second call to either before receiving the RAIL events callback to signal success or error on the transmit. Since the application only transmits on a fixed interval it is not as important to protect multiple writes since they are spaced out by multiple seconds.
After the transmit is complete, the RAIL events callback will execute. The events flag will be set to RAIL_EVENTS_TX_COMPLETION which signals it is now ok to switch the radio to another mode. Since the radio is used by both Bluetooth and Proprietary Wireless, a call must be made to RAIL_YieldRadio() to signal that the radio is free. After yielding the radio we must call RAIL_StartRx() to start background listening for Proprietary Wireless packets in-between Bluetooth transmissions. Note: If you were to transmit and receive on the same channel, the RAIL_StartRx() call would not be necessary. Since the Kernel 201 Application transmits and receives on different channels, it is necessary to call RAIL_StartRx() and specify what channel to listen on.
LAB_Q_ID_PROP_RX
When the radio is not communicating via Bluetooth or transmitting a Proprietary Wireless message, it is in a listening state for Proprietary Wireless messages from the Kernel 201 Instructor Application. All boards running the Kernel 201 Application listen for messages on the same channel. This allows the Kernel 201 Instructor Application to broadcast messages to all boards running the Kernel 201 Application.
When data is received, it is expected to be a specific size and in a specific format. The size was configured during the RAIL configuration; all packets are LAB_PROP_PACKET_SIZE. The format of the data is a combination of the structure above and a LAB_MSG_s. By reusing the LAB_MSG_s structure this allows the Proprietary Wireless task to just “peel off” the LAB_PROP_RX_s data and send the LAB_MSG_s data to the appropriate task. The images below show how an LED message fits into the RAIL Rx packet.
After a packet is received, the Proprietary Wireless task will copy the LAB_MSG_s data into a lab message and send it to the task specified in the task_dest field. The following flowchart shows how packets are received in the Kernel 201 Application.
When a packet is received on the radio, the RAIL stack triggers a callback to the specified callback function labPropRailEventHandler(). Once it is determined that the event triggering the callback is a packet has been received, the RAIL Event Handler must determine if we’ve seen this message before. The Kernel 201 Instructor Application sends out multiple copies of the same message due to the possibility that some Thunderboard Sense 2s may be communicating via Bluetooth or transmitting Proprietary Wireless messages. By using a sequence number, this allows the RAIL Event Handler to quickly determine if the message has already been seen, and if so release the message. If it is a new sequence number that we have not yet seen, the pointer for the packet is passed to the Proprietary Wireless task to be processed.
The Task Message Queue releases when the RAIL packet pointer is received from the RAIL Event Handler. The packet pointer is passed into labPropRadioRxData() where a lab message is allocated, a lab message is extracted from the RAIL packet and the message is copied into the allocated buffer. The destination of the lab message is also pulled from the RAIL packet, the data is sent to the appropriate task and then the RAIL packet is released.
Final Thoughts
The Proprietary Wireless task is a much more complex task than the Bluetooth task when it comes to wireless communication. This is partially due to the flexibility of the Proprietary Wireless stack but also because the RAIL API calls are the lowest level calls available to interface with the radio. When the Bluetooth stack wishes to use the radio, it also must use RAIL API calls under the hood.This application only uses a small subset of the Proprietary Wireless Configurator and RAIL API. For more detailed information on RAIL, check out the resources on this page.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note:It is recommended to download the Kernel 201 WebBluetooth Application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
WebBluetooth API is a Javascript API that provides Bluetooth support for web pages. The API, which was first published in 2015, is still considered Experimental but it has been supported in Google Chrome since version 56 and Opera since version 43 for desktop and Android devices. At the time of writing this, WebBluetooth API is still not supported on Edge, Firefox, or Safari and it does not work on iPhones at all.
The advantage of using the WebBluetooth API is it allows for a quick way to develop an application that can run on desktop or mobile from the same code. In the past if a developer wished to interact with Bluetooth on both desktop and mobile, typically separate applications were needed. The code provided on the Project Resources page runs on any web server that has HTTPS enabled, and will work on desktop and Android.
The WebBluetooth App that interacts with the Kernel 201 Application is based off of StartBootstrap’s SB Admin 2 template. The template provides the necessary css code to support small screens such as mobile devices and large screens like desktop monitors.
The code included in the WebBluetooth App has been paired back considerably from the full SB Admin template to only include the files needed. If you wish to view the entire SB Admin template, it can be found here.
There are only 3 files and one folder in the WebBluetooth App specific to the Kernel 201 Application. The rest of the files are part of Bootstrap, jQuery and SB Admin files that do not need modification.
index.html & images directory
css/bt.css
js/bt.js
index.html & images directory
The entire WebBluetooth App’s HTML code is located in this one file. The app’s display is very simplistic, just display fields and buttons for control. All of the different views for the application are already in the index.html page, the Javascript code controls the showing and hiding of different elements based on the different states of the application.
The images folder holds the different images shown in the WebBluetooth App. In the current version of the application it is used only to hold the logos as seen above.
bt.css
The bt.css file is the main css file from the SB Admin template. There have been minor changes such as color scheme, screen size and card behavior made to the file.
bt.js
This file is what controls the behavior of the WebBluetooth App. It contains the WebBluetooth API code as well as control of what elements are shown on the index.html page.
After clicking the Lab 1 button, a user is brought to the screen above. Clicking connect will trigger the function btConnect() in bt.js. This function begins making WebBluetooth API calls that will start the connection process to the Kernel 201 Application.
The Javascript Engine runs in a single thread, so when it hits a function that requires a delay or user input, instead of blocking the entire application it puts that function off until the delay or input is received and continues executing the rest of the code. In order to ensure the WebBluetooth API functions are called in order, the connect code relies on Javascript Promises. The Promises allow the connection events to happen sequentially using the then() keyword. In the example above you can see that the connect() will not occur until the requestDevice() function has finished. If the then() keyword was not used, Javascript would attempt to run connect() before the requestDevice() function completed causing an error.
When the requestDevice() function is first executed, a filter is applied. The filter is using the UUID of the Lab Control service. You may recall from the Kernel 201: Bluetooth Task blog that the Lab Control service contains all of the tasks in the lab application. By filtering on the Lab Control service, it allows the Bluetooth Connection Dialog to only show Bluetooth devices running the Kernel 201 Application as shown above.
After the connection is made, the WebBluetooth App connects to the Lab Control service and subscribes to notifications from all of the characteristics specified. The notifications allow for the Kernel 201 Application to send a status update to the WebBlueooth App as soon as it occurs, rather than having the WebBluetooth App constantly poll for changes. After its completed the notification subscriptions, the WebBluetooth App makes read requests from the Kernel 201 Application and displays the device state as shown above.
Once the display has been changed to the current state of the Kernel 201 Application, the WebBluetooth App maintains a Bluetooth connection but sit idle until one of two actions occur:
The user clicks a button
A notification is received from the Kernel 201 Application
When the user clicks an LED button, the LED write function will call the Bluetooth write function to send a write command to the Kernel 201 Application with the LED data. After the command is sent, the WebBluetooth App does not change the user interface to reflect the command just sent. The change in the user interface will come from the Kernel 201 Application sending a notification that a change has been made to the LED. This prevents the user interface from getting out of sync with the Kernel 201 Application.
When a notification is received from the Kernel 201 Application, the callback bt_cb_onnotify() fires and determines what portion of the UI needs to be updated. It then passes along the received data to the necessary functions to update the UI.
Final Thoughts
The WebBluetooth API provides a simple, quick way to create apps that run on both mobile and desktop natively. Unfortunately, since it is still experimental, there is a lack of support for it. Hopefully in the future, it will become a widely accepted way of interacting with Bluetooth devices. If you have any questions or thoughts on this feel free to leave them below.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note:It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Note: This project has a slightly different RTOS interface with the Bluetooth stack than other example projects. The differences will be highlighted in this article.
Introduction
The goal of the Bluetooth task in the Kernel 201 Application is to communicate with a web browser running a WebBluetooth App. The WebBluetooth App is highlighted in detail in another post that can be found here.
The Bluetooth task will perform the following actions:
Start Bluetooth Advertising after the stack boots and after a disconnect
Handle read and write requests from the WebBluetooth App
Send notifications to the WebBluetooth App
Kernel 201 Bluetooth Initialization
Before starting the Bluetooth Task, configurations and initializations must be performed for the Bluetooth stack to operate correctly. Part of the configuration for the Bluetooth stack comes from the GATT editor in Simplicity Studio; the other portion of the Bluetooth configuration comes from the gecko_configuration_t structure.
To modify the Bluetooth GATT configuration, there is an application in Simplicity Studio to help configure the GATT database. In the Kernel 201 Application there is a file called kernel201.isc. Opening that file in Simplicity Studio will open the GATT Configurator where you can make changes to the GATT database.
In the GATT Configurator, a new Bluetooth Service called Lab Control has been added for the Kernel 201 Application. Any task in the application that wishes to have Bluetooth control will have a characteristic under the Lab Control service. For the Kernel 201 Application, each characteristic will support Read, Write and Notify. The characteristics are given unique UUID numbers that are hardcoded into the WebBluetooth App. If someone wishes to add a new task, modifications would also have to be made to the WebBluetooth App to support the new UUID. The code for the WebBluetooth App is available for download under the Kernel 201: Project Resources page.
By default, the Kernel 201 Application shows up as “Kernel201” for the Bluetooth Device Name. If you wish to change the Device Name to something else, this can be accomplished under the Device Name Characteristic in the GATT Configurator. The value field is where the new Device Name would be specified and the length must be adjusted. Note: For the DMP portion of this application to work correctly the Device Name must be 16 characters or less. This is a Kernel 201 Application requirement, not a Bluetooth requirement.
After all changes have been made in the GATT Configurator, the kernel201.isc file needs to be saved and then click Generate to update the GATT information. For further information on the GATT Configurator, refer to UG365: GATT Configurator Users Guide
In the lab_bluetooth.c file, there is also the gecko_configuration_t Bluetooth configuration structure that must be passed in during initialization. As shown above the structure sets internal flags, heap location and size (separate heap from Micrium OS), GATT Database, callback functions and other power and security parameters. For more information on the structure, refer to UG136: Silicon Labs Bluetooth C Application Developer’s Guide.
The Kernel 201 Application also introduces a second configuration structure that is not found in other Dynamic Multiprotocol Applications or officially supported by the Bluetooth stack.
static BLUETOOTH_RTOS_CFG_s bluetooth_rtos_cfg = // Bluetooth RTOS config
{
.ll_prio = SLAB_LINKLAYER_PRIO,
.bt_prio = SLAB_BLUETOOTH_PRIO,
.bt_callback = labBluetoothEvtCallback, // Callback to signal a Bluetooth
.gecko_config = &bluetooth_config // event to process.
};
Typically, the function bluetooth_start_task() only takes in the task priorities for the Link Layer and Bluetooth tasks. This structure adds a callback function that will be used when there is a Bluetooth event for the labBluetoothTask to process.
This change is necessary because the existing implementation of the Bluetooth Stack’s RTOS port relied on the user application to pend on an Event Flag. Since the tasks in the Kernel 201 Application pend on Task Message Queues, it was not possible to use the Bluetooth Event Flags in conjunction with the Task Message Queue. The addition of the callback function adds the flexibility for applications to use any kernel service desired for signaling rather than being forced to use an Event Flag.
Kernel 201 Bluetooth Task
The main Bluetooth task loop is structured just like all other tasks in the Kernel 201 Application (except for the watchdog task). When the task is created, the Bluetooth stack is configured and initialized and then begins waiting for messages in the Task Message Queue.
while (DEF_TRUE)
{
p_msg = OSTaskQPend( LAB_WDOG_TASK_TIMEOUT_MS, // Wait for lab message
OS_OPT_PEND_BLOCKING, // or timeout.
&lab_q_id,
0,
&err);
do {
if(err.Code == RTOS_ERR_TIMEOUT) { // If timeout, feed watchdog
break;
} else if(err.Code != RTOS_ERR_NONE) { // Assert on other errors
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
}
switch(lab_q_id) {
case LAB_Q_ID_BLUETOOTH_EVT:
labBluetoothHandleEvt(); // Handle Bluetooth Event
break;
case LAB_Q_ID_MSG: // Handle Lab Message
labBluetoothHandleMsg((LAB_MSG_s*) p_msg);
labUtilMsgFree(p_msg); // Then free Lab Message
break;
default:
break;
}
} while(0);
labWDOGFeed(LAB_TASK_BLUETOOTH); // Feed the watchdog
}
There are two types of messages and one error code that the Bluetooth task handles:
RTOS_ERR_TIMEOUT – No events have been received in the timeout period, feed the software watchdog and pend again.
LAB_Q_ID_BLUETOOTH_EVT – Called when there is an event from the Bluetooth stack to handle.
LAB_Q_ID_MSG – Called when there is a message in the void* argument of the Task Message Queue to process.
RTOS_ERR_TIMEOUT
As part of the software watchdog, the Bluetooth task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the Bluetooth task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_BLUETOOTH_EVT
When the Bluetooth stack has an event for the Kernel 201 Application to process, it makes a call to the callback function specified in the BLUETOOTH_RTOS_CFG_s structure. That function is defined as follows:
In the callback function, a message is sent to the labBluetoothTask to tell it there is a message waiting to be processed. The callback function must send a message because the callback function is executed from the internal Bluetooth Task. If the callback function was to process the Bluetooth event it would lock up the internal Bluetooth task and potentially cause the Bluetooth Stack to miss other Bluetooth events.
When the signal to process an event is received by the labBluetoothTask, the function labBluetoothHandleEvt() is called. This function first retrieves the event that must be processed, and then determines how to handle it. The chart below shows the flow of the event handler:
gecko_evt_system_boot_id and gecko_evt_le_connection_closed_id
These events are called when the Bluetooth stack has been booted, and when a Bluetooth connection has been dropped. In both cases, the Bluetooth stack must be told to start advertising again as we don’t have a connection.
gecko_evt_gatt-server_user_write_request_id
The write event is called when the WebBluetooth App is wishing to send a command to a task in the system. A common example would be using the WebBluetooth App to change the LEDs.
The Bluetooth write event that is received from the Bluetooth stack also contains data such as the task the data is to be sent to, as well as the data itself. To send the data to the specified task, a lab message buffer is allocated using the utility functions. Once a buffer has been allocated, it is configured for the task destination and then the data specific to that task is copied into the buffer. The buffer is then sent to the desired task/s.
gecko_evt_gatt-server_user_read_request_id
The read event is called only when a WebBluetooth App first connects to the Kernel 201 Application. The read is used to determine the state of all of the tasks in the system for the WebBluetooth App. After the WebBluetooth App has connected and received the task status the first time, all subsequent updates are received via a Bluetooth notification.
LAB_Q_ID_MSG
When another task in the system wishes to send data via Bluetooth, a lab message must be sent to the Bluetooth task via the Task Message Queue. In most cases, this is used when a task has completed a request and is updating the remote application. In the LED example, after the LED has been changed a message is sent to the Bluetooth task so the WebBluetooth App connected, if any, is updated.
Kernel 201 Bluetooth Example
The following diagram shows the typical flow of data when a user wishes to change the LED:
The user makes a request on the WebBluetooth App to change the LED color to red.
The WebBluetooth App sends a Bluetooth write command to the Kernel 201 Application.
The Silicon Labs internal Bluetooth stack receives the message via the radio, assemble the data and executes the labBluetoothEvtCallback() that was passed in during the Bluetooth configuration. The callback puts a LAB_Q_ID_BLUETOOTH_EVT message in the labBluetoothTask’s Task Message Queue.
The labBluetoothTask receives the LAB_Q_ID_BLUETOOTH_EVT in its Task Message Queue and handles it as follows:
Get the Bluetooth Event from the Bluetooth stack.
Determine it is a write event.
Determine it is an LED write event.
Allocate a lab message buffer, configure it for a LED message and send it to the LED task via the LED Task’s Task Message Queue.
The labLEDTask receives the LAB_Q_ID_MSG along with the lab message buffer pointer.
It proceeds to change the LED as requested.
After the change has been made, the LED task sends the same lab message content it received back to the Bluetooth task, but signals that it is an update message, not a command message.
The labBluetoothTask receives a LAB_Q_ID_MSG in its Task Message Queue.
The message is sent to the Silicon Labs internal Bluetooth stack via the Gecko characteristic notification command
The Silicon Labs internal Bluetooth stack processes the characteristic notification command and sends the data to the WebBluetooth App.
The WebBluetooth App receives the Bluetooth characteristic notification update and changes the WebBluetooth App to show the LED color has been changed to red.
Final Thoughts
The Bluetooth Task is one of two wireless tasks in the system that will send commands to the other tasks in the system and report back the status of the system. The other task is the Proprietary Wireless that is covered in Kernel 201: Proprietary Wireless Task. The Bluetooth Task has the advantage that the developer does not need to make multiple RAIL calls to configure the radio or listen for hardware interrupts because it is abstracted away by the Bluetooth Stack. The Proprietary Wireless Task does not have this luxury and will require some more in-depth knowledge of the RAIL API. If you have any questions or comments on the Bluetooth Task feel free to leave them below.
If you are running in a multi-threaded environment in which you have more than one task making use of a peripheral driver such as USART, SPI, I2C, etc., you should consider making it thread-safe.
The Micrium OS kernel offers a variety of services designed to protect shared resources. In our case, let’s make use of the Mutual Exclusion Semaphore also known as mutex. Why a mutex? Because we want our resource (peripheral driver) to be accessed only by one task at a time. Regular semaphores present a vulnerability with priority-inversion. Mutexes, on the other hand, are implemented in a way that priority inversions are prevented by using priority inheritance.
For this exercise, you will be editing files from the Gecko SDK, which is not recommended therefore do this with caution.
In the peripheral driver file that you want to protect, include the following file:
#include <kernel/include/os.h>
Now we have to declare a global variable for our mutex, for example, if you want to protect the SPI you could declare it as follows:
OS_MUTEX SPI_Mutex;
With the mutex now declared, you need to invoke the kernel call to create it. My recommendation is to make this in the initialization function of the peripheral driver that you are using. You create the mutex by calling:
OSMutexCreate(&SPI_Mutex, “SPI Mutex”, &err);
Notice how the function requires an error argument, just declare RTOS_ERR err locally and pass it on.
Always make sure to check the error returned, if it’s not RTOS_ERR_NONE then something went wrong.
The mutex is now created and registered with the kernel. Now you will need to wrap around the driver calls that your application is using with:
void foo () {
RTOS_ERR err;
OSMutexPend(&SPI_Mutex, /* Pointer to the mutex */
0, /* No timeout */
OS_OPT_PEND_BLOCKING, /* Block if not available */
DEF_NULL, /*Timestamp not used */
&err);
if (err.Code != RTOS_ERR_NONE) {
/* handle error */
}
/* peripheral driver function code */
...
...
...
OSMutexPost(&SPI_Mutex,
OS_OPT_POST_NONE,
&err);
if (err.Code != RTOS_ERR_NONE) {
/* handle error */
}
}
Please make sure to check the returned errors. A common one is RTOS_ERR_NOT_READY, this happens when the pend or post calls are made before the kernel is in its running state (after the call to OSStart()).
If the driver initialization function can potentially be called multiple times from more than one task, my recommendation is to also protect it.
With this setup, you can be sure that your peripheral is only being accessed by one task at a time.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note:It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
There are different levels of safety to consider when developing a real-time system.
Safety Critical
Systems whose failure may result in death/serious injury, loss or damage to property or environmental harm.
Typically, has well defined specifications for product certification and release (e.g. DO-178, IEC 61508, IEC 62304)
Usually an expensive and time-consuming process to release a product.
Non-Safety Critical
System failures are not directly related to catastrophic events.
Minimal or no certification required for product release.
Even though a majority of products may fall under the non-safety critical system category, it doesn’t mean a safety critical design pattern shouldn’t be considered when developing a real-time system. Designing an application using safety critical components can make a product more robust and reliable.
There are two properties of a system that are commonly considered in safety critical designs:
Safety – System does not create accidents, injuries, loss of life or destruction of property.
Reliability – System runs for long periods of time without error.
These two properties are not mutually exclusive. A system that is considered safe may fail as long as it is able to recover and operate in a manner that does not cause accidents; it is just considered unreliable. On the other hand, a system that can continue to run without error but operates unsafely is a reliable, but unsafe system.
There are many software architectural patterns that a developer can use to help improve a system’s safety and reliability. While the main focus of this article is on one specific pattern (Watchdog Pattern), these are a few other patterns you may come across when researching safety critical design:
Dual Channel – Multiple channels perform the same operations and compare results
Monitor-Actuator – Separate channels for actuators performing actions and monitors keeping track of the actuator
Watchdog Pattern – Software or hardware component that requires signals from other parts of the system on a periodic basis.
Safety Executive – Centralized coordinator to monitor system safety. Typically incorporates a watchdog, build-in tests, and monitor actuator or dual channel patterns.
This article will focus on implementing a watchdog pattern using a mix of software and hardware.
Hardware Watchdog
A standard feature on EFM32 and EFR32 microcontrollers is a hardware watchdog. The hardware watchdog is a timer that when enabled, constantly counts up. The watchdog timer has a user configured timeout value that the application is responsible for “feeding” (clearing) the watchdog counter before the counter reaches that value. If the watchdog reaches the configured time value, a system reset will occur. The assumption is because the application did not feed the watchdog in time, an error has occurred in the system and it must be reset.
The hardware watchdog pattern works well for super-loop applications or in RTOS applications where only one task needs to be monitored and a system-wide reset would not negatively affect other tasks in the system. Complications arise when you wish to ensure multiple tasks are running as expected. One way to solve this problem is to implement a software watchdog that is responsible for handling the hardware watchdog.
This is a simplified overview of the hardware watchdog timer available on EFM32 and EFR32. For more detailed on the EFM32 & EFR32 watchdog timers, Series 0 can be found here and Series 1 can be found here.
Software Watchdog
A software watchdog pattern is a safety design pattern that allows for the monitoring of multiple tasks in an RTOS application. It is typically used in conjunction with a hardware watchdog, but in cases where a hardware watchdog is not available, the software watchdog can be responsible for performing the system reset.
Micrium OS Kernel provides two different kernel services that can be used to implement a software watchdog: Event Flags and Message Queues. For applications that have 32 tasks or less, an Event Flag is an ideal kernel service to use due to the Event Flag being able to monitor 32 unique bits. For applications with more than 32 tasks to be monitored, either multiple flag groups are needed or tasks can send unique values via a Message Queue to periodically check in.
In the image above, each task is represented with a unique bit in the Event Flag Group. The Event Flag is configured to pend on almost all bits of the Event Flag Group and has a timeout configured. If every task posts to the Event Flag before the timeout value is reached, the Event Flag pend returns with RTOS_ERR_NONE, the software watchdog will feed the hardware watchdog and then call pend on the Event Flag again.
If one or more tasks do not post to the Event Flag before the timeout value is reached, the Event Flag pend will return with RTOS_ERR_TIMEOUT. Also, the return value of the pend call will tell you what tasks have and have not checked in. Then with that information the software watchdog can make the decision on whether or not it should feed the hardware watchdog or let it reset the system.
Implementing a Software Watchdog in the Kernel 201 Application
The rest of this article will refer to the Kernel 201 Dynamic Multiprotocol Application. It is recommended to download the project to follow along. There are three files related to the software watchdog implementation: lab.h, lab_main.c and lab_wdog.c.
lab.h
The lab.h file is the centralized location for the application’s configuration parameters such as task priorities, stack sizes, message structures and enums. More detail on lab.h can be found in the blog Kernel 201: Task Architecture & Communication.
The defines above can be found in lab.h and are used as unique identifiers for each task in the system that will be monitored by the software watchdog. Each value will represent a bit in the Event Flag Group bitfield.
lab_main.c
When Micrium OS Kernel is initialized and started in main(), its common to only create one task before calling OSStart(). This is typically done to allow for the statistics task to get a baseline reading of the system at idle before other tasks are created.
In many sample applications the first task created is called the startup task because it is responsible for creating the rest of the tasks in the system and “starting up” the application. This is a poor naming choice for a task as after the startup task creates the other tasks in the system, some examples delete the startup task. What many do not realize is calling OSTaskDel() does not free up any resources related to the task. If the application does not know to access the startup task's stack and TCB, that memory sits unused.
It is not recommended to call OSTaskDel() on startup tasks. A better solution is to use the task for monitoring the system or repurpose the task for another task in the system.
Instead of calling it a startup task, the Kernel 201 Application calls it a system task. It could also be called the watchdog task since the software watchdog will run out of the system task. After the application has been initialized and all of the other tasks have been created, the last call in the labSystemTask function is:
labWDOGTask(); // Run the WDOG task. This call does NOT return
Rather than creating a new task for the watchdog, the application uses the system task to run the watchdog.
lab_wdog.c
The software watchdog file is very straightforward with only three functions: labWDOGInit(), labWDOGTask() and labWDOGFeed().
labWDOGInit()
The init function initializes the hardware watchdog (but does not enable it yet) and creates the Event Flag for the software watchdog.
labWDOGTask()
The software watchdog task operates as follows:
while(1) {
OSFlagPend(&labWDOGFlag, // Wait for all tasks to check in
flags,
LAB_WDOG_SYSTEM_TIMEOUT_MS,
OS_OPT_PEND_FLAG_SET_ALL +
OS_OPT_PEND_FLAG_CONSUME,
0,
&err);
if(err.Code == RTOS_ERR_NONE) { // All tasks checked on-time.
WDOG_Feed(); // Feed the hardware watchdog.
} else if(err.Code == RTOS_ERR_TIMEOUT) {
while(1); // Trigger a reset. Logic can be set
} // here to ignore tasks that did not
} // check in on time.
The watchdog pends on the specified task flags as seen in lab.h, and the pend includes a timeout. If all tasks call a flag post at least once before the timeout value is reached, the OSFlagPend() call returns with RTOS_ERR_NONE, the hardware watchdog is fed via WDOG_Feed() and it loops around and starts the pend over again.
If one or more tasks fail to check in, the return value will be RTOS_ERR_TIMEOUT. In this example the code will then sit in a while(1) until the hardware watchdog resets the system. This would be the ideal location to either add logic to see what flags did or did not check in, and also to add a flag to persistent memory so when the system does reset, it can see on boot that it reset due to an error.
labWDOGFeed()
void labWDOGFeed(CPU_INT32U task)
{
RTOS_ERR err;
OSFlagPost(&labWDOGFlag, // Set the task's flag in the WDOG flag group
task,
OS_OPT_POST_FLAG_SET,
&err);
APP_RTOS_ASSERT_CRITICAL((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), ;);
}
The feed function is provided so all tasks do not need to have a reference to the Watchdog Event Flag. When tasks check in they just need to provide their identifier defined in lab.h. Since a flag group bit can only be 0 or 1, it does not matter if a task checks in once or multiple times each pend cycle.
Since every task is centered around a Message Queue, the timeout parameter on the message queue is set to a value less than the watchdog flag group to ensure that each task feeds the watchdog before the watchdog timeout.
Final Thoughts
One downside to this pattern is it does not work well with a low-power, battery operated system where you may want to sleep for longer periods of time than the hardware watchdog can handle. However, if power is not a concern the software watchdog pattern provides a simple way to ensure all tasks in an RTOS application are operating as expected. If you have any questions or comments, feel free to leave them below.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Task Architecture
The first step of designing any real-time system, before writing any code, is to identify all events the system will be responsible for. Listing out these actions will help identify what events may be grouped together in tasks and what actions will require their own task/s. There are a number of resources available that discuss different task modeling strategies. Some common strategies you may come across are:
Signal event groups – Every event is put in its own task. Not efficient in large systems.
Sequential events – Events that must be processed in order can be grouped together.
Timing events – Events that occur on a periodic time base can be grouped together
Interface events – All events associated with a particular interface
Safety monitoring – Any safety monitoring events should be separated from any actuation tasks
For this application, these are the following events that must be handled and how they’ll be grouped into tasks:
Bluetooth TX – labBluetoothTask
Bluetooth RX – labBluetoothTask
Proprietary Wireless TX – labPropTask
Proprietary Wireless RX – labPropTask
GPIO LED Control – labLEDTask
RBG LED Control – labLEDTask
Button 0 Read – labBtnTask
Button 1 Read – labBtnTask
Hardware Watchdog – labWDOGTask
labBluetoothTask
Silicon Labs’ Bluetooth stack already creates two tasks, Bluetooth and LinkLayer to handle the transmission and reception. The Bluetooth task provides callbacks for the user to implement to know when there is an event to process. The labBluetoothTask will handle those events from the Bluetooth task and pass that data along to the other tasks in the system. The labBluetoothTask will also receive data from other tasks in the system to transmit via Bluetooth.
labPropTask
Silicon Labs does not provide internal tasks for Proprietary Wireless (RAIL) as it does for Bluetooth, but it is important to group the RAIL functions together as they are not thread-safe functions. The labPropTask will handle all data received by RAIL and send it to other tasks in the system as needed. It will also receive data from other tasks in the system and send it out via RAIL.
labLEDTask
Since only the GPIO or RGB LEDs can be active at one time, it makes sense to group them together.
labBtnTask
The button task operates on a low priority polling interval. It could be switched to an interrupt based task but by leaving it as a polling task, it leaves open the option to add other polling events to the task in the future.
labWDOGTask
The watchdog task will implement a software based watchdog to control how the system feeds the hardware watchdog. It is important that this task have no other operations than handling the watchdog.
Task Communication
Now that the events have been divided up into their respective tasks, it is important to implement a consistent method for communication between tasks. There are again many different ways to implement task communication. Micrium OS Kernel provides Semaphores, Task Semaphores, Event Flags, Message Queues and Task Message Queues. While each of these kernel objects have various pros and cons, no one of these services is better than another; it just depends on the application and how it is using these kernel services.
For this application, each task (with the exception of the watchdog task) will be centered around a Task Message Queue and the entire application will use a sort-of layered messaging scheme, similar to the OSI model but much more simplified. The main loop of each task will pend on a Task Message Queue and wait for a message or timeout to determine its next action.
The image above shows the basic layout for all tasks in the system. The code snippet below shows some pseudocode for the task
void* p_msg;
LAB_Q_ID_e lab_q_id;
while (DEF_TRUE)
{
p_msg = OSTaskQPend( LAB_WDOG_TASK_TIMEOUT_MS, // Block the task until we
OS_OPT_PEND_BLOCKING, // receive a message
&lab_q_id,
0,
&err);
do {
if(err.Code == RTOS_ERR_TIMEOUT) { // If we timeout, just feed
break; // the watchdog task
} else if(err.Code != RTOS_ERR_NONE) { // Shouldn't occur, assert
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
}
switch(lab_q_id) {
case LAB_Q_ID_XXX_EVT:
labXXXHandleEvt(); // Handle a task event
break;
case LAB_Q_ID_MSG:
labXXXHandleMsg((LAB_MSG_s*) p_msg); // Handle a lab message
labUtilMsgFree(p_msg); // Then free the message
break; // back to the pool
default:
break;
}
} while(0);
labWDOGFeed(LAB_TASK_ID_XXX); // Feed the watchdog after
} // every task action
By using the Task Message Queue, it allows a task to wait on three different types of events:
Timeouts
Events
Lab Messages
Timeouts
The software watchdog wants every task to check in on a specified rate to show it has not gone out to lunch. By specifying a timeout value in the Task Message Queue pend, the pend call will return with RTOS_ERR_TIMEOUT if a message is not received in the specified time. If a task expects a message in a set time interval, logic can also be added to the error check for RTOS_ERR_TIMEOUT to keep track of how many timeouts we receive in a row.
Events
Some tasks may have a hardware interrupt or software timer callback they need to handle in addition to processing messages from other tasks in the system. The msg_size data field in OSTaskQPend() provides a 32-bit value that is used as an identifier field for this task design. When a task receives a message, it will have a switch statement to handle the value of msg_size. In this application, all message identifiers can be found in an enum called LAB_Q_ID_e.
When an interrupt or software timer callback wants to send a message to the task it is associated with, it will make a call similar to the one below:
It is important to note that the LAB_Q_ID_e value is specified in the msg_size field and not the void* field. All messages sent must have a valid identifier in the msg_size field where the void* argument is optional. In this case, the void* argument is set to 0 as there is no extra data to send.
Lab Messages
These messages are how tasks communicate with each other in this application. Rather than having the Bluetooth or Proprietary Wireless tasks spend time configuring PWM timers to drive the RGB LEDs, it makes more sense to send the LED task a LED command to be processed. This also allows us to handle wireless communication at a higher priority than changing the LEDs or polling the button states. The wireless tasks may be affected if responses are not sent out at specific intervals where if the system has to wait an extra 10ms to toggle the LED, the user most likely won’t notice.
The lab messages follow a layered model where every message has the same header, the header defines what else is to follow in the packet. This application provides utility functions for getting, sending and freeing messages (refer to lab_util.c). There are advantages to these utility functions that will be discussed a little later. When you get/free a message you are actually pulling from a memory pool of fixed block sizes. All messages, regardless of how much of the block they use, are the same size. In the case of this application, all messages are 32 bytes in size. This means that if a task needs to send or receive a message larger than 32 bytes, either the size of every block in the system must be increased, multiple messages must be sent or modifications must be made to the utility functions to allow for allocation and freeing of different size blocks.
The struct above shows the lab message structure that all messages will start with. The ID field identifies what message will follow it and the type corresponds to if it’s a command, response, update or error message. The void* argument is used so you can cast the next layer to the end of the lab message struct.
LAB_MSG_s *p_msg;
p_msg = (LAB_MSG_s*) labUtilMsgGet(); // Get a data buffer to send a message
if(p_msg == DEF_NULL) {
break;
}
p_msg->id = LAB_MSG_ID_LED; // Set the message id
p_msg->type = LAB_MSG_TYPE_CMD; // Set that its a command message
When you wish to allocate a message to send, the code snippet above shows how you would use the labUtil function to allocate a message. The image below shows how the lab message fits into the allocated block.
After you configure the message ID and message type fields, you need to configure the next part of the block for the message ID you specified. For this example, let’s assume we’re changing the LEDs. LEDs are controlled by the LAB_LED_MSG_s struct. To accomplish this, you will cast the LAB_LED_MSG_s to p_data so when you enter information for the LED message, it goes into the correct location in lab message.
LAB_MSG_s *p_msg;
LAB_LED_MSG_s *p_led_msg;
p_msg = (LAB_MSG_s*) labUtilMsgGet(); // Get a data buffer to send a message
if(p_msg == DEF_NULL) {
break;
}
p_msg->id = LAB_MSG_ID_LED; // Set the message id
p_msg->type = LAB_MSG_TYPE_CMD; // Set that its a command message
p_led_msg = (LAB_MSG_LED_s*) &(p_msg->p_data);
p_led_msg->mode = LAB_LED_MODE_RGB;
p_led_msg->color = LAB_LED_COLOR_GREEN;
p_led_msg->state = LAB_LED_STATE_BLINK;
The code snippet above shows how to cast the p_data argument to the LED message structure. Now when you modify the fields of the LED message structure it will be aligned in the message block as shown below.
Once the full message is assembled, the message needs to be sent to the desired task/s. Some messages may only go to one task where others may need to be sent to multiple tasks. The lab utility function for sending offers flexibility for sending messages. Using the labUtilSend() function as shown below, you pass the pointer to the message block just configured and the task/s the message should be sent to.
labUtilMsgSend(p_msg, dest_task); // Send the message to the specified destination
In the case where one message is being sent to multiple tasks, the send function will keep track of the number of tasks it was sent to. This allows each task to call the free function after its processed the message, but the memory block is not actually freed until all tasks that received the message free it. To free a message all a task needs to do is make the following call:
labUtilMsgFree(p_msg); // Free the message back to the pool
It is the labUtilMsgFree function's job to determine when the message is actually returned to the message pool. If one task never calls free on the message it received, that block will never be freed and that memory will be lost so it is imperative that all tasks free all messages they receive, even if they receive the message in error.
Final Thoughts
This wraps up the Task Communication and Architecture section of the Kernel 201 application. Moving forward the Bluetooth, Proprietary Wireless, LED and Button tasks will all be using this communication structure. If you have any questions about this post feel free to leave comments below.
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
Micrium OS offers default configurations to allow a user to get started quickly. These configurations are useful for quick starts, test applications and instances where memory is not a constraint. It is important that all developers using Micrium OS do give some attention to the RTOS configuration as the defaults are not ideal for a released application.
To ensure the dynamic multiprotocol application starts off on a good foot, one of the first steps that will be taken is to configure Micrium OS. Most of the sample applications available in Simplicity Studio provide a good starting point, but this post will look a little more in-depth at the configuration that’s available.
Micrium OS Configuration Files
os_cfg.h
Note: Some example projects have already made modifications to os_cfg.h. The comments here are based on differences between the default os_cfg.h found in the Gecko SDK Micrium OS directory and this project.
This file provides all compile-time options for Micrium OS Kernel. It provides the ability to turn on and off almost all parts of the kernel. Typically, the default values are fine to start a project out with. In this project there are a few differences from the default config worth highlighting:
App hooks enable will turn on the callback functions (if one is defined) anytime the following events occur:
Redzone Hit (if Redzone is enabled) – Occurs when a stack overflow is detected
Task Create – Occurs after the task has been created but before OSTaskCreate() returns
Task Delete (if task delete is enabled) – Occurs after the TCB is cleared and removed from the kernel but before OSTaskDel() returns.
Task Return – Occurs if a task attempts to return
Idle Task – Occurs at the end of the idle task while(1) loop
Stat Task – Occurs after CPU usage calculation in the stat task
Task Switch – Occurs right before a task is switched out
Time Tick – Occurs right before a semaphore post to the tick task
The hook that is set up in most projects is the idle task hook. Since this hook is called from the idle task loop, it is an ideal time to enter a lower power mode. Right now the kernel can run from EM0, EM1 or EM2 (EM2 requires use of the LFRCO).
Dynamic Tick
Dynamic tick allows the kernel to adjust the tick rate as needed to minimize the amount of time the kernel has to wake from sleep. The images below show the advantage of using dynamic tick over a periodic tick.
Periodic Tick
Dynamic Tick
Priority Maximum
The PRIO_MAX setting controls the maximum number of task priorities allowed in the system. Micrium OS supports an unlimited* number of task priorities but there are advantages of having the value set to a smaller number.
If a system only has 10 tasks, there is an advantage to setting PRIO_MAX to 32 vs 64, 128, 256, 512 or some other very large number. The way the kernel’s scheduler looks for the next available task is it has an array called OSPrioTbl[] which is defined as follows:
Each bit in the PrioTbl corresponds to a task priority so since each row holds 32 bits, that’s 32 priority levels that can be checked in one go. If PRIO_MAX is set to 512, the kernel will potentially have to go through 16 rows before deciding to drop to the idle task. That’s a lot of extra overhead for task priorities that are not being used.
*Hardware provides the limitation, there is no limitation in software.
OSTaskDel
The OSTaskDel() should be disabled by default in almost all Micrium OS applications. Deleting a task only removes it from the kernel so it is no longer scheduled. It does not take into account any kernel objects associated with the task nor does it free up any memory associated with the task being deleted. There are very few situations where OSTaskDel() is needed.
common_cfg.h
The common_cfg.h file is used to configure the memory, string and clock modules. The defaults for string and clock are fine for this project, but since the goal of this project is to reduce the kernel’s footprint the memory will be adjusted.
By default the kernel uses an internal heap to allocate space for internal tasks and message queues. By making the following change to the heap size, it will turn off the internal heap.
#define LIB_MEM_CFG_HEAP_SIZE 0uL
If this was the only change is made to the project, the project would compile but fail immediately during runtime when OSInit() is called due to no memory being available. A change also has to be made in rtos_cfg.h to disable the use of the internal heap for internal tasks and message queues.
rtos_cfg.h
The rtos_cfg.h file controls three things:
Assert configuration
Memory usage configuration for internal tasks and message queues
Logging
The assert configuration defaults to locking the system in a while(1) loop for debug and critical asserts. The debug asserts should be modified when going into production but for development it can be useful to leave the while(1) loop to find issues quicker.
The logging is an optional module that is not used in this project.
The Default Config configuration is what determines if the heap is used for internal tasks or not. By making the following change, it will require that a number of internal structures are externally configured. This will allow for the kernel to operate with the heap being set to 0.
A full list of available RTOS_MODULE options can be found in the Gecko SDK version of rtos_description.h.
Main Configuration
Once the changes have been made to the necessary config files, the desired kernel configuration must be made in the application. In this example, the file lab_main.c has the following kernel configuration.
Tick Task
The tick task is used to control the kernel’s system tick which is used for time delay calls and pend timeouts. In this application it is based off of the RTCC interrupt. The following configuration will define a stack for the tick task and set the config only if OS_CFG_TASK_TICK_EN is set in the os_cfg.h file. The stack size, priority and frequency of the tick task are all configured in the lab.h file.
The idle task is the lowest priority task in the system. When the kernel has no other tasks to schedule, it will run the Idle Task. When entering the Idle Task, there is a hook function that can be implemented to allow the user application to enter a lower power mode. The following configuration will define a stack for the idle task and set the config only if OS_CFG_TASK_IDLE_EN is set in the os_cfg.h file. If the idle task is not enabled, when the current task finishes the scheduler sits in a while(1) loop until another action happens in the system rather than jump to a different task. When the idle task is enabled the stack size of the idle task can be found in lab.h. You can not set the priority of the idle task because it must always be the lowest priority task in the system.
The timer task is used to control Micrium OS Kernel’s software timers. The reason the timers operate in a different task than the tick task is the timer callback functions are made from the timer task, so it allows the timer callbacks to run at a different priority than the tick task if desired. The following configuration will define a stack and set the config only if OS_CFG_TMR_ EN is enabled in the os_cfg.h file. The stack size, priority and frequency of the timer task are all configured in the lab.h file.
The stat task is an internal task that provides such run-time statistics as overall CPU utilization (0.00 to 100.00%), per-task CPU utilization (0.00 to 100.00%), and per-task stack usage. The following configuration will define a stack and set the config only if OS_CFG_STAT_TASK_EN is enabled in the os_cfg.h file. The stack size, priority and frequency of the stat task are all configured in the lab.h file.
The ARM Cortex-M series has a separate stack for interrupts to execute out of. Micrium OS Kernel will configure the stack location during the OSInit() call, but a stack must be provided. The following configuration creates the interrupt stack and sets the necessary configuration. This is not an optional configuration, it is required of all Micrium OS Kernel applications.
If message queues will be used in an application (this application makes heavy use of them) then a message pool must be defined. When messages are sent in either a message queue or task message queue, a message pool object is allocated to hold the data being sent (void* argument and message size argument). The message pool should have enough entries for every message queue entry in the application. For example, if you have 3 message queues each with 10 entries in them, you should have a message pool size of 30. The following configuration is used to specify the message pool size in this application.
And finally, in the main() before OSInit() is called, the memory segment must be created so the kernel knows how to access the memory buffer specified above.
Mem_SegCreate( "Msg Pool Mem", // Create the Message Pool segment
&MsgPoolMemSeg,
(CPU_ADDR)&MsgPoolMem,
MSG_POOL_SIZE,
LIB_MEM_PADDING_ALIGN_NONE,
&err);
APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1);
Putting it all together
You may have noticed that all of the configs are just #defines. These #defines are used to create one giant #define as shown below.
Once the OS_INIT_CFG_APP define is put assembled with all of the task and memory pool configs, the define should be assigned to the global variable as follows:
const OS_INIT_CFG OS_InitCfg = OS_INIT_CFG_APP;
This variable is externed by the kernel so it must be named OSInitCfg as this is what the kernel uses internally.
Final Thoughts
While it may appear a little complicated, the configuration of Micrium OS Kernel is very important to the success of an application and worth the extra effort. A misconfigured kernel can cause issues farther down the road so it is important to configure it correctly to start with. By removing the general purpose heap it allows the developer to have a full understanding of what's going on under the hood so there is no confusion about how the kernel is configured. If you have any questions or comments feel free to leave them below.
When developing an application with a real-time operating system, developers have numerous design decisions to make when starting a project. These design decisions can greatly affect how a system performs, how easy it is to expand an application and even how well a system can tolerate failures.
The following blog series will take an in-depth look at designing a real-time operating system application in a dynamic multiprotocol system (Bluetooth + Proprietary Wireless). It will walk through configuring Micrium OS, discuss task architecture, look in-depth at the Bluetooth and Proprietary Wireless tasks, and look at some safety design patterns.
The goal of the application discussed throughout the blog series is to run the dynamic multiprotocol application on a Thunderboard Sense 2 board. There is a web application that will communicate over Bluetooth with the Thunderboard using the WebBluetooth API. The Thunderboard will also communicate to a Blue Gecko board via Proprietary Wireless. The image below shows the layout of communication.
Official Blog of Silicon Labs
Kernel 201: Instructor Application & Electron App
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 Instructor Application & Electron App and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Kernel 201 Instructor Application
The instructor application is designed to be run by the instructor of a Kernel 201 class, or in the case of someone following along this blog post, by a user. The application communicates via Proprietary Wireless with all of the Thunderboard Sense 2s running the Kernel 201 Application in a one-to-many fashion. When the instructor application sends out a command, it acts as a broadcast to all devices listening. Since the Thunderboard Sense 2s are running Dynamic Multiprotocol, it is possible the radio may be operating in Bluetooth mode when the instructor application broadcasts a message. To mitigate this, the instructor application will actually transmit out the same message multiple times in an attempt to ensure all boards receive the message. The instructor application also can receive data from all Kernel 201 Applications. This is why the Kernel 201 Application only transmits data once every 10 seconds, rather than whenever it receives commands.
Note: It is worth noting that even with the multiple broadcasts, it is still possible for a Kernel 201 Application to miss a Proprietary Wireless message. In a real-world application where missing messages is not acceptable, a basic transmit-ack scheme would help or the use of a more complex wireless stack such as SiLab’s Connect Networking Stack. For this application, in order to keep the wireless portion simpler, it is acceptable for messages to be missed.
The entire instructor application acts as a translator between the Electron App and the Proprietary Wireless stack. When a user sends a command in the Electron App, a LAB_MSG_s packet is built up in the Electron App and sent to the instructor application via serial. The instructor application then takes that data and transmits it out using the RAIL API. When data is received via Proprietary Wireless, the instructor application receives the RAIL packet and then sends the data it received via serial to the Electron App. This allows the instructor application to operate without knowing too many details about the command packets.
Proprietary Wireless Task
The Proprietary Wireless task design in the instructor application is slightly different than the Proprietary Wireless task in the Kernel 201 Application. In the instructor application the Proprietary Wireless task only handles the receive events. Transmit events are triggered by the VCOM task in the instructor application.
VCOM Task
The VCOM task takes in data from the Electron App over serial and transmits it to the Kernel 201 Application via Proprietary Wireless. The data received from the Electron App comes in the format of two sync bytes, two bytes for the Task ID, a LAB_MSG_s packet and then two end sync bytes. A state machine is used in the UART interrupt to handle the received data. Once a full packet has been received, a semaphore is posted to signal to the VCOM task to signal that it is time to transmit the data received. The VCOM task takes the data received and calls labPropTx() where the data is loaded into the RAIL transmit queue. After the data is successfully transmitted, the task goes back to pending waiting for another message to send.
Kernel 201 Instructor Electron App
The Electron App is a cross-platform desktop application that communicates via serial to the Kernel 201 Instructor Application. The app allows you to interact with all Thunderboard Sense 2s running the Kernel 201 application.
To get started, you need to have npm installed on your machine and have the Kernel 201 Instructor Electron App source code from the Kernel 201: Project Resources page.
From the electron app directory run the following commands:
The install command will download all necessary packages for the application. The command npm start will run the application. There is also the option to generate executable packages for both Windows and Mac via the following commands:
Windows packages can only be generated on Windows and Mac packages can only be generated on Mac. The advantage of generating packages is you do not need to have npm or node installed on a machine when a package is used.
Using the Kernel 201 Instructor Electron App
The first screen that should appear is the Scan Serial Ports window. Make sure the instructor application board is plugged into your computer and click scan.
After scanning you should see at least one device. On Mac’s the SiLabs boards show up as /dev/tty.usbmodem and on Windows they will show up as COM. There is a filter in place to not show every serial device connected in an effort to make this simpler. If you find that you can not find the device, look at the file serial.js located in the app/js directory and remove the Silicon Labs filter.
Note: If you have the Blue Gecko and the Thunderboard Sense 2 connected to the same computer, both will show up. Be sure to select the right device!
Once connected to the serial device, you will be presented with the window above. To view all Kernel 201 Application devices, click View Devices. To send commands to all Kernel 201 Application devices, click Control Devices.
Under View Devices, any Kernel 201 Application devices should show up. Remember, those boards only transmit their status once every 10 seconds so it may take a little while for them to appear. If you make changes to the board via the WebBluetooth App, those changes should eventually be reflected here as well.
To send commands to the boards, go to the Control Devices window. Here you have the ability to configure the LEDs and then either send it once or send it repeatedly. If you have the WebBluetooth App open and connected to the Kernel 201 Application, as you push changes out via the Electron App you should see the changes reflected in the WebBluetooth App immediately.
Final Thoughts
The instructor application and Electron App provide a simple way to interact with the Kernel 201 Application via Proprietary Wireless. While the response time is very slow compared to Bluetooth and the WebBluetooth App, the Proprietary Wireless provides a way to communicate with more than one board at a time which can be extremely useful.
Kernel 201: UI Tasks
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
Up until this point, all of the tasks covered in this blog series have been for wireless communication and the system watchdog. This post will cover the two user interface tasks: LEDs and buttons. These tasks are simple interfaces that take advantage of some of the hardware on the Thunderboard Sense 2. In a real-world application, these tasks would be just part of a number of application tasks that may interact with other sensors, user input or even other processors.
LED Task
When the LED Task is first created, there are two sets of LEDs that are initialized. The Thunderboard Sense 2 provides a simple red and green LED controlled by GPIO pins, but it also has RGB LEDs on both sides of the board. The RGB LEDs require the use of a hardware timer to control the color, so they can show any color you wish. To keep the lab simpler, the LEDs only offer red, green and yellow since those colors are available on both the GPIO LED and the RGB LEDs.
The LED Task is structured like all other tasks in the system (except for the watchdog task) in that it pends on a task message queue to perform an action.
The Task Message Queue processes two events and one RTOS error. These events are:
RTOS_ERR_TIMEOUT
As part of the software watchdog, the LED Task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the LED Task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_MSG
When another task in the system wishes to send a command to the LED Task, a lab message must be sent to the LED Task via the Task Message Queue. In most cases, this is either the Bluetooth or Proprietary Wireless task relaying LED control messages.
After a valid command message is successfully processed, an update is sent to both the Bluetooth and Proprietary Wireless tasks to alert them that the LEDs have been changed. The Bluetooth Web App will immediately reflect these changes due to the use of Bluetooth notifications. The Kernel 201 Instructor Application will be delayed on updating the LED status due to the Proprietary Wireless task transmitting on a fixed interval.
LAB_Q_ID_LED_TMR
When the LED Task is sent a command to blink the LED, a software timer is enabled. When the Micrium OS Software Timer expires, a callback function is executed similar to how an interrupt may trigger an interrupt routine. The software timer callback is treated similar to an interrupt function in that the code must be short because the callback function is executing out of the software timer’s callstack. Rather than controlling the LEDs from the callback, a message is sent to the LED Task via the Task Message Queue sending the message LAB_Q_ID_LED_TMR to signal that a software timer timeout occurred. The LED Task then knows based on the signal from the timer callback that it should blink the LEDs.
Button Task
After the Button Task is created, it initializes the two push buttons on the Thunderboard Sense 2. The Button Task is structured like all other tasks in the system (except for the watchdog task) in that it pends on a task message queue to perform an action. The button state is obtained by polling the GPIO state for each button’s GPIO. In low-power systems, interrupt-based buttons would make more sense but in the Kernel 201 Application, it is assumed the board is always powered via USB so low-power is not a concern.
The Button Task’s Task Message Queue processes only one event and one error. If there was a desire to add the ability to change the polling rate of the buttons, the Task Message Queue could be adjusted to accept a lab message to change the polling rate.
RTOS_ERR_TIMEOUT
As part of the software watchdog, the Button Task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the Button Task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_BTN_TMR
The Button Task operates by polling the GPIO state for the buttons. The task uses a Micrium OS Software Timer similar to the LED Task. When the timer expires, a callback is executed from the software timer’s call stack. The callback function quickly checks the state of the buttons. The callback function then sends the state of the buttons to the Button Task via the Task Message Queue specifying the button state in the void* parameter.
The Button Task passes the data it receives from the time callback to the function labBtnCheckUpdate(). This function determines if either button state has changed. If one or both buttons have had a state change, the information is sent via the Task Message Queue to both the Bluetooth and Proprietary Wireless tasks. In the Bluetooth Web App the change will be seen immediately due to the use of the Bluetooth notifications. The Kernel 201 Instructor Application will not see the change immediately due to the Proprietary Wireless Task transmitting on a fixed interval. This means if you wish to see a “pushed” state for either button in the Kernel 201 Instructor Application you must hold the button until after the Proprietary Wireless task has transmitted.
Final Thoughts
The LED and Button Tasks are two very simple UI tasks implemented in the Kernel 201 Application. In a real-world application, other more complex tasks that communicate with external sensors/hardware would most likely exist alongside simple tasks such as these. These two tasks provide a solid framework to build a more complex application on top of.
As always, if there are any comments, questions or concerns, feel free to leave them below.
Kernel 201: Proprietary Wireless Task
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Note: It is important to understand RAIL API calls are not thread-safe. The design of the Proprietary Wireless task is to ensure multiple transmit or receive RAIL calls are not made simultaneously.
Introduction
The goal of the Proprietary Wireless task in the Kernel 201 Application is to communicate with another Silicon Labs board running the Kernel 201 Instructor Application. In a lab setting there may be many Thunderboard Sense 2s running the Kernel 201 Application and they would all communicate to one Kernel 201 Instructor Application board.
Just like the Bluetooth task, the Proprietary Wireless task will handle only the wireless communication and pass messages to other tasks in the system as needed. The Proprietary Wireless task will handle both transmitting and receiving in one task rather than having them split into two separate tasks.
Kernel 201 Proprietary Wireless Initialization
When the Proprietary Wireless task is started, the first action it needs to perform is to configure the radio. This is a separate configuration than the Bluetooth configuration which has already occurred (Bluetooth task has a higher priority than the Proprietary Wireless task). The configuration of the radio is done in two different locations: the Proprietary Configurator and the labPropRadioInit() function.
The kernel201.isc file holds the Proprietary Wireless Configuration in addition to the Bluetooth Configuration. For the Kernel 201 Application, the default configuration in the Proprietary Configurator is all that is needed. In the screenshot above, the second profile was removed, but it is not necessary to do so.
The configuration performed in labPropRadioInit() is shown above. There is a great multi-part tutorial on RAIL that walks through each of these calls and can be found here: RAIL Tutorial.
Kernel 201 Proprietary Wireless Task Design
The Proprietary Wireless task loop is structured just like all other tasks in the Kernel 201 Application (except for the watchdog task). When the task is created, a call is made to labPropRadioInit() which goes through the initialization as shown above. After the radio is configured for Proprietary Wireless it waits on the Task Message Queue.
The Task Message Queue processes three events and one RTOS error. These events are:
RTOS_ERR_TIMEOUT
As part of the software watchdog, the Proprietary Wireless task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the Proprietary Wireless task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_MSG
When another task in the system wishes to send data via Proprietary Wireless, a lab message must be sent to the Proprietary Wireless task via the Task Message Queue. In most cases, this is used when a task has completed a request and is updating the remote Kernel 201 Instructor Application. Unlike the Bluetooth task, data is not transmitted immediately. Instead it is stored in a status structure and transmitted on a fixed interval.
LAB_Q_ID_PROP_TMR
The Kernel 201 Application is designed to be used in a lab class situation, with many Thunderboard Sense 2 boards running and only one Kernel 201 Instructor Application running. In order to not overwhelm the Kernel 201 Instructor Application, the Kernel 201 Application transmits status data on a fixed time interval. This is necessary because if the Kernel 201 Application responded to changes immediately, this would mean every time the Instructor Application pushed out an LED change every Kernel 201 Application would respond at the exact same time and messages would be dropped.
When the status data is transmitted, it must fit into the same packet size as the data we receive, specified by LAB_PROP_PACKET_SIZE. Since there may be multiple devices transmitting on the same channel to the Kernel 201 Instructor Application, each device must be differentiated.
The struct above is used to inform the Kernel 201 Instructor Application who sent the data it is receiving. The seq_num value is not used in the current revision of the application on the transmit side, but it is used on the receive side which will be covered in another section of this post.
The LAB_PROP_STATUS_s struct is the second part of the data sent to the Kernel 201 Instructor Application. When lab messages are received from other tasks in the system, the data is stored in this structure and transmitted on a fixed interval. The reason for the separate struct is this data will be peeled away from the LAB_PROP_DATA_TX_s and sent to a desktop application on the Kernel 201 Instructor Application side.
The diagram below shows how the Proprietary Wireless task uses a software timer to trigger transmits. All data that is received from other tasks in the system is stored in a status structure and when the timer expires, the status structure is transmitted.
Note: Typically in an RTOS application a guard should be placed around the RAIL_WriteTxFifo() and RAIL_StartTx() to prevent a second call to either before receiving the RAIL events callback to signal success or error on the transmit. Since the application only transmits on a fixed interval it is not as important to protect multiple writes since they are spaced out by multiple seconds.
Note: If you were to transmit and receive on the same channel, the RAIL_StartRx() call would not be necessary. Since the Kernel 201 Application transmits and receives on different channels, it is necessary to call RAIL_StartRx() and specify what channel to listen on.
LAB_Q_ID_PROP_RX
When the radio is not communicating via Bluetooth or transmitting a Proprietary Wireless message, it is in a listening state for Proprietary Wireless messages from the Kernel 201 Instructor Application. All boards running the Kernel 201 Application listen for messages on the same channel. This allows the Kernel 201 Instructor Application to broadcast messages to all boards running the Kernel 201 Application.
When data is received, it is expected to be a specific size and in a specific format. The size was configured during the RAIL configuration; all packets are LAB_PROP_PACKET_SIZE. The format of the data is a combination of the structure above and a LAB_MSG_s. By reusing the LAB_MSG_s structure this allows the Proprietary Wireless task to just “peel off” the LAB_PROP_RX_s data and send the LAB_MSG_s data to the appropriate task. The images below show how an LED message fits into the RAIL Rx packet.
After a packet is received, the Proprietary Wireless task will copy the LAB_MSG_s data into a lab message and send it to the task specified in the task_dest field. The following flowchart shows how packets are received in the Kernel 201 Application.
Final Thoughts
The Proprietary Wireless task is a much more complex task than the Bluetooth task when it comes to wireless communication. This is partially due to the flexibility of the Proprietary Wireless stack but also because the RAIL API calls are the lowest level calls available to interface with the radio. When the Bluetooth stack wishes to use the radio, it also must use RAIL API calls under the hood.This application only uses a small subset of the Proprietary Wireless Configurator and RAIL API. For more detailed information on RAIL, check out the resources on this page.
Kernel 201: WebBluetooth App
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 WebBluetooth Application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
WebBluetooth API is a Javascript API that provides Bluetooth support for web pages. The API, which was first published in 2015, is still considered Experimental but it has been supported in Google Chrome since version 56 and Opera since version 43 for desktop and Android devices. At the time of writing this, WebBluetooth API is still not supported on Edge, Firefox, or Safari and it does not work on iPhones at all.
The advantage of using the WebBluetooth API is it allows for a quick way to develop an application that can run on desktop or mobile from the same code. In the past if a developer wished to interact with Bluetooth on both desktop and mobile, typically separate applications were needed. The code provided on the Project Resources page runs on any web server that has HTTPS enabled, and will work on desktop and Android.
The full WebBluetooth API can be found here.
WebBluetooth App
The WebBluetooth App that interacts with the Kernel 201 Application is based off of StartBootstrap’s SB Admin 2 template. The template provides the necessary css code to support small screens such as mobile devices and large screens like desktop monitors.
The code included in the WebBluetooth App has been paired back considerably from the full SB Admin template to only include the files needed. If you wish to view the entire SB Admin template, it can be found here.
There are only 3 files and one folder in the WebBluetooth App specific to the Kernel 201 Application. The rest of the files are part of Bootstrap, jQuery and SB Admin files that do not need modification.
index.html & images directory
The entire WebBluetooth App’s HTML code is located in this one file. The app’s display is very simplistic, just display fields and buttons for control. All of the different views for the application are already in the index.html page, the Javascript code controls the showing and hiding of different elements based on the different states of the application.
The images folder holds the different images shown in the WebBluetooth App. In the current version of the application it is used only to hold the logos as seen above.
bt.css
The bt.css file is the main css file from the SB Admin template. There have been minor changes such as color scheme, screen size and card behavior made to the file.
bt.js
This file is what controls the behavior of the WebBluetooth App. It contains the WebBluetooth API code as well as control of what elements are shown on the index.html page.
After clicking the Lab 1 button, a user is brought to the screen above. Clicking connect will trigger the function btConnect() in bt.js. This function begins making WebBluetooth API calls that will start the connection process to the Kernel 201 Application.
The Javascript Engine runs in a single thread, so when it hits a function that requires a delay or user input, instead of blocking the entire application it puts that function off until the delay or input is received and continues executing the rest of the code. In order to ensure the WebBluetooth API functions are called in order, the connect code relies on Javascript Promises. The Promises allow the connection events to happen sequentially using the then() keyword. In the example above you can see that the connect() will not occur until the requestDevice() function has finished. If the then() keyword was not used, Javascript would attempt to run connect() before the requestDevice() function completed causing an error.
When the requestDevice() function is first executed, a filter is applied. The filter is using the UUID of the Lab Control service. You may recall from the Kernel 201: Bluetooth Task blog that the Lab Control service contains all of the tasks in the lab application. By filtering on the Lab Control service, it allows the Bluetooth Connection Dialog to only show Bluetooth devices running the Kernel 201 Application as shown above.
After the connection is made, the WebBluetooth App connects to the Lab Control service and subscribes to notifications from all of the characteristics specified. The notifications allow for the Kernel 201 Application to send a status update to the WebBlueooth App as soon as it occurs, rather than having the WebBluetooth App constantly poll for changes. After its completed the notification subscriptions, the WebBluetooth App makes read requests from the Kernel 201 Application and displays the device state as shown above.
Once the display has been changed to the current state of the Kernel 201 Application, the WebBluetooth App maintains a Bluetooth connection but sit idle until one of two actions occur:
When the user clicks an LED button, the LED write function will call the Bluetooth write function to send a write command to the Kernel 201 Application with the LED data. After the command is sent, the WebBluetooth App does not change the user interface to reflect the command just sent. The change in the user interface will come from the Kernel 201 Application sending a notification that a change has been made to the LED. This prevents the user interface from getting out of sync with the Kernel 201 Application.
When a notification is received from the Kernel 201 Application, the callback bt_cb_onnotify() fires and determines what portion of the UI needs to be updated. It then passes along the received data to the necessary functions to update the UI.
Final Thoughts
The WebBluetooth API provides a simple, quick way to create apps that run on both mobile and desktop natively. Unfortunately, since it is still experimental, there is a lack of support for it. Hopefully in the future, it will become a widely accepted way of interacting with Bluetooth devices. If you have any questions or thoughts on this feel free to leave them below.
Kernel 201: Bluetooth Task
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Note: This project has a slightly different RTOS interface with the Bluetooth stack than other example projects. The differences will be highlighted in this article.
Introduction
The goal of the Bluetooth task in the Kernel 201 Application is to communicate with a web browser running a WebBluetooth App. The WebBluetooth App is highlighted in detail in another post that can be found here.
The Bluetooth task will perform the following actions:
Kernel 201 Bluetooth Initialization
Before starting the Bluetooth Task, configurations and initializations must be performed for the Bluetooth stack to operate correctly. Part of the configuration for the Bluetooth stack comes from the GATT editor in Simplicity Studio; the other portion of the Bluetooth configuration comes from the gecko_configuration_t structure.
To modify the Bluetooth GATT configuration, there is an application in Simplicity Studio to help configure the GATT database. In the Kernel 201 Application there is a file called kernel201.isc. Opening that file in Simplicity Studio will open the GATT Configurator where you can make changes to the GATT database.
In the GATT Configurator, a new Bluetooth Service called Lab Control has been added for the Kernel 201 Application. Any task in the application that wishes to have Bluetooth control will have a characteristic under the Lab Control service. For the Kernel 201 Application, each characteristic will support Read, Write and Notify. The characteristics are given unique UUID numbers that are hardcoded into the WebBluetooth App. If someone wishes to add a new task, modifications would also have to be made to the WebBluetooth App to support the new UUID. The code for the WebBluetooth App is available for download under the Kernel 201: Project Resources page.
By default, the Kernel 201 Application shows up as “Kernel201” for the Bluetooth Device Name. If you wish to change the Device Name to something else, this can be accomplished under the Device Name Characteristic in the GATT Configurator. The value field is where the new Device Name would be specified and the length must be adjusted. Note: For the DMP portion of this application to work correctly the Device Name must be 16 characters or less. This is a Kernel 201 Application requirement, not a Bluetooth requirement.
After all changes have been made in the GATT Configurator, the kernel201.isc file needs to be saved and then click Generate to update the GATT information. For further information on the GATT Configurator, refer to UG365: GATT Configurator Users Guide
In the lab_bluetooth.c file, there is also the gecko_configuration_t Bluetooth configuration structure that must be passed in during initialization. As shown above the structure sets internal flags, heap location and size (separate heap from Micrium OS), GATT Database, callback functions and other power and security parameters. For more information on the structure, refer to UG136: Silicon Labs Bluetooth C Application Developer’s Guide.
The Kernel 201 Application also introduces a second configuration structure that is not found in other Dynamic Multiprotocol Applications or officially supported by the Bluetooth stack.
Typically, the function bluetooth_start_task() only takes in the task priorities for the Link Layer and Bluetooth tasks. This structure adds a callback function that will be used when there is a Bluetooth event for the labBluetoothTask to process.
This change is necessary because the existing implementation of the Bluetooth Stack’s RTOS port relied on the user application to pend on an Event Flag. Since the tasks in the Kernel 201 Application pend on Task Message Queues, it was not possible to use the Bluetooth Event Flags in conjunction with the Task Message Queue. The addition of the callback function adds the flexibility for applications to use any kernel service desired for signaling rather than being forced to use an Event Flag.
Kernel 201 Bluetooth Task
The main Bluetooth task loop is structured just like all other tasks in the Kernel 201 Application (except for the watchdog task). When the task is created, the Bluetooth stack is configured and initialized and then begins waiting for messages in the Task Message Queue.
There are two types of messages and one error code that the Bluetooth task handles:
RTOS_ERR_TIMEOUT
As part of the software watchdog, the Bluetooth task must feed the software watchdog at a specified rate. There is a define in lab.h that all tasks use called LAB_WDOG_TASK_TIMEOUT_MS. This define specifies how often every task should check-in with the software watchdog. If the Task Message Queue does not receive a message before the timeout value is hit, the pend call returns with RTOS_ERR_TIMEOUT. Since it’s a timeout the Bluetooth task knows that no message was received, it only feeds the software watchdog and then starts pending on the Task Message Queue again.
LAB_Q_ID_BLUETOOTH_EVT
When the Bluetooth stack has an event for the Kernel 201 Application to process, it makes a call to the callback function specified in the BLUETOOTH_RTOS_CFG_s structure. That function is defined as follows:
In the callback function, a message is sent to the labBluetoothTask to tell it there is a message waiting to be processed. The callback function must send a message because the callback function is executed from the internal Bluetooth Task. If the callback function was to process the Bluetooth event it would lock up the internal Bluetooth task and potentially cause the Bluetooth Stack to miss other Bluetooth events.
When the signal to process an event is received by the labBluetoothTask, the function labBluetoothHandleEvt() is called. This function first retrieves the event that must be processed, and then determines how to handle it. The chart below shows the flow of the event handler:
gecko_evt_system_boot_id and gecko_evt_le_connection_closed_id
These events are called when the Bluetooth stack has been booted, and when a Bluetooth connection has been dropped. In both cases, the Bluetooth stack must be told to start advertising again as we don’t have a connection.
gecko_evt_gatt-server_user_write_request_id
The write event is called when the WebBluetooth App is wishing to send a command to a task in the system. A common example would be using the WebBluetooth App to change the LEDs.
The Bluetooth write event that is received from the Bluetooth stack also contains data such as the task the data is to be sent to, as well as the data itself. To send the data to the specified task, a lab message buffer is allocated using the utility functions. Once a buffer has been allocated, it is configured for the task destination and then the data specific to that task is copied into the buffer. The buffer is then sent to the desired task/s.
gecko_evt_gatt-server_user_read_request_id
The read event is called only when a WebBluetooth App first connects to the Kernel 201 Application. The read is used to determine the state of all of the tasks in the system for the WebBluetooth App. After the WebBluetooth App has connected and received the task status the first time, all subsequent updates are received via a Bluetooth notification.
LAB_Q_ID_MSG
When another task in the system wishes to send data via Bluetooth, a lab message must be sent to the Bluetooth task via the Task Message Queue. In most cases, this is used when a task has completed a request and is updating the remote application. In the LED example, after the LED has been changed a message is sent to the Bluetooth task so the WebBluetooth App connected, if any, is updated.
Kernel 201 Bluetooth Example
The following diagram shows the typical flow of data when a user wishes to change the LED:
Final Thoughts
The Bluetooth Task is one of two wireless tasks in the system that will send commands to the other tasks in the system and report back the status of the system. The other task is the Proprietary Wireless that is covered in Kernel 201: Proprietary Wireless Task. The Bluetooth Task has the advantage that the developer does not need to make multiple RAIL calls to configure the radio or listen for hardware interrupts because it is abstracted away by the Bluetooth Stack. The Proprietary Wireless Task does not have this luxury and will require some more in-depth knowledge of the RAIL API. If you have any questions or comments on the Bluetooth Task feel free to leave them below.
Making Peripheral Drivers Thread-safe
If you are running in a multi-threaded environment in which you have more than one task making use of a peripheral driver such as USART, SPI, I2C, etc., you should consider making it thread-safe.
The Micrium OS kernel offers a variety of services designed to protect shared resources. In our case, let’s make use of the Mutual Exclusion Semaphore also known as mutex. Why a mutex? Because we want our resource (peripheral driver) to be accessed only by one task at a time. Regular semaphores present a vulnerability with priority-inversion. Mutexes, on the other hand, are implemented in a way that priority inversions are prevented by using priority inheritance.
For this exercise, you will be editing files from the Gecko SDK, which is not recommended therefore do this with caution.
In the peripheral driver file that you want to protect, include the following file:
Now we have to declare a global variable for our mutex, for example, if you want to protect the SPI you could declare it as follows:
With the mutex now declared, you need to invoke the kernel call to create it. My recommendation is to make this in the initialization function of the peripheral driver that you are using. You create the mutex by calling:
Notice how the function requires an error argument, just declare RTOS_ERR err locally and pass it on.
Always make sure to check the error returned, if it’s not RTOS_ERR_NONE then something went wrong.
The mutex is now created and registered with the kernel. Now you will need to wrap around the driver calls that your application is using with:
Please make sure to check the returned errors. A common one is RTOS_ERR_NOT_READY, this happens when the pend or post calls are made before the kernel is in its running state (after the call to OSStart()).
If the driver initialization function can potentially be called multiple times from more than one task, my recommendation is to also protect it.
With this setup, you can be sure that your peripheral is only being accessed by one task at a time.
Beware also that some drivers have abort functions, you have to be careful with this and not lock your system on a mutex pend. For more information on how to protect a resource using Micrium OS please visit https://doc.micrium.com/display/OSUM50600/Resource+Management+Using+the+Kernel
Kernel 201: Implementing a Software Watchdog
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
There are different levels of safety to consider when developing a real-time system.
Safety Critical
Non-Safety Critical
Even though a majority of products may fall under the non-safety critical system category, it doesn’t mean a safety critical design pattern shouldn’t be considered when developing a real-time system. Designing an application using safety critical components can make a product more robust and reliable.
There are two properties of a system that are commonly considered in safety critical designs:
These two properties are not mutually exclusive. A system that is considered safe may fail as long as it is able to recover and operate in a manner that does not cause accidents; it is just considered unreliable. On the other hand, a system that can continue to run without error but operates unsafely is a reliable, but unsafe system.
There are many software architectural patterns that a developer can use to help improve a system’s safety and reliability. While the main focus of this article is on one specific pattern (Watchdog Pattern), these are a few other patterns you may come across when researching safety critical design:
This article will focus on implementing a watchdog pattern using a mix of software and hardware.
Hardware Watchdog
A standard feature on EFM32 and EFR32 microcontrollers is a hardware watchdog. The hardware watchdog is a timer that when enabled, constantly counts up. The watchdog timer has a user configured timeout value that the application is responsible for “feeding” (clearing) the watchdog counter before the counter reaches that value. If the watchdog reaches the configured time value, a system reset will occur. The assumption is because the application did not feed the watchdog in time, an error has occurred in the system and it must be reset.
The hardware watchdog pattern works well for super-loop applications or in RTOS applications where only one task needs to be monitored and a system-wide reset would not negatively affect other tasks in the system. Complications arise when you wish to ensure multiple tasks are running as expected. One way to solve this problem is to implement a software watchdog that is responsible for handling the hardware watchdog.
This is a simplified overview of the hardware watchdog timer available on EFM32 and EFR32. For more detailed on the EFM32 & EFR32 watchdog timers, Series 0 can be found here and Series 1 can be found here.
Software Watchdog
A software watchdog pattern is a safety design pattern that allows for the monitoring of multiple tasks in an RTOS application. It is typically used in conjunction with a hardware watchdog, but in cases where a hardware watchdog is not available, the software watchdog can be responsible for performing the system reset.
Micrium OS Kernel provides two different kernel services that can be used to implement a software watchdog: Event Flags and Message Queues. For applications that have 32 tasks or less, an Event Flag is an ideal kernel service to use due to the Event Flag being able to monitor 32 unique bits. For applications with more than 32 tasks to be monitored, either multiple flag groups are needed or tasks can send unique values via a Message Queue to periodically check in.
In the image above, each task is represented with a unique bit in the Event Flag Group. The Event Flag is configured to pend on almost all bits of the Event Flag Group and has a timeout configured. If every task posts to the Event Flag before the timeout value is reached, the Event Flag pend returns with RTOS_ERR_NONE, the software watchdog will feed the hardware watchdog and then call pend on the Event Flag again.
If one or more tasks do not post to the Event Flag before the timeout value is reached, the Event Flag pend will return with RTOS_ERR_TIMEOUT. Also, the return value of the pend call will tell you what tasks have and have not checked in. Then with that information the software watchdog can make the decision on whether or not it should feed the hardware watchdog or let it reset the system.
Implementing a Software Watchdog in the Kernel 201 Application
The rest of this article will refer to the Kernel 201 Dynamic Multiprotocol Application. It is recommended to download the project to follow along. There are three files related to the software watchdog implementation: lab.h, lab_main.c and lab_wdog.c.
lab.h
The lab.h file is the centralized location for the application’s configuration parameters such as task priorities, stack sizes, message structures and enums. More detail on lab.h can be found in the blog Kernel 201: Task Architecture & Communication.
The defines above can be found in lab.h and are used as unique identifiers for each task in the system that will be monitored by the software watchdog. Each value will represent a bit in the Event Flag Group bitfield.
lab_main.c
When Micrium OS Kernel is initialized and started in main(), its common to only create one task before calling OSStart(). This is typically done to allow for the statistics task to get a baseline reading of the system at idle before other tasks are created.
In many sample applications the first task created is called the startup task because it is responsible for creating the rest of the tasks in the system and “starting up” the application. This is a poor naming choice for a task as after the startup task creates the other tasks in the system, some examples delete the startup task. What many do not realize is calling OSTaskDel() does not free up any resources related to the task. If the application does not know to access the startup task's stack and TCB, that memory sits unused.
It is not recommended to call OSTaskDel() on startup tasks. A better solution is to use the task for monitoring the system or repurpose the task for another task in the system.
Instead of calling it a startup task, the Kernel 201 Application calls it a system task. It could also be called the watchdog task since the software watchdog will run out of the system task. After the application has been initialized and all of the other tasks have been created, the last call in the labSystemTask function is:
Rather than creating a new task for the watchdog, the application uses the system task to run the watchdog.
lab_wdog.c
The software watchdog file is very straightforward with only three functions: labWDOGInit(), labWDOGTask() and labWDOGFeed().
labWDOGInit()
The init function initializes the hardware watchdog (but does not enable it yet) and creates the Event Flag for the software watchdog.
labWDOGTask()
The software watchdog task operates as follows:
The watchdog pends on the specified task flags as seen in lab.h, and the pend includes a timeout. If all tasks call a flag post at least once before the timeout value is reached, the OSFlagPend() call returns with RTOS_ERR_NONE, the hardware watchdog is fed via WDOG_Feed() and it loops around and starts the pend over again.
If one or more tasks fail to check in, the return value will be RTOS_ERR_TIMEOUT. In this example the code will then sit in a while(1) until the hardware watchdog resets the system. This would be the ideal location to either add logic to see what flags did or did not check in, and also to add a flag to persistent memory so when the system does reset, it can see on boot that it reset due to an error.
labWDOGFeed()
The feed function is provided so all tasks do not need to have a reference to the Watchdog Event Flag. When tasks check in they just need to provide their identifier defined in lab.h. Since a flag group bit can only be 0 or 1, it does not matter if a task checks in once or multiple times each pend cycle.
Since every task is centered around a Message Queue, the timeout parameter on the message queue is set to a value less than the watchdog flag group to ensure that each task feeds the watchdog before the watchdog timeout.
Final Thoughts
One downside to this pattern is it does not work well with a low-power, battery operated system where you may want to sleep for longer periods of time than the hardware watchdog can handle. However, if power is not a concern the software watchdog pattern provides a simple way to ensure all tasks in an RTOS application are operating as expected. If you have any questions or comments, feel free to leave them below.
Kernel 201: Task Architecture & Communication
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Task Architecture
The first step of designing any real-time system, before writing any code, is to identify all events the system will be responsible for. Listing out these actions will help identify what events may be grouped together in tasks and what actions will require their own task/s. There are a number of resources available that discuss different task modeling strategies. Some common strategies you may come across are:
For this application, these are the following events that must be handled and how they’ll be grouped into tasks:
labBluetoothTask
Silicon Labs’ Bluetooth stack already creates two tasks, Bluetooth and LinkLayer to handle the transmission and reception. The Bluetooth task provides callbacks for the user to implement to know when there is an event to process. The labBluetoothTask will handle those events from the Bluetooth task and pass that data along to the other tasks in the system. The labBluetoothTask will also receive data from other tasks in the system to transmit via Bluetooth.
labPropTask
Silicon Labs does not provide internal tasks for Proprietary Wireless (RAIL) as it does for Bluetooth, but it is important to group the RAIL functions together as they are not thread-safe functions. The labPropTask will handle all data received by RAIL and send it to other tasks in the system as needed. It will also receive data from other tasks in the system and send it out via RAIL.
labLEDTask
Since only the GPIO or RGB LEDs can be active at one time, it makes sense to group them together.
labBtnTask
The button task operates on a low priority polling interval. It could be switched to an interrupt based task but by leaving it as a polling task, it leaves open the option to add other polling events to the task in the future.
labWDOGTask
The watchdog task will implement a software based watchdog to control how the system feeds the hardware watchdog. It is important that this task have no other operations than handling the watchdog.
Task Communication
Now that the events have been divided up into their respective tasks, it is important to implement a consistent method for communication between tasks. There are again many different ways to implement task communication. Micrium OS Kernel provides Semaphores, Task Semaphores, Event Flags, Message Queues and Task Message Queues. While each of these kernel objects have various pros and cons, no one of these services is better than another; it just depends on the application and how it is using these kernel services.
For this application, each task (with the exception of the watchdog task) will be centered around a Task Message Queue and the entire application will use a sort-of layered messaging scheme, similar to the OSI model but much more simplified. The main loop of each task will pend on a Task Message Queue and wait for a message or timeout to determine its next action.
The image above shows the basic layout for all tasks in the system. The code snippet below shows some pseudocode for the task
By using the Task Message Queue, it allows a task to wait on three different types of events:
Timeouts
The software watchdog wants every task to check in on a specified rate to show it has not gone out to lunch. By specifying a timeout value in the Task Message Queue pend, the pend call will return with RTOS_ERR_TIMEOUT if a message is not received in the specified time. If a task expects a message in a set time interval, logic can also be added to the error check for RTOS_ERR_TIMEOUT to keep track of how many timeouts we receive in a row.
Events
Some tasks may have a hardware interrupt or software timer callback they need to handle in addition to processing messages from other tasks in the system. The msg_size data field in OSTaskQPend() provides a 32-bit value that is used as an identifier field for this task design. When a task receives a message, it will have a switch statement to handle the value of msg_size. In this application, all message identifiers can be found in an enum called LAB_Q_ID_e.
When an interrupt or software timer callback wants to send a message to the task it is associated with, it will make a call similar to the one below:
It is important to note that the LAB_Q_ID_e value is specified in the msg_size field and not the void* field. All messages sent must have a valid identifier in the msg_size field where the void* argument is optional. In this case, the void* argument is set to 0 as there is no extra data to send.
Lab Messages
These messages are how tasks communicate with each other in this application. Rather than having the Bluetooth or Proprietary Wireless tasks spend time configuring PWM timers to drive the RGB LEDs, it makes more sense to send the LED task a LED command to be processed. This also allows us to handle wireless communication at a higher priority than changing the LEDs or polling the button states. The wireless tasks may be affected if responses are not sent out at specific intervals where if the system has to wait an extra 10ms to toggle the LED, the user most likely won’t notice.
The lab messages follow a layered model where every message has the same header, the header defines what else is to follow in the packet. This application provides utility functions for getting, sending and freeing messages (refer to lab_util.c). There are advantages to these utility functions that will be discussed a little later. When you get/free a message you are actually pulling from a memory pool of fixed block sizes. All messages, regardless of how much of the block they use, are the same size. In the case of this application, all messages are 32 bytes in size. This means that if a task needs to send or receive a message larger than 32 bytes, either the size of every block in the system must be increased, multiple messages must be sent or modifications must be made to the utility functions to allow for allocation and freeing of different size blocks.
The struct above shows the lab message structure that all messages will start with. The ID field identifies what message will follow it and the type corresponds to if it’s a command, response, update or error message. The void* argument is used so you can cast the next layer to the end of the lab message struct.
When you wish to allocate a message to send, the code snippet above shows how you would use the labUtil function to allocate a message. The image below shows how the lab message fits into the allocated block.
After you configure the message ID and message type fields, you need to configure the next part of the block for the message ID you specified. For this example, let’s assume we’re changing the LEDs. LEDs are controlled by the LAB_LED_MSG_s struct. To accomplish this, you will cast the LAB_LED_MSG_s to p_data so when you enter information for the LED message, it goes into the correct location in lab message.
The code snippet above shows how to cast the p_data argument to the LED message structure. Now when you modify the fields of the LED message structure it will be aligned in the message block as shown below.
Once the full message is assembled, the message needs to be sent to the desired task/s. Some messages may only go to one task where others may need to be sent to multiple tasks. The lab utility function for sending offers flexibility for sending messages. Using the labUtilSend() function as shown below, you pass the pointer to the message block just configured and the task/s the message should be sent to.
In the case where one message is being sent to multiple tasks, the send function will keep track of the number of tasks it was sent to. This allows each task to call the free function after its processed the message, but the memory block is not actually freed until all tasks that received the message free it. To free a message all a task needs to do is make the following call:
It is the labUtilMsgFree function's job to determine when the message is actually returned to the message pool. If one task never calls free on the message it received, that block will never be freed and that memory will be lost so it is imperative that all tasks free all messages they receive, even if they receive the message in error.
Final Thoughts
This wraps up the Task Communication and Architecture section of the Kernel 201 application. Moving forward the Bluetooth, Proprietary Wireless, LED and Button tasks will all be using this communication structure. If you have any questions about this post feel free to leave comments below.
Kernel 201: Configuring Micrium OS
This blog is part of the Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS. The table of contents for this blog series can be found here.
Note: It is recommended to download the Kernel 201 application and have the code in front of you as you go through this blog post. The code can be downloaded on the Kernel 201: Project Resources page.
Introduction
Micrium OS offers default configurations to allow a user to get started quickly. These configurations are useful for quick starts, test applications and instances where memory is not a constraint. It is important that all developers using Micrium OS do give some attention to the RTOS configuration as the defaults are not ideal for a released application.
To ensure the dynamic multiprotocol application starts off on a good foot, one of the first steps that will be taken is to configure Micrium OS. Most of the sample applications available in Simplicity Studio provide a good starting point, but this post will look a little more in-depth at the configuration that’s available.
Micrium OS Configuration Files
os_cfg.h
Note: Some example projects have already made modifications to os_cfg.h. The comments here are based on differences between the default os_cfg.h found in the Gecko SDK Micrium OS directory and this project.
This file provides all compile-time options for Micrium OS Kernel. It provides the ability to turn on and off almost all parts of the kernel. Typically, the default values are fine to start a project out with. In this project there are a few differences from the default config worth highlighting:
App Hooks
App hooks enable will turn on the callback functions (if one is defined) anytime the following events occur:
The hook that is set up in most projects is the idle task hook. Since this hook is called from the idle task loop, it is an ideal time to enter a lower power mode. Right now the kernel can run from EM0, EM1 or EM2 (EM2 requires use of the LFRCO).
Dynamic Tick
Dynamic tick allows the kernel to adjust the tick rate as needed to minimize the amount of time the kernel has to wake from sleep. The images below show the advantage of using dynamic tick over a periodic tick.
Periodic Tick
Dynamic Tick
Priority Maximum
The PRIO_MAX setting controls the maximum number of task priorities allowed in the system. Micrium OS supports an unlimited* number of task priorities but there are advantages of having the value set to a smaller number.
If a system only has 10 tasks, there is an advantage to setting PRIO_MAX to 32 vs 64, 128, 256, 512 or some other very large number. The way the kernel’s scheduler looks for the next available task is it has an array called OSPrioTbl[] which is defined as follows:
Each bit in the PrioTbl corresponds to a task priority so since each row holds 32 bits, that’s 32 priority levels that can be checked in one go. If PRIO_MAX is set to 512, the kernel will potentially have to go through 16 rows before deciding to drop to the idle task. That’s a lot of extra overhead for task priorities that are not being used.
*Hardware provides the limitation, there is no limitation in software.
OSTaskDel
The OSTaskDel() should be disabled by default in almost all Micrium OS applications. Deleting a task only removes it from the kernel so it is no longer scheduled. It does not take into account any kernel objects associated with the task nor does it free up any memory associated with the task being deleted. There are very few situations where OSTaskDel() is needed.
common_cfg.h
The common_cfg.h file is used to configure the memory, string and clock modules. The defaults for string and clock are fine for this project, but since the goal of this project is to reduce the kernel’s footprint the memory will be adjusted.
By default the kernel uses an internal heap to allocate space for internal tasks and message queues. By making the following change to the heap size, it will turn off the internal heap.
If this was the only change is made to the project, the project would compile but fail immediately during runtime when OSInit() is called due to no memory being available. A change also has to be made in rtos_cfg.h to disable the use of the internal heap for internal tasks and message queues.
rtos_cfg.h
The rtos_cfg.h file controls three things:
Assert configuration
Memory usage configuration for internal tasks and message queues
The assert configuration defaults to locking the system in a while(1) loop for debug and critical asserts. The debug asserts should be modified when going into production but for development it can be useful to leave the while(1) loop to find issues quicker.
The logging is an optional module that is not used in this project.
The Default Config configuration is what determines if the heap is used for internal tasks or not. By making the following change, it will require that a number of internal structures are externally configured. This will allow for the kernel to operate with the heap being set to 0.
rtos_description.h
This file defines what Micrium OS modules are enabled. For this project only two defines are needed:
A full list of available RTOS_MODULE options can be found in the Gecko SDK version of rtos_description.h.
Main Configuration
Once the changes have been made to the necessary config files, the desired kernel configuration must be made in the application. In this example, the file lab_main.c has the following kernel configuration.
Tick Task
The tick task is used to control the kernel’s system tick which is used for time delay calls and pend timeouts. In this application it is based off of the RTCC interrupt. The following configuration will define a stack for the tick task and set the config only if OS_CFG_TASK_TICK_EN is set in the os_cfg.h file. The stack size, priority and frequency of the tick task are all configured in the lab.h file.
Idle Task
The idle task is the lowest priority task in the system. When the kernel has no other tasks to schedule, it will run the Idle Task. When entering the Idle Task, there is a hook function that can be implemented to allow the user application to enter a lower power mode. The following configuration will define a stack for the idle task and set the config only if OS_CFG_TASK_IDLE_EN is set in the os_cfg.h file. If the idle task is not enabled, when the current task finishes the scheduler sits in a while(1) loop until another action happens in the system rather than jump to a different task. When the idle task is enabled the stack size of the idle task can be found in lab.h. You can not set the priority of the idle task because it must always be the lowest priority task in the system.
Timer Task
The timer task is used to control Micrium OS Kernel’s software timers. The reason the timers operate in a different task than the tick task is the timer callback functions are made from the timer task, so it allows the timer callbacks to run at a different priority than the tick task if desired. The following configuration will define a stack and set the config only if OS_CFG_TMR_ EN is enabled in the os_cfg.h file. The stack size, priority and frequency of the timer task are all configured in the lab.h file.
Stat Task
The stat task is an internal task that provides such run-time statistics as overall CPU utilization (0.00 to 100.00%), per-task CPU utilization (0.00 to 100.00%), and per-task stack usage. The following configuration will define a stack and set the config only if OS_CFG_STAT_TASK_EN is enabled in the os_cfg.h file. The stack size, priority and frequency of the stat task are all configured in the lab.h file.
Interrupt Stack
The ARM Cortex-M series has a separate stack for interrupts to execute out of. Micrium OS Kernel will configure the stack location during the OSInit() call, but a stack must be provided. The following configuration creates the interrupt stack and sets the necessary configuration. This is not an optional configuration, it is required of all Micrium OS Kernel applications.
Message Pool
If message queues will be used in an application (this application makes heavy use of them) then a message pool must be defined. When messages are sent in either a message queue or task message queue, a message pool object is allocated to hold the data being sent (void* argument and message size argument). The message pool should have enough entries for every message queue entry in the application. For example, if you have 3 message queues each with 10 entries in them, you should have a message pool size of 30. The following configuration is used to specify the message pool size in this application.
In addition to specifying the configuration, a memory segment and memory buffer must be allocated as follows:
And finally, in the main() before OSInit() is called, the memory segment must be created so the kernel knows how to access the memory buffer specified above.
Putting it all together
You may have noticed that all of the configs are just #defines. These #defines are used to create one giant #define as shown below.
Once the OS_INIT_CFG_APP define is put assembled with all of the task and memory pool configs, the define should be assigned to the global variable as follows:
This variable is externed by the kernel so it must be named OSInitCfg as this is what the kernel uses internally.
Final Thoughts
While it may appear a little complicated, the configuration of Micrium OS Kernel is very important to the success of an application and worth the extra effort. A misconfigured kernel can cause issues farther down the road so it is important to configure it correctly to start with. By removing the general purpose heap it allows the developer to have a full understanding of what's going on under the hood so there is no confusion about how the kernel is configured. If you have any questions or comments feel free to leave them below.
Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS
Introduction
When developing an application with a real-time operating system, developers have numerous design decisions to make when starting a project. These design decisions can greatly affect how a system performs, how easy it is to expand an application and even how well a system can tolerate failures.
The following blog series will take an in-depth look at designing a real-time operating system application in a dynamic multiprotocol system (Bluetooth + Proprietary Wireless). It will walk through configuring Micrium OS, discuss task architecture, look in-depth at the Bluetooth and Proprietary Wireless tasks, and look at some safety design patterns.
Table of Contents
Kernel 201: Configuring Micrium OS
Kernel 201: Task Architecture & Communication
Kernel 201: Implementing a Software Watchdog
Kernel 201: Bluetooth Task
Kernel 201: WebBluetooth App
Kernel 201: Proprietary Wireless Task
Kernel 201: UI Tasks
Kernel 201: Instructor Application & Electron App
Kernel 201: Project Resources
The goal of the application discussed throughout the blog series is to run the dynamic multiprotocol application on a Thunderboard Sense 2 board. There is a web application that will communicate over Bluetooth with the Thunderboard using the WebBluetooth API. The Thunderboard will also communicate to a Blue Gecko board via Proprietary Wireless. The image below shows the layout of communication.