A Nordic DevAcadamy Course on the nRFConnect SDK
This blog contains my course notes for an excellent course given on the Nordic website on the DevAcadamy site. I thought to create this blog to take notes on the course that I used to get familiar with the nRF9160DK. I used the course to get my development environment running and to experiment using the SDK. I have a roadtest, Nordic nRF9160 DK -- A single board development kit for evaluation and development on the nRF9160 for LTE-M , on the NnRF9160 Development Kit. This course is using the Nordic nRF52833 DK, but any of the following development kits: nRF5340 DK, nRF52840 DK, nRF52833 DK, nRF52 DK, nRF9160 DK or Thingy:91.can be used
I was able to get up and programming with the environment, with little problems, and after about a week I completed the course.
I was able to install the nRF SDK, nRF Connect VS code extensions and work with many firmware examples by taking the nRF Connect SDK Fundamentals course
nRF Connect SDK Fundamentals is a self-paced hands-on online course focusing on learning the essentials of firmware development using the highly extensible and feature-rich nRF Connect SDK.
The nRF Connect Software Development Kit contains a highly configurable real-time operating system called the Zephyr RTOS and a wide range of samples, application protocols, protocol stacks, libraries, and hardware drivers.
By the end of this course, you will have a solid understanding of nRF Connect SDK, its structure and content. You will learn how to develop portable RTOS-based applications that can run on any of Nordic Semiconductor devices (nRF52, nRF53, or nRF91 Series).
In addition to that, you will gain substantial practical experience in interfacing with common peripherals/system blocks and external sensors, which will give you the confidence to apply the knowledge and the know-how to your project.
What You'll Learn
- Develop a fundamental understanding of the nRF Connect SDK
- Learn how hardware is described using devicetree
- Learn how to configure software modules using Kconfig
- Practice through hands-on exercises using common hardware peripherals (GPIO, UART, I2C)
- Develop more knowledge about Zephyr RTOS 101 - Execution model, ISRs, threads, thread's life-cycle and inter-task communication/synchronization mechanisms
Course Outline and Links
I included the course outline here with links to the lessons.
Lesson 1 – nRF Connect SDK Introduction
nRF Connect SDK structure and content
Exercise 1 -Installing nRF Connect SDK and VS Code
Exercise 2 - Build and flash your first nRF Connect SDK application
Lesson 2 – Reading buttons and controlling LEDs
Exercise 1 - Controlling an LED through a button (polling based)
Exercise 2 - Controlling an LED through a button (interrupt based)-
Lesson 3 – Elements of an nRF Connect SDK application
Devicetree overlays, CMake, and multi-image builds
Exercise 1-Creating an application
Exercise 2 - Customizing the application
Lesson 4 – Printing messages to console and logging
Exercise 1 -Printing to the console
Exercise 2 - Using the logger module
Exercise 3 - Exploring the logger module feature
Lesson 5 – Serial communication (UART)
Exercise 1 - Controlling LEDs through UART
Lesson 6 – Serial communication (I2C)
Exercise 1 - Connecting an STTS751 temperature sensor
Exercise 2 - Connecting to the BH1749 Ambient Light Sensor on the Thingy:91 and Thingy:53
Lesson 7 – Multithreaded applications
Bare-metal vs RTOS programming
Exercise 1 - Thread creation and priorities
Exercise 3 - Workqueue creation and work item submission
Lesson 8 – Thread synchronization
My Notes
This section contains notes and important concepts that I picked up while taking the course. I used it as a study guide to review sections of the course that I would be able to reference when using the nRF9160 DK SDK.
Lesson 1 – nRF Connect SDK Introduction
This lesson introduces the SDK and instructs you how to Install the SDK and VS Code for firmware development. It contains an overview of the SDK, and 2 exercises that gets a firmware development environment setup and running and describes how to run your first application BLINKY
nRF Connect SDK structure and content
Examines the SDK extensible framework for building firmware. It describes the four main repositories of the SDK. And it describes the toolchain of tools used in the creation of an application.
Exercise 1
- I
- Downloaded the above version.
- install it by clicking on it.
- Installs J-link for 64 bit
- Note ( For the firm ware update I installed for 32 bit version of the SDK?
- COMPLETED the install
- Install nRF Command Line Tools.
- Download link: https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools
What version is my windows OS?
Edition Windows 10 Pro
Version 21H1
Installed on 11/7/2020
OS build 19043.1826
Installed 10..17.0 windows x86 64 bit
- Download link: https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools
- Download nRF for desktop
- install nRF connect for desktop
- Note ALREADY HAVE THIS INSTALLED SKIPED THIS
- install and open Toolchain manager in nRF Connect for DESKTOP
- Configure the installation directory for the nRF Connect SDK.
- this is the directory where the SDK will be downloaded and installed.
- As recommended create C:\nordicsemi
- But it appears that the c:\ncs directory has install the SDK OK?
- Install nRF Connect SDK.
- Wanted to install v1.7.0 but only v.1.8 was available
- SO I selected the latest version 2.0.0
- Set up nRF Connect for VS Code.
Now that we have the SDK installed, the next step is to set up an integrated development environment (IDE) to simplify the application development process. We will use the nRF Connect for VS Code. Full documentation of the nRF Connect for VS Code is available
The nRF Connect for VS Code is an extension pack that allows developers to use the popular Visual Studio Code Integrated Development Environment (VS Code IDE) to develop, build, test, and deploy embedded applications based on Nordic’s nRF Connect SDK (Software Development Kit). It includes an interface to the compiler and linker, an RTOS-aware debugger, a seamless interface to the nRF Connect SDK, and an integrated serial terminal among other valuable development tools.
7.1 Install VS Code.
Go to https://code.visualstudio.com/download and install the version that matches your operating system. ALREADY HAVE IT. SKIPPED THIS
7.2 Open VS Code.
Since I already have VSCODE I installed the dependencies right from TOOLCHAIN MANAGER. But this did not work for SDK 2.0.0
But since I had not install the cmd line tools before starting the installation. All the dependencies did not get installed. So I skiped that step and manually started VSCODE?
But section 7.2 specifically says to Open VS code
Head to Extensions, then type Nordic in the search field and select nRF Connect Extension pack and click on Install as shown in the illustration below:
I got an error and never completed.
SO I disabled all my VSCode Extensions and rebooted a few times then went in VS Code days later
Installed v2022.7.242 of the extensions
The bundle consists of:
In addition to Cortex-Debug, GNU Linker Map files, and CMake, the bundle consists of the following components:
- nRF Connect for VS Code,the main extension, which contains an interface to the build system and nRF Connect SDK, as well as external tools such as guiconfig.
- DeviceTree for the ZephyrProject, which provides DeviceTree language support.
- Kconfig for the Zephyr Project,which provides Kconfig language support.
- nRF Terminal, a serial and RTT terminal.
- C/C++from Microsoft which adds language support for C/C++ including features such as IntelliSense.
7.3 Open nRF Connect for VS Code.
A slight change from the instruction in this section. I did the following steps for the version I installed of the nRFExtensions.
Here are the steps I took
- Open the extension by clicking the icon (1).
- Click on the menu item “Open Welcome Page”
- Select the nRF Connect installed from the Dropdown box
- The correct nRF Connect toolchain appeared in the text box
NOTE:
- nRF Connect SDK is IDE agnostic, which means you can use it with the IDE of your choice and you can also use it without an IDE at all.
- However, we highly recommend using VS Code with our nRF Connect for VS Code extension pack as it contains both a convenient graphical user interface (GUI) and an efficient command-line interface (CLI) in one place, in addition to many features that make firmware development much easier.
- Setting up another IDE to work with nRF Connect SDK will require some extra manual steps that are out of the scope of this course
Exercise 2
Build and flash your first nRF Connect SDK application
- In this exercise, we will program a simple application based on the blinky template to
- toggle an LED on our board.
- Again, any Nordic Semiconductor development board will work (nRF52, nRF53, nRF91 Series).
- The idea is to make sure that all the tools needed to build and flash a sample are set up correctly.
- The focus is to learn how to
- Create an application from a template,
- build the application,
- and flash it on a Nordic-powered board.
- Basically, we will walk you through the three short videos below to:
- Create a new application based on a template.
- Build an application.
- Flash an application to a board.
- Either watch the videos or follow the steps below.
Exercise Steps:
- Create a folder close to your root directly that will hold all the exercises that we will be working on throughout this course.
- As recommended, I created in C:\nordic\myapps.
- In VS Code. Click on the nRF Connect Extension. In the Welcome window (panel), click on Create a new application
In the New application window, do the following:
- Select a Freestanding This will link and use an already installed nRF Connect SDK.
- The other type of application is the Workspace application, which will have a dedicated SDK instance for each application. Workspace applications are out of the scope of this course.
- Select the version of nRF Connect SDK to compile and build your application with. The nRF Connect toolchain should match the version of the SDK.
- The list contains versions already installed on your machine. If you want to install a version that is not already present on your machine, press on the install button. This invokes the Toolchain Manager, where you can download different versions of the SDK.
- Select the directory to store your application folder, nordic\myapps.
- Chose the template to base your application on. This will make a copy of the template and store it in the directory you specified in step 3.3 and name it as you will specify in step 3.5.
- We will base our first application on the blinky sample. Type blinky in the application template box and select the one that says zephyr\samples\basic\blinky . This is simply the relative path directly within the nRF Connect SDK where the template is stored.
- WRONG PICTURE and Instructions at the end of this section?
- The Select.. Button in the Application template line
- CORRECT
- Instead select BROWSE to open the following window.
- Note
- The SDK contains a rich set of templates. In the context of templates, Samples are simple and showcase a single feature or library (e.g. SHA256 sample, UART sample, PWM sample, etc.), while Applications are complex and include a variety of libraries to implement a specific use case (e.g. Asset tracker application, Keyboard application, Mouse application
- Name your application fund_less1_exer2as this is the naming convention that we will use for exercises throughout this course. Note that this will create a folder for your application called fund_less1_exer2.Finally, click on the Create Application button
- This will add an unbuilt application to VS Code as shown below:
- Add a build configuration.
- In this step, you will specify which development board you want to build the application for. We will also select the software configuration ( *.conf) to be used in the build.
- One of the many advantages of nRF Connect SDK is the high decoupling between an application source code and the software configuration/hardware description, making it extremely easy to switch the build for a new hardware or software configuration.
- Click on the small Add Build Configurationicon next to the application name.
- this will open the Add Build Configuration window shown below:
- Select the board that is recommened on the page.
- I chose nRF9160 DK nrf9160dk_nrf9160_ns
- I set this value to “default”
- The Page states “Depending on the template you chose in step 3, you will be presented with at least one application configuration prj.conf. Some templates contain more than one application configuration file (eg: prj.conf, prj_minimal.conf, prj_cdc.conf). These different configurations, if found in the template, are explained in the template documentation. The blinky template has only one option.”
- The Kconfig Fragments field will list the Kconfig overlays (aka Kconfig Fragments) that are available in the template or have been added in the application folder. These are modifiers to the application configuration file (covered in Lesson 3). The blinky template has none.
- The Extra CMake arguments field allows you to pass arguments to the build system if needed. We will leave that blank for this demo.
- The Build directory name field gives you the option to manually name the build directory where the final binary files and temporary build files will be stored. We will leave this to be the default name specified by the tool, which is build or build_1 if you have already built one application before.
- Leave the Build after generating configuration option selected to trigger the build process after clicking on the Build Configuration button.
- Select the Enable debug options to enable debugging of the application.
- Click on the Build Configuration button to create the configuration and initiate the build process. The build process will take some time to finish. Open the Terminal (View->Terminal) to see the progress of the build. A successful build is indicated by displaying the memory usage of the application as shown in the screenshot above.
- Flash to the device
- Make sure that your development kit is connected to your computer and that it is switched on
- . It should be listed in the Connected Devices window in the nRF Connect for VS Code extension.
- I Had to press the REFRESH ICON as described on the page in section 5
- If you don’t see your board listed, press on the Refresh connected device icon in the connected devices window.
- From the Actions window, press on the Flash to flash the application to the board. You can open the terminal view to see the progress of the flashing.
Note The difference between “Flash” and “Erase and flash” is that the latter erases the whole device, including all data the application has saved.For example, if the device is added to a mesh network, “Erase and flash” will make the device forget this.
LED1 on your board should be blinking now in 1-second intervals.
- Just for the sake of this demonstration,
7.1 change the value of the SLEEP_TIME_MS (line 11 in main.c, which you can locate in Source Files->Applications or through the Explorer panel of Visual Studio Code) which controls the interval at which the LED is blinking. Change the macro from 1000 to 100.
- Rebuild and re-flash the application on your board. You should observe that the LED is now blinking at a higher frequency.
NEXT In Lesson 2, we will take an in-depth look at the source code of this application to understand how it works.
Lesson 2 – Reading buttons and controlling LEDs
This lesson basically covers, how hardware is described in the SDK. The interaction between applications and hardware is explained. This is done through peices of software called Device Drivers. The device model used in the SDK is examined. To accomplish this, the GPIO hardware peripheral and driver example is used In the exercise sections. The GPIO peripheral to control the LED’s and read the status of buttons using both polling and interrupts methods are described. The BLINKY example is used to examine these concepts.
2.2. Devicetree
- This section, gives a basic overview of how hardware is described and presented using the devicetree.
- It describes
- The treelike structure
- It describes
/dts-v1/;
/ {
a-node {
subnode_label: a-sub-node {
foo = <3>;
};
};
};
-
-
- Aliases
- Accessing the Devicetree
- A devicetree example for a DK
- The DK Devicetree file
- The file for the nRF52833 DK.is describe located in
- <install_path>\zephyr\boards\arm\nrf52833dk_nrf52833\nrf52833dk_nrf52833.dts.
- I COULD NOT FIND it or the nRF9160 DK for an example?
-
2.3. Device Driver Model
- This Section describes how to interact with a hardware peripheral or a system block. A device driver is used to interact with the LOW-LEVEL Details of configuring the hardware the way we want. The Driver is a piece of software, which is highly DECOUPLED from its API. This gives the ability to “switch out” the low-level driver implementation without modifications to the application.
- The application interacts with the hardware through the GENRIC API, which is described in the next section. This is accomplished by obtaining a device pointer for the hardware in question. This pointer references an object (struct) that is used to interact with the hardware device.
- The device pointer is obtained by using the macro DEVICE_DT_GET() or related macros.
- Note …It is also possible to get the device pointer through device_get_binding(), though this is no longer recommended.
- The Zephyr device model is responsible for the association between generic APIs and device driver implementations and is described in this section using the UART API diagramed.
- And finally the macro used to get the device pointer and several helper macro‘s are described.
- DEVICE_DT_GET()
- Returns a pointer to a device object from a devicetree node
- DT_NODELABEL() or DT_ALIAS()
- Used to get the device tree node identifier to be used in the DEVICE_DT_GET call.
- device_is_ready()
- This will check if the device is ready for use, for instance, if it is properly initialized.
- The following code snippet describes getting a device pointer for a UART.
- const struct device *dev;
- dev = DEVICE_DT_GET(DT_NODELABEL(uart0));
- if (!device_is_ready(dev)) {
- return;
- DEVICE_DT_GET()
2.4. GPIO Generic API
This section describes how to interact with the GPIO peripheral using the API <drivers/gpio.h>. This API provides user friendly functions to interact with external GPIO components, such as switches, buttons and LED’s
When using a driver , the 1st step is to INITIALZE For a GPIO pin, the next step is to configure the pin to be either an INPUT or an OUTPUT. Then the next 2 steps are to READ from the input pin and then WRITE to the output pin. These 4 steps are described in the next 4 sections.
2.4.1. Initializing the API
- Some of the API’s have API-SPECIFIC structs (objects). This is the case for the GPIO device. The struct contains the device pointer as well as other information about the device
- This section describes the use of the struct gpio_dt_spec and the function GPIO_DT_SPEC_GET(node_id,prop) that retrieves this device structure.
- Similar to the DEVICE_DT_GET () function, GPIO_DT_SPEC_GET() take the node_id. But also takes the property name of the node. The function will return a struct, containing the device pointer and the pin number and the configuration flag.
- The advantage is that it gets all the information needed to use the device in a single variable, instead of having to extract it from the devicetree line by line.
2.4.2. Configure a single pin
- To do this you call the gpio_pin_configure_dt()
- This function will be used to configure a single pin and some extra flags.
- This function is describe in this section
- With this function you can configure a pin to be INPUT or OUTPUT.
- You can also specify other hardware characteristics to a pin like
- the drive strength,
- pull up/pull down resistors,
- Active high or active low.
- Different hardware characteristics can be combined through the | operator.
- The following line configures the pin led.pin as an output that is active low.
- gpio_pin_configure_dt(&led, GPIO_OUTPUT | GPIO_ACTIVE_LOW);
2.4.3. Write to an output pin
- Writing to an output pin uses the function gpio_pin_set_dt()
- For example, the following line sets the pin associated with gpio_dt_spec led, which can be denoted as led.pin, to logic 1 “active state”:
- gpio_pin_set_dt(&led, 1);
- You can also use the function gpio_pin_toggle_dt(), to toggle an output pin.
- For example, the following line will toggle the pin led.pin, whenever this API is called.
- gpio_pin_toggle_dt(&led);
- For example, the following line will toggle the pin led.pin, whenever this API is called.
- For example, the following line sets the pin associated with gpio_dt_spec led, which can be denoted as led.pin, to logic 1 “active state”:
2.4.4. Read from an input pin
- Basically there are two possible methods to read the status of a input pin. They are described in the next two sections. Reading a pin is not as straightforward as writing to a output pin
2.4.4.1. Polling method
- Polling means “continuously reading the status of the pin to check if it has changed”.
- The function gpio_pin_get_dt() is used to read the current status of a pin.
- For example, the following line reads the current status of led.pin saves it in a variable called val.
- val = gpio_pin_get_dt(&led);
- The drawback of polling, is that you have to repeatedly call the gpio_pin_get_dt() to keep track of the status of a pin.
- For example, the following line reads the current status of led.pin saves it in a variable called val.
2.4.4.2. Interrupt method
- The hardware will notify the CPU once there is a change in the status of the pin.
- This is the recommended way to READ an input pin
- It FREES the CPU from repeatedly having to check the status of the pin. The CPU can sleep and only wake up when there is a chage..
- The following are the steps needed to setup an interrupt on a GPIO pin:
- Configure the interrupt on a pin.
- This is done by calling the function gpio_pin_interrupt_configure_dt()
- The following line will configure an interrupt on button.pin on the change to logical level 1.
- gpio_pin_interrupt_configure_dt(&button,GPIO_INT_EDGE_TO_ACTIVE);
- Define the callback function pin_isr().
- The callback function defined is called when an interrupt is triggered.
- The prototype for pin_isr() needs to be defiened as follows:
- void pin_isr(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins);
- What you put inside the body of the ISR is applicataion-depenedent.
3. The following example, toggles an LED every tome the interrupt is triggered.
void pin_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
gpio_pin_toggle_dt(&led);
}
- Define a variable of type static struct gpio_callback
- static struct gpio_callback pin_cb_data;
- The pin_cb_data gpio callback variable will hold information such as the pin number and the function to be called when an interrupt occurs
- Initialize the gpio callback variable pin_cb_data using gpio_init_callback().
- The gpio_callbackstruct variable stores the address of the callback function and the bitmask relevant to the pin
- Use the function gpio_init_callback() to do this initialization
- This section contains the usage definition and an example.
- For example, the following line will
- initialize the pin_cb_data variable with the callback function pin_isr
- and the bit mask of pin dev.pin.
- Note the use of the macro BIT(n), which simply gets an unsigned integer with bit position n set.
- gpio_init_callback(&pin_cb_data, pin_isr, BIT(dev.pin));
- The final step is to add the callback function through the function gpio_add_callback().
- This section contains the usage definition and an example
- he following line adds the callback function that we set up in the previous steps.
- gpio_add_callback(button.port, &pin_cb_data);
2.5. Dissecting the blinky sample
This section will dissect the blinky sample program to understand how it works.
Bring up the main.c code and follow along.
- Include modules
#include <zephyr.h>
#include <drivers/gpio.h>
- Define the node identifier
// LED0_NODE = led0 defined in the .dts file
#define LED0_NODE DT_ALIAS(led0)
- Retrieve the device pointer, pin number and configuration flags. Now let’s examine the main() function
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
- Verify that the device is ready for use
if (!device_is_ready(led.port)) {
return;
}
- Configure the GPIO pin
int ret;
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return;
}
- Continuously toggle the GPIO pin
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return;
}
k_msleep(SLEEP_TIME_MS);
}
2.6. Exercise 1 -- Controlling an LED through a button (polling based)
- Modify the blinky example so that LED1 is turned on only when button 1 is being pressed.
- These exercises only use the DTS and Schematic for the nRF52833 DK. Which have 4 push buttons.
- Where is the example of the schematic and DTS for the nRF9160 DK ?
Exercise Steps:
- Use the folder created for previous exercises.
- C:\nordic\myapps
- Download the exercise from the download button
- Filename: fund_less2_exer1
- Extract the zip file to C:\nordic\myapps
- Open the exercise code base in VS code
- From the Welcome window in the nRF Connect extension, click on Add an existing application.
- Open main.c from the Explorer in VS Code as shown in the screenshot below:
- Follow the Steps 3 thru 7 in the example code that I modified from the exercise1 section
#include <zephyr.h> #include <drivers/gpio.h> /* STEP 7 - Change the sleep time from 1000 ms to 100 ms */ #define SLEEP_TIME_MS 100 /* STEP 3.1 - Get the node identifier for button 1 through its alias sw0 */ #define SW0_NODE DT_ALIAS(sw0) /* STEP 3.2 - Get the device pointer. pin number, and pin's configuration flags through gpio_dt_spec */ static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios); /* LED0_NODE is the devicetree node identifier for the "led0" alias. */ #define LED0_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); void main(void) { int ret;
if (!device_is_ready(led.port)) { return; } /* STEP 4 - Verify that the device is ready for use */ if (!device_is_ready(button.port)) { return; } ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); if (ret < 0) { return; } /* STEP 5 - Configure the pin connected to the button to be an input pin and set its hardware specifications */ ret = gpio_pin_configure_dt(&button, GPIO_INPUT); if (ret < 0) { return; } while (1) { /* STEP 6.1 - Read the status of the button and store it */ bool val = gpio_pin_get_dt(&button); /* STEP 6.2 - Update the LED to the status of the button */ gpio_pin_set_dt(&led,val);
k_msleep(SLEEP_TIME_MS); // Put the main thread to sleep for 100ms for power optimization } }
|
Add a build configuration, like we did in Lesson 1 Exercise 2.
Build the exercise and flash it to the board as we have done in the previous lesson. Observe that when button 1 is pressed, LED1 is turned ON.
2.7. Exercise 2 -- Controlling an LED through a button (interrupt based)
Modify the application to use the more power-efficient interrupt-based method
Exercise Steps:
Use the folder created for previous exercises.
- C:\nordic\myapps
- Download the exercise from the download button
- Filename: fund_less2_exer2
- Extract the zip file to C:\nordic\myapps
- Open the exercise code base in VS code
- From the Welcome window in the nRF Connect extension, click on Add an existing application.
- Open main.c from the Explorer in VS Code
- Follow the Steps 3 thru 9 in the example code that I modified from the exercise1 section
- Open the exercise code base in VS code
#include <zephyr.h> #include <drivers/gpio.h> /* STEP 9 - Increase the sleep time from 100ms to 10 minutes */ #define SLEEP_TIME_MS 10*60*1000 /* SW0_NODE is the devicetree node identifier for the "sw0" alias */ #define SW0_NODE DT_ALIAS(sw0) static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios); /* LED0_NODE is the devicetree node identifier for the "led0" alias. */ #define LED0_NODE DT_ALIAS(led0) static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); /* STEP 4 - Define the callback function */ void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { gpio_pin_toggle_dt(&led); } /* STEP 5 - Define a variable of type static struct gpio_callback */ static struct gpio_callback button_cb_data; void main(void) { int ret; if (!device_is_ready(led.port)) { return; } if (!device_is_ready(button.port)) { return; } ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); if (ret < 0) { return; } ret = gpio_pin_configure_dt(&button, GPIO_INPUT); if (ret < 0) { return; } /* STEP 3 - Configure the interrupt on the button's pin */ ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE); /* STEP 6 - Initialize the static struct gpio_callback variable */ gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin)); /* STEP 7 - Add the callback function by calling gpio_add_callback() */ gpio_add_callback(button.port, &button_cb_data); while (1) { /* STEP 8 - Remove the polling code */ //bool val = gpio_pin_get_dt(&button); //gpio_pin_set_dt(&led,val); k_msleep(SLEEP_TIME_MS); } }
|
- Build the exercise and flash it to the board. Just like in Exercise 1, observe that when button 1 is pressed, the LED is toggled.
2.8. Lesson 2 quiz
- Took the quiz and I received a 100%
- BUT THE SCORE IS NOT KEPT? WHY?
Lesson 3 – Elements of an nRF Connect SDK application
- In nRF Connect SDK, an application has a number of different elements that are used by the build system in some way to create the final runnable file.
- Understanding these elements, why they are needed and how they interact with each other is crucial when creating your own application.
- In this lesson, we will explore each of these elements to understand how they all function in relation to each other.
In the exercise portion, we will create a minimal working application from scratch and add our own custom files and configurations to customize the application.
app/
|-- CMakeLists.txt
|-- Kconfig
|-- prj.conf
|-- <board_name>.overlay
|-- src/
|-- main.c
Objectives
Understand the use of Kconfig configuration files to enable and configure the different software modules available in nRF Connect SDK
Examine an application configuration file and a board configuration file and understand the relation between them
Learn how to explore the available configuration options of a certain software module using guiconfig
Understand multi-image builds, and how a child-image is added to your application
Practice through hands-on exercise how to create an application from scratch, and how to add modules using Kconfig and modify the devicetree
I Read thru the 2 chapters
- Configuration files,
- Devicetree overlays, CMake and multi-image builds
Exercise 1
- Ran thru the entire exercise
- I learned how to create a working “Hello World” application in nRF Connect SDK from scratch.
- RESULTS
- The only real problem I had was getting the terminal working from step 8 of the exercise?
- What I found on my devices for my board were 3 com port?
- I did not know which one to pick, so I continually tried the first one then the 2nd and after many tries the 3rd one worked.
- I’m not what the other 2 com ports are for? But I noticed that that the working com port, COM7 was indicated a VCOM0. So I’m assuming that the nRF9160 has 3 UART ports.
- I also discovered how to attach a COM port to NRF Terminal. This will elevate the need for Putty.
- To do this
- Click on the Plug button on the right side of the COM port label in the device panel. The NRF TERMINAL window appears in the
- NOTE The 1st time you try this, a install dialog will come up to install NRF TERMINAL. Select it and the install will occur.
- Much easier to use in VS Code. But it is your preference to use.
- Click on the Plug button on the right side of the COM port label in the device panel. The NRF TERMINAL window appears in the
- To do this
- The only real problem I had was getting the terminal working from step 8 of the exercise?
Exercise 2
- Ran thru the entire exercise
- I learned How To customize the application from exercise 1, by adding custom files and configurations and modifying the devicetree.
- I added a separate function file with a sum function. This sum() function is called in in main() to print a sum in the hello world message.
- RESULTS
- I did not see this image as directed in step 11 in the NOTE boxwhen trying to create the overlay file?
- Create an overlay file in the application directory (the same location as CMakeLists.txtand prj.conf) with the name of the board you’re using, in our case nrf52833dk_nrf52833.overlay.
- I did not see this image as directed in step 11 in the NOTE boxwhen trying to create the overlay file?
- Note
- In nRF Connect for VS Code, in the Details panel, there is an option to create an overlay file with the correct board name for you, see the image below. This function also lets you view all the nodes in the devicetree and will add in the correct syntax for you.
- Continued
- NEXT I ran into a slight problem starting at step 13 in the exercise.
- The Baud rate was not being reset to 9600 after I rebuilt/and flashed the firmware.
- Garbage was being printed on the terminal indicating that the putty was receiving at a higher baud rate.
- When I went to follow the next steps I realized that there was no Device Tree tab on the NRF Connect menu.
- SO I assumed it was not installed.
- After the install, the TAB appeared after rebooting VS CODE
- But the TAB was not named DeviceTree as the exercise step shows; Instead, It had the name of my device. This took me a few minutes to relive the change, I was able to confirm the current baud rate to be 9600 as described.
- Now did a rebuild and flash again, But the baud rate changes didn’t take. The firmware was still running at the higher baud rate?
- I realized that I had press the Build and the Flash options in the menu.
- But found when I selected the “Pristine Build” it worked fine
- SO I assumed it was not installed.
- The Baud rate was not being reset to 9600 after I rebuilt/and flashed the firmware.
Lesson 3 QUIZ
- Took the quiz and I received a 100%
- BUT THE SCORE IS NOT KEPT? WHY?
Lesson 4 – Printing messages to console and logging
Lesson 4 – Printing messages to console and logging
- In this lesson we will learn more about logging using both the simple method of printk() and a sophisticated method using the advanced logging module.
- In Exercise1, we will practice using the user-friendly printk() function to print string literals and formatted strings to a console.
- In Exercise 2, we will cover enabling/configuring the features-rich logger module to print string literals, formatted strings, and hex dump
- Lastly, in Exercise 3, we will examine the logger module features in more depth.
- Objectives
- Learn how to print strings and formatted strings to a console using printk()
- Recognize the limitations of printk()
- Learn how to print strings and formatted strings to a console using the logger module
- Learn how to hex dump variables using the logger module
- Explore the logger module features
- Practice through hands-on exercises enabling/configuring software modules
printk() function
- The syntax of printk() is similar to the standard printf() in C.
- However printk(),.is a less advanced function that only supports a subset of the printf() features.
- This makes printk() more optimized for embedded development.
- Here are the set of specifiers supported.
Signed decimal: %d, %i and its subcategories
Unsigned decimal: %u and its subcategories
Unsigned hexadecimal: %x (%X is treated as %x)
Pointer: %p
String: %s
Character: %c
Percent: %%
New line: \n
Carriage return: \r
- Usage
- printk("Button 1 was pressed!\n\r");
- int x = 44;
- printk("The value of x is %d\n\r",x);
- To use the function in your firmware follow the next 3 steps.
- Include the header file <sys/printk.h> in your application source code.
- Include the console drivers.
- enabling the configuration option CONFIG_CONSOLE in the application configuration file.
- If not set in the board configuration file Select the console.
- The default console set in the board configuration file is the UART console
- There are a few options available, such as the UART console (CONFIG_UART_CONSOLE) and RTT console (CONFIG_RTT_CONSOLE).
Logger module
- This module is the recommended method for SENDING MESSAGES TO THE CONSOLE.
- The printk() function does not return until all bytes are sent.
- The logger module supports both in-place and deferred logging
- Also supports many advanced features as:
- Multiple backends
- Compile time filtering on module level
- Run time filtering independent for each backend
- Timestamping with user provided function
- Dedicated API for dumping data
- Coloring of logs
- printk() support – printk message can be redirected to the logger
- The logging module, when used, will by default create a low priority thread (log_process_thread_func). The task of this thread is to take the deferred “queued” logs and push them out to a console.
- The logger module is highly configurable at compile time and at run time.
- There are 4 Security Levels, which can be set
- DBG, INF, WRN, or ERR.
- Dumping data
- The LOG_HEXDUMP_X macros for dumping data where X can be DBG, INF, WRN, or ERR.
- Configuration categories
- There are two configuration categories for the logger module: configurations per module and global configuration.
Exercise 1
- In this exercise, we will practice using the user-friendly printk() function to print strings to the console.
- Followed the 9 steps
- Results
- There were not problems
- My Results
Exercise 2
In this exercise, we will simply redo Exercise 1 of this lesson but this time we will be using the feature-rich logger module. We will enable the logger, and utilize it to print logs of different severities and to hexdump variables.
Followed the 9 steps
Results
- There were not problems
Exercise 3
Exploring the logger module features In this exercise, we will practice configuring a software module using Kconfig configuration options. We will use the logger module as a case study and experiment with some of its features.
Let’s verify this by invoking guiconfig.
PROBLEM HOW DO I RUN guiconfig? From where?
- At first I had a problem finding this
- Then I went to the Actions menu on the Kconfig barr brought up the … and select guiconfig as shown and the dialog came up
The logger module is found under the menu Sub Systems and OS Services as shown in the figure below:
Followed the 9 steps
Results
- working
Lesson 4 Quiz
Completed
Lesson 5 – Serial communication (UART)
In this lesson , you will learn how to use the UART driver in an interrupt-driven fashion so that when new data arrives the applicationis interrupted and a callback function is called.
The ex\xercise section , will control the LED’s on the board by sending commands over UART
What you will learn in this section
- Send/receive data over UART in asynchronous mode
- How to configure the UART hardware through the UART API.
- Practice using the UART Driver
UART Protocol
This section describes the UART Protocol
UART Driver
This section describes the UART Driver
It describes the UART Asynchronous mode API
There are 2 other UART API available
- Polling
- Raw-interrupts
you can learn more about the other APIs here.
Exercise 1 – Controlling LEDs through UART
Completed successfully. What a great exercise
Lesson 6 – Serial communication (I2C)
This Lesson demonstrates how to use i2C a popular protocol used to communicate to external components. In this lesson you will go i\over I2C basics and learn how to use the I2C driver.
I was un able to complete these exercises because I did not have the required components to use as I2C components.
- Exercise 1 – Using the X-NUCLEO-IKS01A3 expansion board on top of a DK to communicate with the STTS751 temperature sensor.
- Exercise 2 – Using a Thingy:91 or Thingy:53 to connect to the BH1749 color sensor on the board.
I wondering if I can connect a seed grove expansion board?
I2C Protocol
This section describes the I2C protocol.
I2C Driver
This section describes how to setup the I2C driver in the nRF Connect SDK, focusing on the master.
For this exercise, we will use the very simple STTS751temperature sensor, which is hosted on an X-NUCLEO-IKS01A3 expansion board.
Exercise 1 -- Connecting an STTS751 temperature sensor
When placing the board on top of the DK in the UNO R3 connector, the sensors on the expansion board connect to the nRF52833 DK through the I2C SDA and SCL lines which are wired to pins P0.26 and P0.27. This is shown in the GPIO pin mapping obtained from the nRF52833 DK schematics.
I wonder,Can I Connect the SEED groove expansion board?
Exercise 2 -- Connecting to the BH1749 Ambient Light Sensor on the Thingy:91 and Thingy:53
Instead of the usual development kits, we have used to run samples and exercises throughout this course, this exercise uses a special type of Nordic Semiconductors’ prototyping boards called the Thingy. This exercise can be run on either a Thingy:53 or a Thingy:91.
I do not own one of these now. But maybe someday I can get my hands on one and try this exercise. I just became selected for a hackster.io contest including the Thingy53. So stay tuned for some progress on this.
I should be able to connect jumper wires to this connection. Maybe try the groove display?
Lesson 7 – Multithreaded applications
Bare-metal vs RTOS programming
This section explains the differences between a bare-metal piece of firmware to a system of firmware thread running over a real time operating system. This is one of the best descriptons I have seen yet.
Zephyr RTOS basics
This section describes how firmware runs in a multi-thread archetecture over the RTOS used on the board.
Exercise 1 - Thread creation and priorities
In this exercise, we will learn how to create and initialize two threads and learn how they can affect one another with their priorities.
This is done using the K_THREAD_DEFINE() macro. This is the macro for defining and initializing a thread and plugging its data structures into the RTOS kernel.
Completed Exercise
Exercise 2 - Time slicing
If you don’t want to worry about creating perfect logic for yielding between equal priority threads, you can enable time slicing.
In this exercise, we will take a close look at time slicing and how it affects the interaction between threads, both of equal and different priorities.
Very good example of using 2 threads and utilizing time slicing to tune the execution of threads, so each thread gets time to execute.
Completed Exercise
Exercise 3 -- Workqueue creation and work item submission
Since threads of higher priority have the ability to starve other low priority threads, it is good practice to offload all non-urgent execution in these threads into lower-priority threads.
In this exercise, we will create and initialize a workqueue to offload work from a higher priority thread.
There are 2 sections in this exercises that take you to point from the point of the last exercise,(where thread0 was hogging all the time.), to using a workqueue to make a more balanced execution of all threads.
- Threads with different priorities
- Offloading work from high priority task
- Completed Exercise
Lesson 8 – Thread synchronization
This lesson discusses the use of both semaphores and mutexes as a thread synchronization mechanism in a RTOS environment.
in this section, semaphores and mutexes are discussed in detail, including a list of properties and a visualization.
In this section an excellent explanation and visualization of Semaphores is presented.
IN this section another excellent explanation and visualization of Semaphores is presented.
In this exercise, we will initialize a semaphore that will have the count-property reflect the number of instances of a particular resource in the system.
There will be one consumer thread that will want to consume the resource
- and a producer thread that will produce it.
Every time the consumer thread tries to get access,
- we decrement and print the value of the count,
and every time the producer thread relinquishes access to the instance,
- we increment and print the value of the count.
Perform 11 exercise steps
COMPLETED exercise
In this exercise, we will create an application with two threads running and accessing the same code section.
The logic looks perfect, but when two different threads try to access the code section simultaneously, unexpected things happen.
We will see how a mutex can be utilized to synchronize these two threads.
Perform 13 exercises
COMPLETD Exercise
Finished the entire course
But progress bar only show 88% done. I wonder why?
It would be nice to include a lesson on getting the debugger operational with the firmware environmnet. I will need to look into this.
All in all a great course. It took me a long time top complete but I now have a working knowledge of the programming environment using open source tools and VS code as an IDE. Very Cool.