GPIO

The phyCORE-RT1170 module provides extensive support for General-Purpose Input/Output (GPIO) pins. These pins can be configured as either inputs or outputs and many signals on the phyCORE Connector are available as GPIOs through pin multiplexing.

The i.MX RT1170 processor includes 13 independent GPIO controllers. For detailed hardware-level information, refer to the GPIO Interface chapter in the Hardware Manual.

GPIO Controller Mapping

Each GPIO controller is exposed as a device node in the system. To list all available GPIO controllers, you can use the Zephyr shell:

uart:~$ gpio devices
Device           Other names
gpio@4012c000    gpio1
gpio@40138000    gpio4
gpio@4013c000    gpio5
gpio@40140000    gpio6
gpio@40c5c000    gpio7
gpio@40c60000    gpio8
gpio@40c64000    gpio9
gpio@40c68000    gpio10
gpio@40c6c000    gpio11
gpio@40c70000    gpio12
gpio@40ca0000    gpio13
gpio@40130000    gpio2
gpio@40134000    gpio3
gpio@42008000    fgpio2
gpio@4200c000    fgpio3

You can inspect the available GPIO lines within a specific controller by running:

uart:~$ gpio info gpio1
ngpios: 32
Reserved pin mask: 0x00000000

Reserved  Pin  Line Name
           0
           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31

Alternatively, run the command without arguments to list all GPIO lines across all controllers:

uart:~$ gpio info

Controlling GPIOs via Shell

The Zephyr GPIO Shell subsystem enables configuration and control of GPIO pins directly from the shell.

To configure a GPIO line (e.g., pin 13 on gpio@40c64000 / gpio9) as output:

uart:~$ gpio conf gpio9 13 o

To set the output value of the pin (e.g., turn LED2 located on the carrier-board on):

uart:~$ gpio set gpio9 13 1

To configure the button as input (e.g., pin 10 on gpio@40c68000 / gpio10):

uart:~$ gpio conf gpio10 2 i

After configuring a pin as input, you can read its current value:

uart:~$ gpio get gpio10 2

Example: If a button (e.g., S1 on the carrier board) is connected to the pin, press and release it between consecutive gpio get commands to observe the change in value.

Heartbeat LED Example

The heartbeat() function implements a blinking heartbeat LED with a specific pattern. The LED blinks in a sequence of short-short-long-off, where the short blink is 150ms on and 50ms off, and the long pause is 1 second.

GPIO API Usage

The heartbeat() function utilizes the Zephyr GPIO API to control the LED. Here’s a breakdown of the API functions used:

  • gpio_is_ready_dt(): Checks if the GPIO device is ready to use. If not, the function returns without configuring the LED.

  • gpio_pin_configure_dt(): Configures the LED pin as an output using the device tree specification. The GPIO_OUTPUT_ACTIVE flag specifies that the LED pin should be configured as an active output.

  • gpio_pin_set_dt(): Sets the state of the LED pin. This function is used to turn the LED on (value 1) and off (value 0), creating the blink pattern.

GPIO Pin Configuration

The LED pin is configured using the gpio_pin_configure_dt() function, which takes the device tree specification (led) and configuration flags (GPIO_OUTPUT_ACTIVE). The device tree specification is obtained using the GPIO_DT_SPEC_GET() macro, which retrieves the GPIO specification from the devicetree node identified by the “led0” alias.

Controlling the LED State

The gpio_pin_set_dt() function is used to set the state of the LED pin. By passing the device tree specification (led) and the desired state (0 or 1), the LED can be turned on or off. In this example, the LED state is toggled to create the blink pattern, with error checking to handle any potential issues with setting the pin state.

Source Code

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* size of stack area used by each thread */
#define HEARTBEAT_STACKSIZE	1024
/* scheduling priority used by each thread */
#define HEARTBEAT_PRIORITY	5
/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE		DT_ALIAS(led0)

/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

void heartbeat(void)
{
	int ret;

	if (!gpio_is_ready_dt(&led)) {
		return;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return;
	}

	while (1) {
		ret = gpio_pin_set_dt(&led, 1);
		if (ret < 0) {
			return;
		}
		k_sleep(K_MSEC(150));

		ret = gpio_pin_set_dt(&led, 0);
		if (ret < 0) {
			return;
		}
		k_sleep(K_MSEC(50));

		ret = gpio_pin_set_dt(&led, 1);
		if (ret < 0) {
			return;
		}
		k_sleep(K_MSEC(150));

		ret = gpio_pin_set_dt(&led, 0);
		if (ret < 0) {
			return;
		}

		k_sleep(K_SECONDS(1));
	}
}

K_THREAD_DEFINE(heartbeat_tid, HEARTBEAT_STACKSIZE, heartbeat, NULL, NULL,
		NULL, HEARTBEAT_PRIORITY, 0, 0);

For more information see the heartbeat.c file.

GPIO Button Example

The button() function sets up and configures a GPIO pin connected to a button so that a callback function, button_pressed(), is triggered when the pin transitions from low to high (i.e., when the button is pressed). The button_pressed() callback logs a message including a timestamp whenever this event occurs.

GPIO Configuration

The example uses the Zephyr GPIO API to interact with the button hardware, defined in the devicetree using the sw0 alias. The gpio_dt_spec structure sw_btn holds the relevant GPIO configuration extracted from the devicetree.

The line:

gpio_is_ready_dt(&sw_btn)

verifies that the device corresponding to the GPIO controller is initialized and ready before proceeding.

Pin Setup

The pin is configured as an input using:

gpio_pin_configure_dt(&sw_btn, GPIO_INPUT);

This prepares the GPIO line to read logic levels (high or low) from the button.

Interrupt Configuration

An interrupt is configured on the pin to trigger when the logic level transitions from low to high using:

gpio_pin_interrupt_configure_dt(&sw_btn, GPIO_INT_EDGE_TO_ACTIVE);

This enables edge detection so the system reacts only when the button is pressed (assuming active-high logic).

Callback Setup

To handle the interrupt event, a callback is initialized and registered:

gpio_init_callback(&button_cb_data, button_pressed, BIT(sw_btn.pin));
gpio_add_callback(sw_btn.port, &button_cb_data);

The gpio_init_callback() function associates the button_cb_data structure with the button_pressed() function and specifies which pin should trigger it. The gpio_add_callback() function then registers this callback with the GPIO device.

The button_pressed() function is subsequently invoked whenever the button is pressed, and it logs the event using printk().

Source Code

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <inttypes.h>

#define SLEEP_TIME_S		K_SECONDS(1)

/* size of stack area used by each thread */
#define BUTTON_STACKSIZE	1024
/* scheduling priority used by each thread */
#define BUTTON_PRIORITY		5

/*
 * Get button configuration from the devicetree sw0 alias. This is mandatory.
 */
#define SW0_NODE	DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS_OKAY(SW0_NODE)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec sw_btn = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,
							      {0});
static struct gpio_callback button_cb_data;

void button_pressed(const struct device *dev, struct gpio_callback *cb,
		    uint32_t pins)
{
	printk("Button S1 pressed at %" PRIu32 "\n", k_cycle_get_32());
}

void button(void)
{
	int ret;

	if (!gpio_is_ready_dt(&sw_btn)) {
		printk("Error: button device %s is not ready\n",
		       sw_btn.port->name);
		return;
	}

	ret = gpio_pin_configure_dt(&sw_btn, GPIO_INPUT);
	if (ret != 0) {
		printk("Error %d: failed to configure %s pin %d\n",
		       ret, sw_btn.port->name, sw_btn.pin);
		return;
	}

	ret = gpio_pin_interrupt_configure_dt(&sw_btn,
					      GPIO_INT_EDGE_TO_ACTIVE);
	if (ret != 0) {
		printk("Error %d: failed to configure interrupt on %s pin %d\n",
			ret, sw_btn.port->name, sw_btn.pin);
		return;
	}

	gpio_init_callback(&button_cb_data, button_pressed, BIT(sw_btn.pin));
	gpio_add_callback(sw_btn.port, &button_cb_data);
	printk("Set up button at %s pin %d\n", sw_btn.port->name, sw_btn.pin);

	while (1) {
		k_sleep(SLEEP_TIME_S);
	}
}

K_THREAD_DEFINE(button_tid, BUTTON_STACKSIZE, button, NULL, NULL,
		NULL, BUTTON_PRIORITY, 0, 0);

For more information see the TODO! file.