You now have a good grasp of interrupt and thread contexts, as well as how to pass data between threads safely using a message queue. This time, let’s practice passing data between threads using FIFO and also explore the kernel options available in nRF Connect SDK and Zephyr.
A FIFO is used to pass data items of variable size or a variable number between threads, or from an ISR to a thread. For example, if you receive data from a peripheral (UART) and don’t know the data size, you can use the FIFO with the heap to allocate memory to the received data and pass it to other software components in the system.
In this exercise, you will develop an application with the same software components as the previous exercise: five threads (producer, consumer, main, logging, idle), and a function that runs periodically every 500 milliseconds (timer) in an interrupt context. The difference is that the producer thread this time will generate a random number of data items. This emulates a situation where data is run-time dependent and the number or size of data items can’t be predicted at compile time.
Same as in the previous exercise, the main thread will setup the GPIO pins for LED0 and LED1, start a timer and then terminate normally by returning 0.
When the main thread terminates, the producer thread is the highest priority thread in the ready queue, so it will execute. In the producer thread, you will generate a random number of data items (between 4 and 14), and put them in the FIFO and the thread will go to sleep for PRODUCER_SLEEP_TIME_MS, which is 2200 ms.
The consumer thread will execute after the producer thread. It gets all data items submitted by the producer thread, and then becomes Unready when all data items in the FIFO are consumed.
The logger module will execute next and send all logs to the logging backend (UART0), and finally the idle thread is executed to put the system in a power-saving mode. Once the time defined in PRODUCER_SLEEP_TIME_MS has elapsed, the cycle repeats itself.
Exercise steps
As covered in the previous exercise, you need to open the code base of the exercise. Open the nRF Connect For VS Code extension, navigate to Create a new application, select Copy a sample, and search for Lesson 1 – Exercise 2. Make sure to select the right version directory when copying as explained in the previous exercise.
Alternatively, in the GitHub repository for this course, go to the base code for this exercise, found in l1/l1_e2 of whichever version directory you are using.
1. Enable random number generation.
This is needed for emulation purposes only, to generate a random number of data items in the producer thread. Add the following Kconfig Symbol in the application configuration file (prj.conf):
Copy
CONFIG_ENTROPY_GENERATOR=y
Kconfig
This Kconfig enables the entropy driver to generate a random number based on the cryptographic hardware available in the chip. The driver is selected automatically by the build system based on the default value available in your board Kconfig files (for example, with the nRF52840 DK, the CryptoCell 310 driver will be enabled). Alternatively, you can use the Kconfig option CONFIG_TEST_RANDOM_GENERATOR, which is intended for testing purposes only and never for production code.
In the producer thread, you will use the random number generator function sys_rand32_get() to get a random value between 4 and 14 to emulate a random number of data items at run-time. You will also use it to generate a Return, a 32-bit value to be passed as part of the data item between the producer thread and the consumer thread.
2. Allocate proper heap size for FIFO usage.
You need to predict the maximum number of data items to calculate the heap size allocated for FIFO usage. In this sample, the expected maximum number no more than 14 data items, each of which is 40 bytes. Since 14×40 = 560, the heap size needs to be higher than that (1024 bytes).
Copy
CONFIG_HEAP_MEM_POOL_SIZE=1024
Kconfig
3. Define the FIFO.
Add the following line in main.c:
Copy
K_FIFO_DEFINE(my_fifo);
C
4. Define the data type of the FIFO items.
In the data item, we want to store a string with the following format:
Data Seq. <Unsigned Integer> <Random Unsigned Integer>
Where Unsigned Integer is incremented by 1 every time the producer thread pushes a data item into the FIFO, and Random Unsigned Integer is a value returned by calling sys_rand32_get(). Therefore we will define the data item as:
The first member of the struct, fifo_reserved, is mandatory for all FIFOs and is used internally by the kernel. The second member, data, is an array of uint8_t of size MAX_DATA_SIZE, and the last member, len, will hold the actual data written into the array.
5. Add data items into the FIFO.
Add the following code inside the producer_func() function:
Copy
while (1) {int bytes_written; /* Generate a random number between MIN_DATA_ITEMS & MAX_DATA_ITEMS to represent the number of data items to send every time the producer thread is scheduled */uint32_t data_number = MIN_DATA_ITEMS + sys_rand32_get() % (MAX_DATA_ITEMS - MIN_DATA_ITEMS + 1);for (int i = 0; i < data_number; i++) { /* Create a data item to send */structdata_item_t *buf = k_malloc(sizeof(structdata_item_t));if (buf == NULL) { /* Unable to locate memory from the heap */LOG_ERR("Unable to allocate memory");return; } bytes_written = snprintf(buf->data, MAX_DATA_SIZE, "Data Seq. %u:\t%u", dataitem_count, sys_rand32_get());buf->len = bytes_written; dataitem_count++;k_fifo_put(&my_fifo, buf); }LOG_INF("Producer: Data Items Generated: %u", data_number);k_msleep(PRODUCER_SLEEP_TIME_MS);}
C
Every time the producer thread is scheduled for execution, it will add data_number of data items in the FIFO where data_number is a random value between 4 and 14.
Notice that, to add a data item in a FIFO, you need to allocate memory space for the item (k_malloc()). It is essential to make sure that the memory allocation is successful by checking the value returned by k_malloc().
In the data item, you will write a string with the sequence number of the data item, and a random 32-bit unsigned integer inside buf->data. An example of a string generated inside the producer thread is shown below:
Copy
Data Seq. 1: 1733782513
The length of the string will be variable since it dependents on the random value generated and the sequence number. You will store the size of the string in buf->len. The data item is added to the FIFO using the function k_fifo_put(&my_fifo,buf);
6. Read data items from the FIFO.
Add the following code inside the consumer_func() function:
The consumer thread will get all the data items submitted by the producer thread by calling the function k_fifo_get(&my_fifo, K_FOREVER); inside an infinite loop. It will be put to sleep once all data items are consumed from the FIFO because the parameter K_FOREVER is used as a timeout option.
The consumer thread will submit the received data to the logger module in the following format:
[00:13:22.112,609] <inf> Less1_Exer2: Consumer: Data Seq. 1: 1733782513 Size: 23
Terminal
It’s critical that the application code frees the memory allocated to the data item after it has been consumed. This is done by calling k_free(). Failing to free the memory allocated to consumed data items will result in a run-time error (heap overflow).
7. Build and flash the application to your board.
8. Connect to the serial terminal and examine the output.
You should see a random number of data items passed between the producer and consumer thread. Below is a sample output.
[00:02:41.204,315] <inf> Less1_Exer2: Producer: Data Items Generated: 4[00:02:41.204,376] <inf> Less1_Exer2: Consumer: Data Seq. 742: 1266499320 Size: 25[00:02:41.204,406] <inf> Less1_Exer2: Consumer: Data Seq. 743: 4061392639 Size: 25[00:02:41.204,467] <inf> Less1_Exer2: Consumer: Data Seq. 744: 1452774199 Size: 25[00:02:41.204,498] <inf> Less1_Exer2: Consumer: Data Seq. 745: 721881686 Size: 24[00:02:41.204,559] <inf> Less1_Exer2: Consumer: Data Seq. 746: 1090030288 Size: 25[00:02:43.407,135] <inf> Less1_Exer2: Producer: Data Items Generated: 11[00:02:43.407,196] <inf> Less1_Exer2: Consumer: Data Seq. 747: 2517510613 Size: 25[00:02:43.407,257] <inf> Less1_Exer2: Consumer: Data Seq. 748: 3661502652 Size: 25[00:02:43.407,287] <inf> Less1_Exer2: Consumer: Data Seq. 749: 2479144377 Size: 25[00:02:43.407,348] <inf> Less1_Exer2: Consumer: Data Seq. 750: 2723040850 Size: 25[00:02:43.407,379] <inf> Less1_Exer2: Consumer: Data Seq. 751: 3377936138 Size: 25[00:02:43.407,440] <inf> Less1_Exer2: Consumer: Data Seq. 752: 1772986057 Size: 25[00:02:43.407,470] <inf> Less1_Exer2: Consumer: Data Seq. 753: 3285953890 Size: 25[00:02:43.407,531] <inf> Less1_Exer2: Consumer: Data Seq. 754: 331749191 Size: 24[00:02:43.407,562] <inf> Less1_Exer2: Consumer: Data Seq. 755: 3144611545 Size: 25[00:02:43.407,623] <inf> Less1_Exer2: Consumer: Data Seq. 756: 1973893503 Size: 25[00:02:43.407,653] <inf> Less1_Exer2: Consumer: Data Seq. 757: 91793513 Size: 23[00:02:43.407,714] <inf> Less1_Exer2: Consumer: Data Seq. 758: 3285217816 Size: 25
Terminal
You can also do a debugging session like in the previous exercise to examine the content of the local variables and FIFO in run-time.
Now that you have practiced using FIFO using, the next step is exploring the Kernel options for an nRF Connect SDK application.
Exploring kernel options
In this part, you will dive into the Kernel options discussed in the Scheduler in-depth topic.
Starting from nRF Connect SDK v2.8.0, Sysbuild is the default build system. nRF Kconfig GUI allows you to display the Kconfig configurations for Sysbuild itself and any of its images in the project.
This is done by first selecting the image of interest from the APPLICATIONS viewand then clicking on the nRF Kconfig GUI in the ACTIONS view, as shown below where the application image is selected.
You can see where these are located in the screenshot below:
The nRF Kconfig GUI is a graphical representation of the complete application Kconfig options. This includes the prj.conf file, board default Kconfigs, SDK Kconfigs, and fragments. The GUI lists these options in groups, and lets you control them directly from VS Code. It parses the generated .config file and presents it into groups (menus). Since they are created from the generated file, it is only possible to use the GUI once an application is built.
The kernel options are sourced when Zephyr RTOS is built, which is triggered as one of the early stages of building an nRF Connect SDK application. The default out-of-the-box kernel options are located in <nRF Connect SDK Installation Path>\zephyr\kernel\Kconfig.
9.1. Examine the options at the top of the menu.
Multithreading (CONFIG_MULTITHREADING) is enabled by default in all nRF Connect SDK applications. This is because all the libraries and modules in the nRF Connect SDK rely on the multithreading features offered by the Zephyr RTOS. You will not be able to use any of the connectivity options (such as Bluetooth Low Energy, Wi-Fi, or cellular) if you disable this option. This option is enabled by default in <nRF Connect SDK Installation Path>\zephyr\kernel\Kconfig.
In the nRF Kconfig GUI, you can always click on the information symbol next to each entry to learn more details about that Kconfig configuration. Other options that exist on the top of the General Kernel Options:
The number of priority levels in the application, both preemptible and cooperative.
The stack size of the main thread (CONFIG_MAIN_STACK_SIZE). This is an important parameter, especially if your main thread is going to perform a lot of tasks.
The stack size used by interrupt service routines (ISRs).
The stack size of the idle thread.
9.2. Scroll down for more options.
Find the Scheduler priority queue algorithm and Wait queue priority algorithm menus.
In the Scheduler priority queue algorithm menu, you can control the internal implementation of the ready queue. There are three options that affect factors such as code size, constant factor runtime overhead and performance scaling when many threads are added. The default option selected (Simple linked-list ready queue) is ideal for systems with few runnable threads at any given time. It has fast, constant time performance and very low code size. You can find more details on the other options in the scheduling documentation for Zephyr. It’s recommended you don’t change this option.
In the wait queue priority algorithm menu, you can control the internal implementation of wait_q. It is a core internal data structure used by the kernel intensively to allow IPC primitives (such as the message queue and FIFOs) to pend threads for later wakeup based on data availability. Two options are available. The default one (Simple linked-list wait_q) is ideal for when you only have a few threads blocked on any single IPC primitive. You can find more details on the other options in the scheduling documentation for Zephyr. It’s recommended you don’t change this option.
9.3. Scroll down for more options.
Find the Kernel Debugging and Metrics and the Work Queue Options menus.
The first Kconfig option in the Kernel Debugging and Metrics menu is the Initialize stack areas (CONFIG_INIT_STACKS) option. This option instructs the kernel to initialize stack areas with a known value (0xaa) before they are first used, so that the high water mark can be easily determined using the Memory Viewer during a debugging session. This is similar to the functionality available in the nRF Debug, except here it’s done manually. This means that the developer needs to inspect the memory locations manually. This applies to the stack areas of both threads, as well as to the interrupt stack.
The boot banner option (corresponding to CONFIG_BOOT_BANNER), which is enabled by default, prints the boot banner in the terminal on boot-up:
*** Booting nRF Connect SDK v2.x.x ***
The boot delay option (corresponding to CONFIG_BOOT_DELAY) delays boot-up for the specified amount of milliseconds. This delay is introduced between the POST_KERNEL and APPLICATION levels discussed in the Boot-up Sequence & Execution context topic.
The thread monitoring option (corresponding to CONFIG_THREAD_MONITOR) instructs the kernel to maintain a list of all threads (excluding those that have not yet started or have already terminated). This is needed for nRF Debug to visualize the threads in the Ready, Unready, and Running states.
The thread name option (corresponding to CONFIG_THREAD_NAME) allows you to set a name for a thread.
Note that both the thread monitoring and thread name optionsare selected in the sample application, in the prj.conf file. This is done with the CONFIG_DEBUG_THREAD_INFO option, which selects both when it is enabled.
The Thread runtime statistic option (corresponding to CONFIG_THREAD_RUNTIME_STATS) is used to gather thread runtime statistics. This can be used in situations where you want your firmware to be able to send this information to, for example, a shell, a Bluetooth LE connection, a wifi connection or cellular. This option is not used in the exercise. Instead, you will use nRF Debug.
In the Work Queue Options menu, you can control the behavior of the System Work Queue. The System Work Queue is a cooperative thread that goes over the submitted work items one by one.
The stack size option (corresponding to SYSTEM_WORKQUEUE_STACK_SIZE) controls the stack size of the System Work Queue. Setting this value based on your system work queue usage is very important.
The System Work Queue goes over the submitted work individually. The priority option (corresponding to CONFIG_SYSTEM_WORKQUEUE_PRIORITY) can be used to select which priority the System Work Queue runs in.
The yielding option (corresponding to CONFIG_SYSTEM_WORKQUEUE_NO_YIELD) allows you to disable yielding. By default, the System Work Queue yields between each work item to prevent other threads from being starved. Selecting this option removes this yield. This means that you should only enable this option if there is a strong need for a sequence of work items to complete without yielding.
The Atomic operations is managed by the architecture port.
9.3. Scroll down for more options.
Find the Timer API Options and Other Kernel Object Options menus.
The Timer API Options menu controls timer settings.
The thread time slicing option (corresponding to CONFIG_TIMESLICING), and the options under it are related to time slicing, which is covered in depth in Lesson 7 – Exercise 2 of the nRF Connect SDK Fundamentals Course.
The Async I/O Framework option (corresponding to CONFIG_POOL) enables the Polling API. The polling API are the k_poll() and k_poll_signal_raise() APIs. The former can wait on multiple events concurrently, which can be either directly triggered or triggered by the availability of some kernel objects (semaphores and FIFOs). An example that uses the Polling API is the Bluetooth NFC pairing sample.
In the Other Kernel Object Options menu, you can configure and enable different kernel objects, such as the Events objects (CONFIG_EVENTS), which is used to pass small amounts of data to multiple threads at once and also to indicate that a set of conditions have occurred. An example that uses the Events objects is the nRF Cloud multi-service sample.
The heap allocation configuration option (CONFIG_HEAP_MEM_POOL_SIZE) is also grouped under the Other Kernel Object Options.
9.4. Scroll down for more options.
At the end of the General Kernel Options, you can find the menus related to key Kconfig options to the kernel.
The System tick frequency (in ticks/second) (CONFIG_SYS_CLOCK_TICKS_PER_SEC) and the System clock’s h/w timer frequency (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC) are obtained from the default value for the architecture port (SoC level) as shown in the screenshot below.
The Security menu has the Compiler stack canaries (CONFIG_STACK_CANARIES). This option enables compiler stack canaries, if it is supported by the compiler used. When enabled, it will emit extra code that inserts a canary value into the stack frame when a function is entered and validates this value upon exit. Stack corruption (such as that caused by buffer overflow) results in a fatal error condition for the running entity (for example, a thread will be terminated). Enabling this option can result in a significant increase in footprint and an associated decrease in performance. Therefore, it’s only recommended during the development and debugging phase.
At the bottom is the tickless kernel option (CONFIG_TICKLESS_KERNEL), which is enabled by default in all nRF Connect SDK applications as we discussed previously.
Nordic Developer Academy Privacy Policy
1. Introduction
In this Privacy Policy you will find information on Nordic Semiconductor ASA (“Nordic Semiconductor”) processes your personal data when you use the Nordic Developer Academy.
References to “we” and “us” in this document refers to Nordic Semiconductor.
2. Our processing of personal data when you use the Nordic Developer Academy
2.1 Nordic Developer Academy
Nordic Semiconductor processes personal data in order to provide you with the features and functionality of the Nordic Developer Academy. Creating a user account is optional, but required if you want to track you progress and view your completed courses and obtained certificates. If you choose to create a user account, we will process the following categories of personal data:
Email
Name
Password (encrypted)
Course progression (e.g. which course you have completely or partly completed)
Certificate information, which consists of name of completed course and the validity of the certificate
Course results
During your use of the Nordic Developer Academy, you may also be asked if you want to provide feedback. If you choose to respond to any such surveys, we will also process the personal data in your responses in that survey.
The legal basis for this processing is GDPR article 6 (1) b. The processing is necessary for Nordic Semiconductor to provide the Nordic Developer Academy under the Terms of Service.
2.2 Analytics
If you consent to analytics, Nordic Semiconductor will use Google Analytics to obtain statistics about how the Nordic Developer Academy is used. This includes collecting information on for example what pages are viewed, the duration of the visit, the way in which the pages are maneuvered, what links are clicked, technical information about your equipment. The information is used to learn how Nordic Developer Academy is used and how the user experience can be further developed.
2.2 Newsletter
You can consent to receive newsletters from Nordic from within the Nordic Developer Academy. How your personal data is processed when you sign up for our newsletters is described in the Nordic Semiconductor Privacy Policy.
3. Retention period
We will store your personal data for as long you use the Nordic Developer Academy. If our systems register that you have not used your account for 36 months, your account will be deleted.
4. Additional information
Additional information on how we process personal data can be found in the Nordic Semiconductor Privacy Policy and Cookie Policy.
Nordic Developer Academy Terms of Service
1. Introduction
These terms and conditions (“Terms of Use”) apply to the use of the Nordic Developer Academy, provided by Nordic Semiconductor ASA, org. nr. 966 011 726, a public limited liability company registered in Norway (“Nordic Semiconductor”).
Nordic Developer Academy allows the user to take technical courses related to Nordic Semiconductor products, software and services, and obtain a certificate certifying completion of these courses. By completing the registration process for the Nordic Developer Academy, you are agreeing to be bound by these Terms of Use.
These Terms of Use are applicable as long as you have a user account giving you access to Nordic Developer Academy.
2. Access to and use of Nordic Developer Academy
Upon acceptance of these Terms of Use you are granted a non-exclusive right of access to, and use of Nordic Developer Academy, as it is provided to you at any time. Nordic Semiconductor provides Nordic Developer Academy to you free of charge, subject to the provisions of these Terms of Use and the Nordic Developer Academy Privacy Policy.
To access select features of Nordic Developer Academy, you need to create a user account. You are solely responsible for the security associated with your user account, including always keeping your login details safe.
You will able to receive an electronic certificate from Nordic Developer Academy upon completion of courses. By issuing you such a certificate, Nordic Semiconductor certifies that you have completed the applicable course, but does not provide any further warrants or endorsements for any particular skills or professional qualifications.
Nordic Semiconductor will continuously develop Nordic Developer Academy with new features and functionality, but reserves the right to remove or alter any existing functions without notice.
3. Acceptable use
You undertake that you will use Nordic Developer Academy in accordance with applicable law and regulations, and in accordance with these Terms of Use. You must not modify, adapt, or hack Nordic Developer Academy or modify another website so as to falsely imply that it is associated with Nordic Developer Academy, Nordic Semiconductor, or any other Nordic Semiconductor product, software or service.
You agree not to reproduce, duplicate, copy, sell, resell or in any other way exploit any portion of Nordic Developer Academy, use of Nordic Developer Academy, or access to Nordic Developer Academy without the express written permission by Nordic Semiconductor. You must not upload, post, host, or transmit unsolicited email, SMS, or \”spam\” messages.
You are responsible for ensuring that the information you post and the content you share does not;
contain false, misleading or otherwise erroneous information
infringe someone else’s copyrights or other intellectual property rights
contain sensitive personal data or
contain information that might be received as offensive or insulting.
Such information may be removed without prior notice.
Nordic Semiconductor reserves the right to at any time determine whether a use of Nordic Developer Academy is in violation of its requirements for acceptable use.
Violation of the at any time applicable requirements for acceptable use may result in termination of your account. We will take reasonable steps to notify you and state the reason for termination in such cases.
4. Routines for planned maintenance
Certain types of maintenance may imply a stop or reduction in availability of Nordic Developer Academy. Nordic Semiconductor does not warrant any level of service availability but will provide its best effort to limit the impact of any planned maintenance on the availability of Nordic Developer Academy.
5. Intellectual property rights
Nordic Semiconductor retains all rights to all elements of Nordic Developer Academy. This includes, but is not limited to, the concept, design, trademarks, know-how, trade secrets, copyrights and all other intellectual property rights.
Nordic Semiconductor receives all rights to all content uploaded or created in Nordic Developer Academy. You do not receive any license or usage rights to Nordic Developer Academy beyond what is explicitly stated in this Agreement.
6. Liability and damages
Nothing within these Terms of Use is intended to limit your statutory data privacy rights as a data subject, as described in the Nordic Developer Academy Privacy Policy. You acknowledge that errors might occur from time to time and waive any right to claim for compensation as a result of errors in Nordic Developer Academy. When an error occurs, you shall notify Nordic Semiconductor of the error and provide a description of the error situation.
You agree to indemnify Nordic Semiconductor for any loss, including indirect loss, arising out of or in connection with your use of Nordic Developer Academy or violations of these Terms of Use. Nordic Semiconductor shall not be held liable for, and does not warrant that (i) Nordic Developer Academy will meet your specific requirements, (ii) Nordic Developer Academy will be uninterrupted, timely, secure, or error-free, (iii) the results that may be obtained from the use of Nordic Developer Academy will be accurate or reliable, (iv) the quality of any products, services, information, or other material purchased or obtained by you through Nordic Developer Academy will meet your expectations, or that (v) any errors in Nordic Developer Academy will be corrected.
You accept that this is a service provided to you without any payment and hence you accept that Nordic Semiconductor will not be held responsible, or liable, for any breaches of these Terms of Use or any loss connected to your use of Nordic Developer Academy. Unless otherwise follows from mandatory law, Nordic Semiconductor will not accept any such responsibility or liability.
7. Change of terms
Nordic Semiconductor may update and change the Terms of Use from time to time. Nordic Semiconductor will seek to notify you about significant changes before such changes come into force and give you a possibility to evaluate the effects of proposed changes. Continued use of Nordic Developer Academy after any such changes shall constitute your acceptance of such changes. You can review the current version of the Terms of Use at any time at https://academy.nordicsemi.com/terms-of-service/
8. Transfer of rights
Nordic Semiconductor is entitled to transfer its rights and obligation pursuant to these Terms of Use to a third party as part of a merger or acquisition process, or as a result of other organizational changes.
9. Third Party Services
To the extent Nordic Developer Academy facilitates access to services provided by a third party, you agree to comply with the terms governing such third party services. Nordic Semiconductor shall not be held liable for any errors, omissions, inaccuracies, etc. related to such third party services.
10. Dispute resolution
The Terms of Use and any other legally binding agreement between yourself and Nordic Semiconductor shall be subject to Norwegian law and Norwegian courts’ exclusive jurisdiction.