Let's do less manual work

As you might have noted, we had to retrieve the const struct device * corresponding to gpioa and configure and toggle pin 5. Of course, we do not want to hardcode the port and the pin number in our code.

Finding the led

If you look at $ZEPHYR_BASE/boards/st/disco_l475_iot1/disco_l475_iot1.dts, you'll notice some interesting fragments, such as:

/ {
  aliases {
    led0 = &green_led_2;
  };
  leds {
    compatible="gpio-leds";
    green_led_1: {
      gpios = < &gpiob 14 GPIO_ACTIVE_HIGH >;
    };
  };
};

The node /aliases is a special one: it defines global aliases on nodes that can then be found with macro DT_ALIAS, such as in DT_ALIAS(led0).

⚠️ Make sure that you use DT_ALIAS to refer to a global alias, and DT_NODELABEL to refer to a node label, those are not interchangeable.

This would in turn reference the green_led_2 node label, which designated a node which contains an interesting gpios property < &gpiob 14 GPIO_ACTIVE_HIGH > with:

  • a reference to a port (&gpiob)
  • a pin number (14)
  • a GPIO_ACTIVE_HIGH flag

Note that the flags could have contained GPIO_ACTIVE_LOW, which would have meant that the ACTIVE state is the LOW state, and the INACTIVE state the HIGH state. By using these flags when initializing the port in your code, you can then use ACTIVE and INACTIVE (rather than HIGH and LOW) and get the proper behaviour.

Our goal will be to retrieve and use this gpio structure.

Looking at the consolidated device tree

You will find the consolidated device tree in build/zephyr/zephyr.dts: it contains a merge of $ZEPHYR_BASE/boards/arm/disco_l475_iot1/disco_l475_iot1.dts and of your own device tree fragments. Right now we don't have any, but we will create some later.

Note that in this file, all numbers are printed in hexadecimal and flags have been replaced by their values. For example, you will find

/ {
  aliases {
    led0 = &green_led_2;
  };
  leds {
    compatible="gpio-leds";
    green_led_1: {
      gpios = < &gpiob 0xe 0 >;
    };
  };
};

This is exactly the same thing as before: 14 == 0xe, and GPIO_ACTIVE_HIGH has a value of 0.

Retrieving the GPIO structure

Zephyr has a handy struct gpio_dt_spec structure which contains three fields: a port device (such as gpioa), a pin number and the flags. Functions from the GPIO driver have an equivalent that takes such struct as a parameter. Also, some macros allow the easy parsing of this structure:

// Designate the `led0` alias from the device tree
#define LED_NODE DT_ALIAS(led0)

// Get the GPIO (port, pin and flags) corresponding to the
// `led0` node alias at compile time.
static const struct gpio_dt_spec led_gpio = GPIO_DT_SPEC_GET(LED_NODE, gpios);

int main() {
  if (!device_is_ready(led_gpio.port)) {
    return -ENODEV;
  }
  // Note that we use `INACTIVE`, not `LOW`. On some boards,
  // the behaviour of the output may be reversed, but this
  // is not our concern. This info belongs to the device tree.
  gpio_pin_configure_dt(&led_gpio, GPIO_OUTPUT_INACTIVE);
  for (;;) {
    gpio_pin_toggle_dt(&led_gpio);
    k_sleep(K_SECONDS(1));
  }
  return 0;
}

Do it, check that it works. Commit push.

Note how the only reference to the hardware is now the led0 name. But you can do better.

Change the led

What if you wanted to use another led as led0?

Create a file named boards/disco_l475_iot1.overlay at the top of your project. Now, you can redefine the alias:

/ {
  aliases {
    led0 = &green_led_1;
  };
};

Since this is a new file, you must reconfigure the project. Remove the build directory before rebuilding the project.

Check that the other green led blinks. Did you notice that you haven't changed one single line of C code?

Oh, enough played, you can remove this board specific files that you created and return the led to its original location.

The led device driver

In fact, Zephyr also has a led device driver. Each device can drive several leds. You can retrieve a const struct device * const directly from the /leds node and forget about even initializing the ports and so on.

Retrieve a pointer to the leds device and make the two leds at index 0 and 1 alternate every second (one is on, the other is off). Do not try to use led_blink(): this function is optional and is not implemented for the GPIO led driver.

If you get an error, maybe you should see if you have enabled everything you need in prj.conf. Is the led driver enabled?

Is it working now? Great!

However, note that we work with const struct device * all the time: the device drivers are not strongly typed. This is your responsibility to only call the right functions on the right device.

Adding a new led

If you look at the board user manual you'll notice two other leds: a yellow one and a blue one. Both are driven at the same time: writing a high level to the corresponding port turns the yellow led on and the blue led off, writing a low level turns the yellow led off and the blue led on.

We want to add this new led as a child to the /leds node. Create a file named boards/disco_l475_iot1.overlay at the top of your project. In it, you will be able to add things to enrich the device tree:

/ {
  leds {
    /* What you will add here will be added to the /leds node */
  };
};

Add a new led blue_led in leds which corresponds to the blue led. Use the board user manual to find on which port the led is connected. Is its active state GPIO_ACTIVE_HIGH or GPIO_ACTIVE_LOW?. What extra flag must you add so that the yellow led does not turn on when you turn off the blue led? Note that you can combine flags in the device tree by putting them between parentheses and separating them with a pipe symbol ((FLAG1 | FLAG2 | FLAG3)).

You will have to remove the build directory in order to have the build system consider the new file.

Once this is done, modify your code so that:

  • turn on all leds (at index 0, 1, and 2)
  • wait 1s
  • turn all leds off
  • wait 1s
  • start over

Does the blue led light up at the same time as the green leds? Great!

Changing the meaning of the output

What if you consider that the led that should get turned on is the yellow led instead of the blue led? Change the flag in the overlay file that you have created to GPIO_ACTIVE_HIGH. Run your program again.

Interesting. How can you drive the yellow led without driving the blue one? (you don't have to make it do so, just think about it)

If you have not done so, you need to read the introduction to device trees in Zephyr documentation.