Leds and Button Sample

Last updated on: January 10, 2023

Introduction

The "Leds and Button Sample" cycles through multiple LED colors, one color at a time, on each press of the button. The LEDs and button are initialized using information directly from the board's device tree. This sample is compatible with the following boards:

  • Icarus IoT board
  • Icarus Bee
[NCS v2.2.x] Download sample from Icarus - LEDs and Button Sample (NCS v2.2.x)
[NCS v2.1.x] Download sample from Icarus - LEDs and Button Sample (NCS v2.1.x)
(legacy) Download sample from Icarus - LEDs and Button Sample (legacy)

Project configuration

The initialization of the application is done in the prj.conf file. By enabling certain options in this file, Zephyr will include the required libraries for the application. For this sample the following options are set:

CONFIG_GPIO=y

The option CONFIG_GPIO enables the use of the GPIO library. This library will allow for inputs to be read from the button, and LEDs to be turned on and off.

Code explanation

First a reference to the GPIO controller needs to be obtained. The controller is defined in the device tree of the board. The device tree is expressed as a set of files which are included in the nRF Connect SDK. In these files, the hardware of the Icarus boards is described in terms of hardware addresses and pin numbers.

In main.c, this reference is obtained in the main function:

static const struct device *gpio_dev;
gpio_dev = DEVICE_DT_GET(GPIO_NODE);

with the GPIO_NODE defined as:

#define GPIO_NODE DT_NODELABEL(gpio0)

In the device tree, the GPIO controller is declared as a node: gpio0. This controller is what receives all GPIO inputs (buttons) and sets all GPIO outputs (LEDs). Using DEVICE_DT_GET a pointer to the device with the node name gpio0 is obtained from the device tree. This pointer is then set to the gpio_dev structure.

After obtaining the reference for the GPIO controller, references need to be obtained for the actual LEDs and the button. This is done in a similar way from the device tree. This time instead of being defined as a node, the LEDs and button have aliases defined in the device tree. The red, green and blue LEDs have the aliases: led0, led1 and led2 respectively. The button has the alias: sw0.

The references to the LEDs and the button are stored in a different structure to the GPIO controller. This is because they contain additional information: the physical pin numbers. The references are obtained as follows:

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios);
static struct gpio_dt_spec red_led = GPIO_DT_SPEC_GET(RED_LED_NODE, gpios);
static struct gpio_dt_spec green_led = GPIO_DT_SPEC_GET(GREEN_LED_NODE, gpios);
static struct gpio_dt_spec blue_led = GPIO_DT_SPEC_GET(BLUE_LED_NODE, gpios);

with the BUTTON_NODE, RED_LED_NODE, GREEN_LED_NODE, BLUE_LED_NODE defined as:

#define BUTTON_NODE DT_ALIAS(sw0)
#define RED_LED_NODE DT_ALIAS(led0)
#define GREEN_LED_NODE DT_ALIAS(led1)
#define BLUE_LED_NODE DT_ALIAS(led2)

The gpios parameter corresponds to the property name in the device tree. This property contains the pin information.

Using the references which are now obtained, the LEDs and the button can be configured. This step configures whether the GPIO is in input or output mode. The button needs to be in input mode, since the GPIO controller is receiving inputs from the button. On the other hand, the LEDs need to be in output mode.

The GPIO_OUTPUT_INACTIVE flag configures each LED pin as an output, and initializes it to a logic 0. Since the LEDs are defined as active low in the device-tree, the pin is set to high in hardware, but this turns the LED off. This is handled internally in the gpio_pin_configure_dt function. The initialization of the LEDs is therefore done as follows:

gpio_pin_configure_dt(&red_led, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&green_led, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&blue_led, GPIO_OUTPUT_INACTIVE);

The button configuration has the additional step of setting up an interrupt callback. The callback is a function which is executed whenever a certain requirement(the interrupt) is satisfied. In this case, the interrupt caused by a button press. This allows the application to execute code immediately when the button is pressed, without actively checking whether it has been pressed using a loop for instance. The button configuration is done as follows:

int ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button.port->name, button.pin);
return false;
}
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button.port->name, button.pin);
return false;
}
gpio_init_callback(&gpio_cb, button_pressed_callback, BIT(button.pin));
gpio_add_callback(button.port, &gpio_cb);
return true;

Now all the GPIO is set up and configured, the LEDs can be turned on and off using the following functions:

gpio_pin_set_dt(&red_led, LED_ON);
gpio_pin_set_dt(&red_led, LED_OFF);

LED_OFF is defined as 0. The function gpio_pin_set_dt interprets this 0 as a logic 0. Since the LEDs are defined as active low in the device-tree, the logic 0 turns the LED off using a high signal on the hardware level.

LED_ON is defined as !LED_OFF. Passing anything to the gpio_pin_set_dt function, which is not 0, is interpreted as a logic 1. This turns the LEDs on.

To create compound colors, multiple LEDs must be turned on at the same time. For example, the LED can be made to shine yellow by turning on the red and green LED at the same time:

gpio_pin_set_dt(&red_led, LED_ON);
gpio_pin_set_dt(&green_led, LED_ON);