Silicon Labs
|
Silicon Labs Community Silicon Labs Community
  • Products
    1. 8-bit MCU
    2. 32-bit MCU
    3. Bluetooth
    4. Proprietary
    5. Wi-Fi
    6. Zigbee & Thread
    7. Z-Wave
    8. Interface
    9. Isolation
    10. Power
    11. Sensors
    12. Timing
  • Development Tools
    1. Simplicity Studio
    2. Third Party Tools
  • Expert's Corner
    1. Announcements
    2. Blog
    3. General Interest
    4. Projects
How to Buy
English
  • English
  • 简体中文
  • 日本語
//
Community // Blog

Official Blog of Silicon Labs

  • Show More
    Publish
    • Immediately
    • Draft
    • At scheduled date and time
     
      • Kernel 201: Instructor Application & Electron App

        Mark Mulrooney | 08/228/2019 | 06:04 PM

        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. 

        1. 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.
        2. The labPropRxTask() runs due to the message being received and calls labPropRadioRxData() to process the RAIL packet.
        3. 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.
        4. 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.
        5. 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. 

      • Kernel 201: UI Tasks

        Mark Mulrooney | 07/197/2019 | 09:36 PM

        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.

      • Kernel 201: Proprietary Wireless Task

        Mark Mulrooney | 07/189/2019 | 10:20 PM

        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.

        labPropRailHandle = RAIL_Init(&labPropRailCfg, NULL);       // 1
        
        RAIL_SetTxFifo(labPropRailHandle,                           // 2
                       labPropTxFifo,
                       0,
                       LAB_PROP_TX_FIFO_SIZE);
        
        
        RAIL_TxPowerConfig_t railTxPowerConfig = {
        #if HAL_PA_2P4_LOWPOWER
            .mode     = RAIL_TX_POWER_MODE_2P4_LP,
        #else
            .mode     = RAIL_TX_POWER_MODE_2P4_HP,
        #endif
            .voltage  = HAL_PA_VOLTAGE,
            .rampTime = HAL_PA_RAMP,
        };
        
        if (channelConfigs[0]->configs[0].baseFrequency < 1000000000UL) {
            railTxPowerConfig.mode = RAIL_TX_POWER_MODE_SUBGIG;
        }
        
        RAIL_EnablePaCal(true);                                     // 3
        status = RAIL_ConfigTxPower( labPropRailHandle,
                                    &railTxPowerConfig);
        APP_RTOS_ASSERT_CRITICAL((status == RAIL_STATUS_NO_ERROR), 1);
        
        RAIL_SetTxPower(labPropRailHandle,
                        HAL_PA_POWER);
        
        RAIL_ConfigChannels(labPropRailHandle,                      // 4
                            channelConfigs[0],
                            NULL);
        
        RAIL_ConfigCal(labPropRailHandle,
                       RAIL_CAL_ALL);
        
        RAIL_ConfigEvents(labPropRailHandle,                        // 5
                          RAIL_EVENTS_ALL,
                          RAIL_EVENT_SCHEDULER_STATUS
                          | RAIL_EVENTS_TX_COMPLETION
                          | RAIL_EVENTS_RX_COMPLETION
                          | RAIL_EVENT_CAL_NEEDED);
        
        RAIL_SetFixedLength(labPropRailHandle,                      // 6
                            LAB_PROP_PACKET_SIZE);
        
        schedulerInfo.priority = 200;                               // 7
        RAIL_StartRx( labPropRailHandle,
                      LAB_PROP_RX_CHANNEL,
                     &schedulerInfo);
        

         

        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.

        1. The labPropRadioInit() function first initializes the RAIL handle. The same RAIL handle will be used for transmit and receive in the Proprietary Wireless task.
        2. 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.
        3. 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.
        4. 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.
        5. 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.
        6. 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.
        7. 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.

         

        while (DEF_TRUE) {
            p_msg = OSTaskQPend( LAB_WDOG_TASK_TIMEOUT_MS,
                                 OS_OPT_PEND_BLOCKING,
                                &lab_q_id,
                                 0,
                                &err);
        
            do {
                if(err.Code == RTOS_ERR_TIMEOUT) {
                    break;
                } else if(err.Code != RTOS_ERR_NONE) {
                    APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
                }
        
                switch(lab_q_id) {
                    case LAB_Q_ID_PROP_RX:
                        labPropRadioRxData((RAIL_RxPacketHandle_t)p_msg);
                        break;
        
                    case LAB_Q_ID_PROP_TMR:
                        labPropRadioTxData();
                        break;
        
                    case LAB_Q_ID_MSG:
                        labPropUpdateStatus(p_msg);
                        labUtilMsgFree(p_msg);
                        break;
        
                    default:
                        break;
                }
        
            } while(0);
        
            labWDOGFeed(LAB_TASK_PROP);
        }
        

         

        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.

        typedef struct {
            CPU_INT16U seq_num;
            CPU_INT08U device_name[LAB_DEVICE_NAME_MAX_LEN];
        } LAB_PROP_DATA_TX_s;
        

         

        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.

        typedef struct {
            LAB_MSG_LED_s led;
            LAB_MSG_BTN_s btn;
            LAB_MSG_SI7021_s si7021;
            CPU_INT32U err_cnt;
            CPU_INT32U updated;
        } LAB_PROP_STATUS_s;
        

         

        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.

         

        1. 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.
        2. 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.
        3. 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.

        typedef struct {
            CPU_INT16U seq_num;
            CPU_INT16U task_dest;
            void* p_data;
        } LAB_PROP_DATA_RX_s;
        

         

        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.

         

        1. 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.
        2. 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. 

      • Kernel 201: WebBluetooth App

        Mark Mulrooney | 06/178/2019 | 03:02 PM

        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
        • 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.

        function btConnect()
        {
            navigator.bluetooth.requestDevice({ 
                filters: [{ services: [btServiceUUID] }] 
            })
            .then(device => {
                btDevice = device;
                btDevice.addEventListener('gattserverdisconnected', 
                                          bt_cb_ondisconnected);
        
                return btDevice.gatt.connect()
            })
            .then(server => {
                bt_view_connection(true);
                return server.getPrimaryService(btServiceUUID)
            })
            .then(service => {
                btService = service;
        
                return bt_listen(service);
            })
            .then(_ => {
                return bt_update();
            })
            .catch(error => {
                console.log("Error: " + error);
            });
        }
        

         

        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.

      • Kernel 201: Bluetooth Task

        Mark Mulrooney | 06/177/2019 | 08:42 PM

        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

        static const gecko_configuration_t bluetooth_config =
        {
            .config_flags              = GECKO_CONFIG_FLAG_RTOS,
            .sleep.flags               = 0,
            .bluetooth.max_connections = LAB_BLUETOOTH_MAX_CONNECTIONS,
            .bluetooth.heap            = bluetooth_stack_heap,
            .bluetooth.heap_size       = sizeof(bluetooth_stack_heap),
            .gattdb                    = &bg_gattdb_data,
            .ota.flags                 = 0,
            .ota.device_name_len       = 3,
            .ota.device_name_ptr       = "OTA",
            .scheduler_callback        = Bluetooth_LLCallback,
            .stack_schedule_callback   = Bluetooth_UpdateCallback,
        #if (HAL_PA_ENABLE)
            .pa.config_enable          = 1,
        #if defined(FEATURE_PA_INPUT_FROM_VBAT)
            .pa.input                  = GECKO_RADIO_PA_INPUT_VBAT,
        #else
            .pa.input                  = GECKO_RADIO_PA_INPUT_DCDC,
        #endif
        #endif
            .mbedtls.flags             = GECKO_MBEDTLS_FLAGS_NO_MBEDTLS_DEVICE_INIT,
            .mbedtls.dev_number        = 0,
        };
        

         

        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:

        void  labBluetoothEvtCallback (void)
        {
            RTOS_ERR err;
        
        
            OSTaskQPost(&labBluetoothTaskTCB,
                         0,
                         LAB_Q_ID_BLUETOOTH_EVT,
                         OS_OPT_POST_FIFO,
                         &err);
        }
        

         

        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:

         

        1. The user makes a request on the WebBluetooth App to change the LED color to red.
        2. The WebBluetooth App sends a Bluetooth write command to the Kernel 201 Application.
        3. 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.
        4. The labBluetoothTask receives the LAB_Q_ID_BLUETOOTH_EVT in its Task Message Queue and handles it as follows:
          1. Get the Bluetooth Event from the Bluetooth stack.
          2. Determine it is a write event.
          3. Determine it is an LED write event.
          4. 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.
        5. The labLEDTask receives the LAB_Q_ID_MSG along with the lab message buffer pointer.
          1. It proceeds to change the LED as requested.
          2. 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.
        6. The labBluetoothTask receives a LAB_Q_ID_MSG in its Task Message Queue.
          1. The message is sent to the Silicon Labs internal Bluetooth stack via the Gecko characteristic notification command
        7. The Silicon Labs internal Bluetooth stack processes the characteristic notification command and sends the data to the WebBluetooth App.
        8. 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.

      • Making Peripheral Drivers Thread-safe

        Janos Magasrevy | 06/177/2019 | 03:27 PM

        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.

        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

        Mark Mulrooney | 06/169/2019 | 09:40 PM

        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.

        #define  LAB_TASK_BLUETOOTH 0x01
        #define  LAB_TASK_PROP      0x02
        #define  LAB_TASK_LED       0x04
        #define  LAB_TASK_BTN       0x08
        

         

        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.

      • Kernel 201: Task Architecture & Communication

        Mark Mulrooney | 06/165/2019 | 09:11 PM

        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:

        OSTaskQPost(&labXXXTaskTCB,
                     0,
                     LAB_Q_ID_XXX_EVT,
                     OS_OPT_POST_FIFO,
                    &err);
        

         

        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.

        typedef struct {
            LAB_MSG_ID_e id;
            LAB_MSG_TYPE_e type;
            void *p_data;
        } LAB_MSG_s;
        

        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.

      • Kernel 201: Configuring Micrium OS

        Mark Mulrooney | 06/165/2019 | 08:12 PM

        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:

        #define  OS_CFG_APP_HOOKS_EN        DEF_ENABLED
        #define  OS_CFG_DYN_TICK_EN         DEF_ENABLED
        #define  OS_CFG_PRIO_MAX            32u
        #define  OS_CFG_TASK_DEL_EN         DEF_DISABLED
        

         

        App Hooks

        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:

        CPU_INT32 OSPrioTbl[(((OS_CFG_PRIO_MAX - 1u) / DEF_INT_CPU_NBR_BITS) + 1u)]

         

        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.

        #define  RTOS_CFG_EXTERNALIZE_OPTIONAL_CFG_EN               DEF_ENABLED

         

        rtos_description.h

        This file defines what Micrium OS modules are enabled. For this project only two defines are needed:

        #define  RTOS_MODULE_KERNEL_AVAIL
        #define  RTOS_MODULE_COMMON_CLK_AVAIL

         

        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.

        #if (OS_CFG_TASK_TICK_EN == DEF_ENABLED)               // Tick Task Configuration
        static CPU_STK TickTaskStk[LAB_OS_TICK_TASK_STK_SIZE];
        #define TICK_TASK_CFG  .TickTaskCfg =       \
        {                                           \
            .StkBasePtr = &TickTaskStk[0],          \
            .StkSize    = LAB_OS_TICK_TASK_STK_SIZE,\
            .Prio       = LAB_OS_TICK_TASK_PRIO,    \
            .RateHz     = LAB_OS_TICK_TASK_RATE_HZ  \
        },
        #else
        #define TICK_TASK_CFG
        #endif 
        

         

        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.

        #if (OS_CFG_TASK_IDLE_EN == DEF_ENABLED)               // Idle Task Configuration
        static CPU_STK IdleTaskStk[LAB_OS_IDLE_TASK_STK_SIZE];
        #define IDLE_TASK_CFG  .IdleTask =          \
        {                                           \
            .StkBasePtr = &IdleTaskStk[0],          \
            .StkSize    = LAB_OS_IDLE_TASK_STK_SIZE \
        },
        #else
        #define IDLE_TASK_CFG
        #endif
        

         

        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.

        #if (OS_CFG_TMR_EN == DEF_ENABLED)                     // Timer Task Configuration
        static CPU_STK TimerTaskStk[LAB_OS_TMR_TASK_STK_SIZE];
        #define TIMER_TASK_CFG  .TmrTaskCfg =       \
        {                                           \
            .StkBasePtr = &TimerTaskStk[0],         \
            .StkSize    = LAB_OS_TMR_TASK_STK_SIZE, \
            .Prio       = LAB_OS_TMR_TASK_PRIO,     \
            .RateHz     = LAB_OS_TMR_TASK_RATE_HZ   \
        },
        #else
        #define TIMER_TASK_CFG
        #endif
        

         

        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.

        #if (OS_CFG_STAT_TASK_EN == DEF_ENABLED)               // Statistics Task Config
        static CPU_STK StatTaskStk[LAB_OS_STAT_TASK_STK_SIZE];
        #define STAT_TASK_CFG  .StatTaskCfg =       \
        {                                           \
            .StkBasePtr = &StatTaskStk[0],          \
            .StkSize    = LAB_OS_STAT_TASK_STK_SIZE,\
            .Prio       = LAB_OS_STAT_TASK_PRIO,    \
            .RateHz     = LAB_OS_STAT_TASK_RATE_HZ  \
        },
        #else
        #define STAT_TASK_CFG
        #endif
        

         

        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.

        static CPU_STK ISRStk[LAB_OS_ISR_STK_SIZE]; // ISR Configuration
        #define ISR_CFG                  .ISR = \
        {                                       \
            .StkBasePtr = (CPU_STK*)&ISRStk[0], \
            .StkSize    = LAB_OS_ISR_STK_SIZE   \
        },
        

         

        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.

        #define  MSG_POOL_SIZE  (LAB_TOTAL_MSG_Q * sizeof(OS_MSG))
        #define  MSG_POOL_CFG    .MsgPoolSize = LAB_TOTAL_MSG_Q, \
                                 .MemSeg      = &MsgPoolMemSeg,
        

         

        In addition to specifying the configuration, a memory segment and memory buffer must be allocated as follows:

        static  MEM_SEG    MsgPoolMemSeg;
        static  CPU_INT08U MsgPoolMem[MSG_POOL_SIZE];
        

         

        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.

        #define  OS_INIT_CFG_APP            {   \
            ISR_CFG                             \
            IDLE_TASK_CFG                       \
            TICK_TASK_CFG                       \
            TIMER_TASK_CFG                      \
            STAT_TASK_CFG                       \
            MSG_POOL_CFG                        \
            .TaskStkLimit    = 0u               \
        }
        

         

        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.

      • Kernel 201: Designing a Dynamic Multiprotocol Application with Micrium OS

        Mark Mulrooney | 06/165/2019 | 08:07 PM

        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.

      • 1
      • 2
      • 3
      Next

      Tags

      • Wireless
      • High Performance Jitter Attenuators
      • EFR32FG22 Series 2 SoCs
      • EFR32MG21 Series 2 SoCs
      • Security
      • Bluegiga Legacy Modules
      • Zigbee SDK
      • ZigBee and Thread
      • EFR32BG13 Series 1 Modules
      • Internet Infrastructure
      • Sensors
      • Wireless Xpress BGX13
      • Blue Gecko Bluetooth Low Energy SoCs
      • Z-Wave
      • Micrium OS
      • Blog Posts
      • Low Jitter Clock Generators
      • Bluetooth Classic
      • Makers
      • Flex SDK
      • Tips and Tricks
      • timing
      • Smart Cities
      • Smart Homes
      • IoT Heroes
      • Reviews
      • RAIL
      • Simplicity Studio
      • Tiny Gecko
      • EFR32MG22 Series 2 SoCs
      • Mighty Gecko SoCs
      • Timing
      • Temperature Sensors
      • Blue Gecko Bluetooth Low Energy Modules
      • Ultra Low Jitter Clock Generators
      • General Purpose Clock Generators
      • EFR32BG22 Series 2 SoCs
      • Industry 4.0
      • Giant Gecko
      • 32-bit MCUs
      • Bluetooth Low Energy
      • 32-bit MCU SDK
      • Gecko
      • Microcontrollers
      • Jitter Attenuators
      • EFR32BG21 Series 2 SoCs
      • News and Events
      • Wi-Fi
      • Bluetooth SDK
      • Community Spotlight
      • Clock Generators
      • Biometric Sensors
      • General Purpose Jitter Attenuators
      • Giant Gecko S1
      • WF200
      • Flex Gecko
      • Internet of Things
      • 8-bit MCUs
      • Wireless Jitter Attenuators
      • Isolation
      • Powered Devices
      • Power

      Top Authors

      • Avatar image Siliconlabs
      • Avatar image Jackie Padgett
      • Avatar image Nari Shin
      • Avatar image lynchtron
      • Avatar image deirdrewalsh
      • Avatar image Lance Looper
      • Avatar image lethawicker

      Archives

      • 2016 March
      • 2016 April
      • 2016 May
      • 2016 June
      • 2016 July
      • 2016 August
      • 2016 September
      • 2016 October
      • 2016 November
      • 2016 December
      • 2017 January
      • 2017 February
      • 2017 March
      • 2017 April
      • 2017 May
      • 2017 June
      • 2017 July
      • 2017 August
      • 2017 September
      • 2017 October
      • 2017 November
      • 2017 December
      • 2018 January
      • 2018 February
      • 2018 March
      • 2018 April
      • 2018 May
      • 2018 June
      • 2018 July
      • 2018 August
      • 2018 September
      • 2018 October
      • 2018 November
      • 2018 December
      • 2019 January
      • 2019 February
      • 2019 March
      • 2019 April
      • 2019 May
      • 2019 June
      • 2019 July
      • 2019 August
      • 2019 September
      • 2019 October
      • 2019 November
      • 2019 December
      • 2020 January
      • 2020 February
      • 2020 March
      • 2020 April
      • 2020 May
      • 2020 June
      • 2020 July
      • 2020 August
      • 2020 September
      • 2020 October
      • 2020 November
      • 2020 December
      • 2021 January
      • 2021 February
      Silicon Labs
      Stay Connected With Us
      Plug into the latest on Silicon Labs products, including product releases and resources, documentation updates, PCN notifications, upcoming events, and more.
      • About Us
      • Careers
      • Community
      • Contact Us
      • Corporate Responsibility
      • Privacy and Terms
      • Press Room
      • Investor Relations
      • Site Feedback
      • Cookies
      Copyright © Silicon Laboratories. All rights reserved.
      粤ICP备15107361号
      Also of Interest:
      • Bring Your IoT Designs to Life with Smart,...
      • A Guide to IoT Protocols at Works With...
      • IoT Hero Rainus Enhances the In-Store Shopping...