Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

Boot-up sequence & execution context

For implementing tasks for your application code, you need to pick the proper execution primitive (pre-emptible thread, cooperative thread, work queue, etc.) and set its priority correctly to not block other tasks that are running on the CPU while you also meet the time requirements of the task.

One of the main goals of this lesson is to learn how to schedule application tasks using the right execution primitive with the right priority level. But before we can dive into the topic of choosing the right execution method to run a given task, we need to consider the following questions:

  • How does an nRF Connect SDK application boot up?
  • What are the out-of-the-box threads and ISR in an application, and what is their priority and execution nature?
  • What is the difference between interrupt and thread contexts, and what to do in each?

Boot-up sequence

1. Early Boot Sequence (C Code Preparation phase)

The primary function of the early boot sequence is to transition the system from its reset state to a state where it becomes capable of executing C code, thereby initiating the kernel initialization sequence. This stage is a pretty standard phase in embedded devices; as an application developer, it is not of much interest.

2. Kernel Initialization

This stage presents a process of initializing the state for all enabled static devices. Some devices are enabled out-of-the-box by the RTOS, while others are enabled by your application configuration file (prj.conf) and your board configuration files as we learned in the nRF Connect SDK Fundamentals course. These latter devices encompass device driver and system driver objects that are defined using Zephyr APIs in a static manner.

The initialization order is controlled by assigning them to specific run levels (for example, PRE_KERNEL_1 , PRE_KERNEL_2), and their initialization sequence within the same run level is further defined using priority levels. Keep in mind that at this stage, the scheduler and the Kernel services are not yet available, so these initialization functions at this stage do not rely on any Kernel services. We will dive into driver initialization in Lesson 7.

2.1. What gets initialized in PRE_KERNEL_1 by default in all nRF Connect SDK applications

  • Clock Control driver: This enables support for the hardware clock controller. The hardware can provide clock for other subsystem, and thus can be also used for power efficiency by controlling their clock.
  • A serial driver: This can be UART(E), RTT, or other transports. It is used to send the debugging output, such as the boot-up banner. This gets initialized only if the debugging option is enabled.

2.2. What gets initialized in PRE_KERNEL_2 by default in all nRF Connect SDK applications

  • System Timer driver: This is usually a Real-time counter peripheral (RTC1) on Nordic SoCs and SiPs (nRF91, nRF53, nRF52 Series). The System Timer will be used for the timing services of the kernel, such as k_sleep() and the kernel timer API.

Important

The list of devices does not include all devices that get initialized in your application. The list only provides the minimal core devices needed by the RTOS. The devices and subsystems initialized will depend on your application configuration and your board configuration file. In the next lesson, we will dive more into how to find out all configured devices and subsystems.

3. Multithreading Preparation

This is where the multithreading features get initialized, including the scheduler. The RTOS will also create two threads (System threads): The RTOS main thread, and the idle thread that is responsible for calling the power management system of the SiP and SoC if no other threads are ready.

During this phase, the POST_KERNEL services are initiated, if any exist. Once the POST_KERNEL services are initiated, the Zephyr boot banner is printed:

*** Booting nRF Connect SDK v2.x.x ***

After that, the APPLICATION level services are initialized, if any exist. Then, all application-defined static threads (using K_THREAD_DEFINE()) are initiated.

3.1. What gets initialized in POST_KERNEL by default in all nRF Connect SDK applications

This is where many libraries, RTOS subsystems, and services get initialized. These libraries require kernel services during configuration so this is why they are initiated in the POST_KERNEL level, where the kernel services are available.

By default, the RTOS does not initialize anything here. However, there are many libraries, RTOS subsystems, and services that get initialized here if they are enabled. For instance, if logging ( CONFIG_LOG )is enabled with deferred mode, this is where the logger module gets initiated, and the logger-dedicated thread (for deferred mode) gets created. Also, if Bluetooth Low Energy is used (CONFIG_BT), this is the location where the Bluetooth stack gets initialized, and the RX and TX threads get created. Similarly, if the system work queue is used, this is the location where the system work queue thread is initialized.

3.2. What gets initialized in APPLICATION by default in all nRF Connect SDK applications

By default, some libraries get initiated here if they are enabled. For instance, if you are developing on the nRF91 Series and you are using the AT Monitor Library (AT_MONITOR), this is the location where this library gets initialized.

The RTOS main thread is the currently active thread. After it’s done with all initializations, it will call your main() function at the end, if it exists. If no user-defined main() exists, the RTOS main thread will terminate, and the scheduler will pick the next ready thread for execution. This could be a user-defined thread, a subsystem thread, or the idle thread if there are no ready threads. What decides which thread will execute is the type and priority of the threads. This is something we will cover in the next topics.

Zephyr , nRF Connect SDK boot up sequence

Note

For multi-core hardware such as the nRF5340 SoC, other peripherals, such as the mailbox (mbox), will be initialized on bootup by the SDK. When Trusted Firmware-M is used, an entropy source such as the psa-rng peripheral will be initialized as well.

After boot-up, there will be several threads and interrupts set up. In the next section, we will compare the context where interrupts run in, called interrupt context, and the context where threads run in, called thread context.

Thread context vs interrupt context

Interrupt and thread contexts refer to different execution contexts with distinct characteristics and intended usages. Let’s explore each context and the allowed and not allowed operations within them:

Thread context

Execution Context: Thread context refers to the normal execution environment where application and system threads run.
Triggering Event: Threads are created by the application or the RTOS and scheduled by the scheduler using defined rules (type and priority).
Preemption: Thread context can be preempted by an interrupt or higher-priority threads. You can find more details on this in the Scheduler in-depth topic.
Duration: Threads can execute longer and perform more complex operations than interrupt context.

Allowed Operations:

  • Access to the full range of kernel services and OS services.
  • Executing time-consuming operations.
  • Waiting on synchronization primitives like mutexes, semaphores, or event flags.
  • Performing blocking I/O operations.

Not Allowed Operations:

  • Accessing hardware registers directly without proper synchronization or abstraction.
  • Running time-critical operations.

Interrupt context

Execution Context: Interrupt context refers to the execution environment when an interrupt handler is running.
Triggering Event: Interrupts can happen completely asynchronously at any time and are triggered by hardware events, such as timers, external signals, or device I/O.
Preemption: Interrupt context preempts the currently running thread context.
Duration: Interrupt handlers are expected to execute quickly to minimize the delay from servicing the interrupt and to not block the execution of system threads and communication stacks threads.
Interrupts Nesting: Zephyr allows nested interrupts, meaning an interrupt handler can be interrupted by another interrupt of higher priority.

Allowed Operations:

  • Executing time-critical operations.
  • Access to a restricted set of kernel services.

Not Allowed Operations:

  • Blocking operations.
  • Using most of the kernel services meant for thread context (for example, sleeping or waiting on synchronization primitives, acquiring a mutex or a semaphore that is potentially blocking).
Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.