Skip to content

Commit fd48bd5

Browse files
committed
Button press callback sample module
This commit pushes a new sample module that demonstrates to use a callback mechanism when user presses the button. The callback should be implemented in the user application code, the callback's 3 parameters would represent the timestamp when the button was pressed (in millisecs), the duration of how long the button was held (also in millisecs) and the type of the button press as an enum value: normal or timeout. The 'timeout' button type indicates that the user had held the button down for longer than a pre-configured duration (e.g, to factory reset the board). This module also has implementation that the user can de-init and re-configure the tiemout duration and/or the callback function address. Signed-off-by: YHusain1 <[email protected]>
1 parent 22ae1d1 commit fd48bd5

File tree

7 files changed

+457
-0
lines changed

7 files changed

+457
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) 2023 T-Mobile USA, Inc.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
cmake_minimum_required(VERSION 3.20.0)
6+
7+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
8+
project(button_press_cb)
9+
10+
target_sources(app PRIVATE src/main.c)
11+
target_sources(app PRIVATE src/button.c)

samples/button_press_cb/Kconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2022 T-Mobile USA, Inc.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
mainmenu "Button Sample DevEdge board V04 Production"
6+
7+
config BUTTON_DEBOUNCE_TIMEOUT_MS
8+
int "Debounce filter timeout for button press in millisec"
9+
range 0 100
10+
default 50
11+
help
12+
Set this to configure timer value in order to debounce button press
13+
and unpress.
14+
15+
source "Kconfig.zephyr"

samples/button_press_cb/README.txt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Title: button_press_cb
2+
3+
Description:
4+
5+
A sample to demonstrate a callback feature of the user push button on the DevEdge kit
6+
The user will implement the Button callback function in the application code.
7+
It gives the timestamp of the button pressed (from system uptime in millisecs), the duration of
8+
how long the button was pressed (also in millisecs), and the type of button press, whether a normal or a timeout.
9+
10+
The duration parameter will be the max timeout value when a timeout occurs, this won't represent
11+
the real physical button press duration though. The callback will be invoked as soon as the
12+
timeout reaches even if the user has not release the button.
13+
14+
--------------------------------------------------------------------------------
15+
16+
Requirements
17+
************
18+
19+
-A T-Mobile DevEdge dev kit (https://devedge.t-mobile.com/)
20+
-A Zephyr build environment (https://docs.zephyrproject.org/latest/develop/getting_started/index.html)
21+
22+
Building and Running Project:
23+
24+
How this project can be built:
25+
26+
-Checkout the T-Mobile downstream zephyr repo:
27+
cd ~/zephyrproject
28+
git clone https://github.com/tmobile/DevEdge-IoTDevKit-ZephyrRTOS zephyr
29+
30+
-Checkout the T-Mobile zephyr-tmo-sdk repo:
31+
cd ~/zephyrproject
32+
git clone https://github.com/tmobile/DevEdge-IoTDevKit-ZephyrSDK tmo-zephyr-sdk
33+
34+
-Run 'west update'
35+
cd ~/zephyrproject
36+
west update
37+
38+
-Build button_press_cb:
39+
cd ~/zephyrproject
40+
west build ~/zephyrproject/tmo-zephyr-sdk/samples/button_press_cb -p -b tmo_dev_edge -- -DBOARD_ROOT=<home folder>/zephyrproject/tmo-zephyr-sdk
41+
(substitute your home folder for '<home folder>' in the command above)
42+
43+
-Connect DevEdge dev kit:
44+
For DevEdge dev kits running tmo_shell built with DevEdge SDK v1.9.1 or earlier:
45+
connect BOTH USB-C ports to your computer or wall power
46+
For DevEdge dev kits running tmo_shell built with DevEdge SDK v1.10.0 or later:
47+
connect only the USB-C port furthest from the button to your computer
48+
(both USB-C ports can be connected, but only one needs to be connected)
49+
50+
-Flash button_press_cb:
51+
cd ~/zephyrproject
52+
west flash
53+
54+
Sample output:
55+
```
56+
*** Booting Zephyr OS build ccdf94636486 ***
57+
58+
59+
Welcome to T-Mobile DevEdge!
60+
61+
This application aims to demonstrate the button press callback sample module which
62+
uses SW0 interrupt pin, which is connected to the user pushbutton switch of the
63+
DevEdge module.
64+
65+
Set up button at gpio@4000a030 pin 13
66+
67+
factory_reset_callback: Button pressed: timestamp: 2562; duration: 10000, button press type: BUTTON_PRESS_TIMEOUT
68+
Set up button at gpio@4000a030 pin 13
69+
70+
factory_reset_callback: Button pressed: timestamp: 14983; duration: 159, button press type: BUTTON_PRESS_NORMAL
71+
factory_reset_callback: Button pressed: timestamp: 15920; duration: 137, button press type: BUTTON_PRESS_NORMAL
72+
factory_reset_callback: Button pressed: timestamp: 16690; duration: 5000, button press type: BUTTON_PRESS_TIMEOUT
73+
factory_reset_callback: Button pressed: timestamp: 22771; duration: 139, button press type: BUTTON_PRESS_NORMAL
74+
factory_reset_callback: Button pressed: timestamp: 23757; duration: 179, button press type: BUTTON_PRESS_NORMAL
75+
```
76+

samples/button_press_cb/prj.conf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2022 T-Mobile USA, Inc.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Required to disable default behavior of deep sleep on timeout
6+
CONFIG_GPIO=y
7+
8+
# Stack sizes
9+
CONFIG_MAIN_STACK_SIZE=4096
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright (c) 2023 T-Mobile USA, Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/device.h>
8+
#include <zephyr/drivers/gpio.h>
9+
#include <zephyr/kernel.h>
10+
#include <zephyr/sys/printk.h>
11+
12+
#include "button.h"
13+
14+
#define SW0_NODE DT_ALIAS(sw0)
15+
16+
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
17+
static struct gpio_callback button_cb_data;
18+
static button_press_callback_t registered_callback;
19+
static struct gpio_callback button_cb_data;
20+
button_press_state button_state = BUTTON_STATE_DEINIT;
21+
struct k_timer debounce_timer;
22+
struct k_timer timeout_timer;
23+
static int64_t last_button_press_time;
24+
static int64_t button_press_duration;
25+
static int64_t max_button_press_timeout;
26+
27+
/* button GPIO interrupt handler */
28+
static void button_press_handler(const struct device *port, struct gpio_callback *cb,
29+
gpio_port_pins_t pins)
30+
{
31+
k_timer_stop(&debounce_timer);
32+
k_timer_start(&debounce_timer, K_MSEC(50), K_NO_WAIT);
33+
button_state = BUTTON_STATE_DEBOUNCING;
34+
}
35+
36+
static void button_timeout_handler(struct k_timer *p_timeout_timer)
37+
{
38+
button_state = BUTTON_STATE_TIMEOUT;
39+
if (NULL != registered_callback) {
40+
/* saving the last button press time, since k_uptime_delta updates it with current
41+
* system uptime, which is undesired behavior in the context of measuring the real
42+
* button press duration even if timeout has occurred */
43+
int64_t save_last_button_press = last_button_press_time;
44+
45+
button_press_duration = k_uptime_delta(&last_button_press_time);
46+
last_button_press_time = save_last_button_press;
47+
/* Invoke the registered user callback handler */
48+
registered_callback(last_button_press_time, button_press_duration,
49+
BUTTON_PRESS_TIMEOUT);
50+
}
51+
k_timer_stop(p_timeout_timer);
52+
}
53+
54+
/* debounce timer handler */
55+
static void button_debounce_timer_handler(struct k_timer *p_debounce_timer)
56+
{
57+
/* As soon as a button press action (hold or release) has occurred, we don't need the
58+
* timeout timer enabled anymore, until the next button event */
59+
k_timer_stop(&timeout_timer);
60+
61+
if (BUTTON_STATE_DEINIT == button_state) {
62+
return;
63+
}
64+
button_state = BUTTON_STATE_DEBOUNCED;
65+
/* Each the button is pressed, last_button_press_time will be 0, indicating a new action on
66+
* user button */
67+
if (last_button_press_time == 0) {
68+
/* button press detected */
69+
last_button_press_time = k_uptime_get();
70+
k_timer_start(&timeout_timer, K_MSEC(max_button_press_timeout), K_NO_WAIT);
71+
} else if (registered_callback != NULL) {
72+
/* button release detected */
73+
74+
/* This basically compares the timestamps of each time the timer handler is called
75+
* (one on button press, another on button release )*/
76+
button_press_duration = k_uptime_delta(&last_button_press_time);
77+
78+
/* Depending on whether the time delta exceeds the configured max timeout value set
79+
* during init function, the button press type is evaluated */
80+
if (button_state != BUTTON_STATE_TIMEOUT &&
81+
button_press_duration < max_button_press_timeout) {
82+
/* Invoke the registered user callback handler */
83+
registered_callback(last_button_press_time, button_press_duration,
84+
BUTTON_PRESS_NORMAL);
85+
}
86+
87+
/* Reset last_button_press_time to capture next button press event */
88+
last_button_press_time = 0;
89+
90+
button_state = BUTTON_STATE_INIT;
91+
}
92+
}
93+
94+
int button_press_module_init(uint32_t max_timeout, button_press_callback_t callback)
95+
{
96+
/* Cannot initialize new paramters of button press without calling deinit first */
97+
if (BUTTON_STATE_DEINIT < button_state) {
98+
return -EBUSY;
99+
}
100+
/* Invalid params */
101+
if (0 == max_timeout || NULL == callback) {
102+
return -EINVAL;
103+
}
104+
105+
int ret = 0;
106+
107+
max_button_press_timeout = max_timeout;
108+
registered_callback = callback;
109+
110+
ret = gpio_pin_configure_dt(&button, GPIO_INPUT | GPIO_PULL_UP);
111+
if (ret != 0) {
112+
printk("Error %d: failed to configure %s pin %d\n", ret, button.port->name,
113+
button.pin);
114+
button_state = BUTTON_STATE_ERROR;
115+
return ret;
116+
}
117+
118+
/* Configrue interrputs on both edges so we can sample timestamp to caluclate duration of
119+
* button press */
120+
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_BOTH);
121+
if (ret != 0) {
122+
printk("Error %d: failed to configure interrupt on %s pin %d\n", ret,
123+
button.port->name, button.pin);
124+
button_state = BUTTON_STATE_ERROR;
125+
return ret;
126+
}
127+
gpio_init_callback(&button_cb_data, button_press_handler, BIT(button.pin));
128+
gpio_add_callback_dt(&button, &button_cb_data);
129+
printk("Set up button at %s pin %d\n\n", button.port->name, button.pin);
130+
131+
k_timer_init(&debounce_timer, button_debounce_timer_handler, NULL);
132+
k_timer_init(&timeout_timer, button_timeout_handler, NULL);
133+
134+
/* Set state of button as configured. Only one configruation can exist at a time */
135+
button_state = BUTTON_STATE_INIT;
136+
137+
return ret;
138+
}
139+
140+
int button_press_module_deinit(void)
141+
{
142+
if (BUTTON_STATE_DEINIT == button_state) {
143+
return 0;
144+
}
145+
146+
int ret = 0;
147+
148+
max_button_press_timeout = 0;
149+
registered_callback = NULL;
150+
151+
/* Disconnect the GPIO from the MCU */
152+
ret = gpio_pin_configure_dt(&button, GPIO_DISCONNECTED);
153+
if (ret != 0) {
154+
printk("Error %d: failed to disconnect %s pin %d\n", ret, button.port->name,
155+
button.pin);
156+
button_state = BUTTON_STATE_ERROR;
157+
return ret;
158+
}
159+
160+
/* Disable all interrupts on it */
161+
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_DISABLE);
162+
if (ret != 0) {
163+
printk("Error %d: failed to disable interrupt on %s pin %d\n", ret,
164+
button.port->name, button.pin);
165+
button_state = BUTTON_STATE_ERROR;
166+
return ret;
167+
}
168+
169+
gpio_remove_callback_dt(&button, &button_cb_data);
170+
if (ret != 0) {
171+
printk("Error %d: failed to remove intr callback on %s pin %d\n", ret,
172+
button.port->name, button.pin);
173+
button_state = BUTTON_STATE_ERROR;
174+
return ret;
175+
}
176+
177+
k_timer_stop(&debounce_timer);
178+
k_timer_stop(&timeout_timer);
179+
180+
/* Set the button state to undefined so a new configruation can be set by calling the init
181+
* function */
182+
button_state = BUTTON_STATE_DEINIT;
183+
184+
return ret;
185+
}
186+
187+
button_press_state button_press_get_state(void)
188+
{
189+
/* Return current state of button */
190+
return button_state;
191+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2023 T-Mobile USA, Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#ifndef BUTTON_H
7+
#define BUTTON_H
8+
9+
#include <zephyr/kernel.h>
10+
11+
typedef enum {
12+
BUTTON_PRESS_NORMAL = 0,
13+
BUTTON_PRESS_TIMEOUT = 1
14+
} button_press_type;
15+
16+
typedef enum {
17+
BUTTON_STATE_ERROR = -1,
18+
BUTTON_STATE_DEINIT = 0,
19+
BUTTON_STATE_INIT = 1,
20+
BUTTON_STATE_DEBOUNCING = 2,
21+
BUTTON_STATE_DEBOUNCED = 3,
22+
BUTTON_STATE_TIMEOUT = 4
23+
} button_press_state;
24+
25+
/**
26+
* @brief Button callback definition, the user will implement the callback in the application code.
27+
* It gives the timestamp of the button pressed (from system uptime in millisecs), the duration of
28+
* how long the button was pressed, and the type of button press, whether a normal or a timeout.
29+
*
30+
* The duration parameter will be the max timeout value when a timeout occurs, this won't represent
31+
* the real physical button press duration though. The callback will be invoked as soon as the
32+
* timeout reaches even if the user has not release the button.
33+
*/
34+
typedef void (*button_press_callback_t)(uint32_t timestamp, uint32_t duration,
35+
button_press_type type);
36+
37+
/**
38+
* @brief Button press initialization. Called by user application code to configure and initialize
39+
* user button to generate callback on button presses.
40+
*
41+
* @param max_timeout [in] Max timeout value for which the user button can be pressed, after
42+
* which it is considered to be not normal operation.
43+
* @param callback [in] Function pointer to the callback that will be called when the button
44+
* has been pressed for a certain duration
45+
* @return int status code indicating success/failure.
46+
*/
47+
int button_press_module_init(uint32_t max_timeout, button_press_callback_t callback);
48+
49+
/**
50+
* @brief De-initialize the button interrupt and callbacks if it was previously done so. Usually
51+
* performed if we want to set a new configuration on the button.
52+
*
53+
* @return int status code indicating success/failure
54+
*/
55+
int button_press_module_deinit(void);
56+
57+
/**
58+
* @brief Get the button configured state. Handy getter function that user application code can use
59+
* to determine if existing callback is set before configuring it with new parameters.
60+
*
61+
* @return button_press_state
62+
*/
63+
button_press_state button_press_get_state(void);
64+
65+
#endif

0 commit comments

Comments
 (0)