BME554L -Fall 2025 - Palmeri
Duke University
Invalid Date
read()
in main()
void main() {
while (1) {
// do stuff
k_msleep(1000);
sw0_event = command_to_read_digital_pin();
if (sw0_event) {
// do stuff
}
}
}
main()
/ other threads from running, otherwise device is paralyzed from acting.
prj.conf
- enable GPIO, enable logging librariesdevicetree.overlay
- define GPIO pin as an input, define callback functionmain.c
- initialize GPIO struct, initialize callback struct, associate callback with GPIO pin, define callback function, test for callback event state in your codeThe Devicetree is used to separate hardware-specific definitions from the firmware logic. Your development kit has a pre-defined devicetree in Zephyr that can be modified with an overlay file. This overlay file can be:
GPIO_ACTIVE_LOW
- button is active when pulled to low voltage (GND
)GPIO_ACTIVE_HIGH
- button is active when pulled to high voltage (VDD
)https://docs.nordicsemi.com/bundle/ug_nrf52833_dk/page/UG/dk/hw_buttons_leds.html
// create this struct before main()
// initialize GPIO struct
static const struct gpio_dt_spec sw0 = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
DT_ALIAS
: reference the pin of interest by an alias (sw0
) in the DTsw0
: name of the struct that will store the information about the GPIO pin// declare callback function
void sw0_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
sw0_cb
: name of the struct based on the gpio_callback
prototype that will store the information about the callback functionevent
or toggle the state of a Boolean, the value of which is reset after action is taken in the main code.main()
// check if interface is ready
if (!device_is_ready(sw0.port)) {
LOG_ERR("gpio0 interface not ready."); // logging module output
return -1; // exit code that will exit main()
}
// configure GPIO pin
int err;
err = gpio_pin_configure_dt(&sw0, GPIO_INPUT);
if (err < 0) {
LOG_ERR("Cannot configure sw0 pin.");
return err;
}
// associate callback with GPIO pin
err = gpio_pin_interrupt_configure_dt(&sw0, GPIO_INT_EDGE_TO_ACTIVE); // trigger on transition from INACTIVE -> ACTIVE
// ACTIVE could be HIGH or LOW
if (err < 0) {
LOG_ERR("Cannot attach callback to sw0.");
}
gpio_init_callback(&sw0_cb, sw0_callback, BIT(sw0.pin)); // populate CB struct with information about the CB function and pin
gpio_add_callback_dt(sw0, &sw0_cb); // associate callback with GPIO pin
// test for the callback event state in your code...
while () {
if (sw0_event) {
do_something_less_trivial(); // this can take more time than the callback function
sw0_event = 0;
}
}
If you want a button to have multiple functionalities, you can do so by changing the callback associated with the button.
Next are the steps to set up a button with 2 different callbacks.
// declare second callback function
void sw0_callback_2(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
// initialize second GPIO Callback Struct}
static struct gpio_callback sw0_cb_2;
// define second callback function.
void sw0_callback_2(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
different_event = 1;
//This callback now toggles a different event trigger
}
//Associate the second callback function to the second callback struct
gpio_init_callback(&sw0_cb_2, sw0_callback_2, BIT(sw0.pin));
The gpio_callback struct is used to store information about the callback function. This includes the function and the GPIO pin that it is associated with.
Once this is setup, the following syntax will switch the function associated with the button press:
void callback_function(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
if (state == AWAKE) {
state = NEW_STATE_A;
} else (state == SLEEP) {
state = NEW_STATE_B;
}
}
// led is a gpio struct you have already created from the devicetree
// check if interface is ready
if (!device_is_ready(led.port)) {
LOG_ERR("gpio0 interface not ready."); // logging module output
return -1; // exit code that will exit main()
}
// configure GPIO pin
int err;
err = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); // ACTIVE referes to ON, not HIGH
if (err < 0) {
LOG_ERR("Cannot configure GPIO output pin.");
return err;
}
#define SLEEP_TIME_MS 1000
bool led_state = true;
int ret = gpio_pin_toggle_dt(&led);
// can explicitly set with gpio_pin_set_dt(&led, led_state);
if (ret < 0) {
LOG_ERR("Cannot toggle GPIO output pin.");
return ret;
}
led_state = !led_state;
LOG_INF("LED state: %s\n", led_state ? "ON" : "OFF");
k_msleep(SLEEP_TIME_MS); // this is BLOCKING
There are several other interrupt configuration flags that can be used to toggle the interrupt to trigger on falling edge, on both edges, etc.:
How to Debounce Button Inputs in a RTOS