Neopixel Led and Button Sample
Introduction
The "Neopixel Led and Button Sample" cycles through multiple LED colors, one color at a time, on each press of the button. The Neopixel LED and button are initialized using information directly from the board's device tree. This sample is compatible with the following boards:
[NCS v2.1.x and v2.2.x] Download sample from Icarus - Neopixel and Button Sample (NCS v2.1.x and v2.2.x)
The neopixel LED stores the last state of the LED even after removing this sample from the board. This can lead to the LED staying on when flashed with other firmware. To avoid this, you can press the reset button while this sample is on the board, or re-flash this sample since the LED is turned off at the start of this sample.
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
CONFIG_LED_STRIP=y
CONFIG_WS2812_STRIP=y
CONFIG_SPI=y
The option CONFIG_GPIO=y
enables the use of the GPIO library. This library will allow for inputs to be read from the button.
The option CONFIG_LED_STRIP=y
enables the use of the LED strip driver.
The option CONFIG_WS2812_STRIP=y
enables the driver for the WS2812 (and compatible) LED strip.
The option CONFIG_SPI=y
enables the SPI hardware bus, needed to communicate with the LED strip.
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 that 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 and outputs, which is needed for the button in this sample. 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 Neopixel LED and the button. This is done in a similar way: from the device tree. This time instead of being defined as a node, the LED and button have aliases defined in the device tree. The LED has the alias led_strip
. The button has the alias: sw0
.
Using these aliases, both the LED and the button's hardware information can be stored in a structure as follows:
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios);
static const struct device *const strip = DEVICE_DT_GET(STRIP_NODE);
with the BUTTON_NODE
and STRIP_NODE
defined as:
#define BUTTON_NODE DT_ALIAS(sw0)
#define STRIP_NODE DT_ALIAS(led_strip)
The gpios
parameter corresponds to the property name in the device tree. This property contains the pin information of the button.
The button needs to be initialized before it can be used. This is done by first configuring the button pin as a GPIO input, and configuring an interrupt for the button. The interrupt is set up using the GPIO_INT_EDGE_TO_ACTIVE
flag, which is triggered when the button pin state changes to a logical high.
A callback for the button is also registered. The callback is a function that is executed whenever a certain requirement(the interrupt) is satisfied. 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 example. The button initialization 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;
The actual callback function is very simple:
void button_pressed_callback(const struct device *gpiob, struct gpio_callback *cb, gpio_port_pins_t pins)
{
button_pressed = true;
}
all that happens is the button_pressed
boolean is set to true. This boolean will be used in the main loop to cycle the LED color.
the button is now initialized and ready to use. Since the Neopixel LED is connected via a serial bus, it does not need to be initialized. However, a check is done to ensure that the LED is ready and reachable:
if (!device_is_ready(strip)) {
printk("LED strip device %s is not ready", strip->name);
return false;
}
Now all the devices are set up, the LED is first asserted to be off. This is done using the following code:
memset(&pixels, 0x00, sizeof(pixels));
rc = led_strip_update_rgb(strip, pixels, STRIP_NUM_PIXELS);
here the pixels
struct is defined globally as follows:
struct led_rgb pixels[STRIP_NUM_PIXELS];
the STRIP_NUM_PIXELS
is the amount of total pixels in the LED strip. Since the board only contains 1 LED, this is equal to 1 pixel. By setting every pixel to 0x00
the LED is fully turned off.
Next, the main loop starts, where a button press will cycle through a set of pre-defined colors. The colors are defined in the global struct colors
as follows:
static const struct led_rgb colors[] = {
RGB(0x0f, 0x00, 0x00), /* red */
RGB(0x00, 0x0f, 0x00), /* green */
RGB(0x00, 0x00, 0x0f), /* blue */
RGB(0x0f, 0x00, 0x0f), /* magenta */
RGB(0x00, 0x0f, 0x0f), /* cyan */
RGB(0x0f, 0x0f, 0x00), /* yellow */
};
These are just some basic colors, but any color can be added to this struct using the correct RGB values in hexadecimal for that color. The while loop cycles through these colors as follows:
while (1) {
if (button_pressed) {
memset(&pixels, 0x00, sizeof(pixels));
memcpy(&pixels, &colors[color], sizeof(pixels));
rc = led_strip_update_rgb(strip, pixels, STRIP_NUM_PIXELS);
if (rc) {
printk("couldn't update strip: %d", rc);
}
color++;
if (color >= ARRAY_SIZE(colors)) {
color = 0;
}
button_pressed = false;
}
k_sleep(K_MSEC(100));
}
every time the button is pressed and the button_pressed
boolean is set to true in the callback, the RGB values in the pixels
struct are all set to 0x00, clearing any previous values. The RGB values in the color
index of the colors
struct are then copied into the pixels
struct which is then used to update the LED.
The color
index is then incremented until all colors have been displayed at which point it resets back to 0, showing the first color again. The button_pressed
flag is set to false, ready for the next button press.