-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathButton.cpp
270 lines (222 loc) · 8.57 KB
/
Button.cpp
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#include "Button.hpp"
//esp-idf includes
#include "esp_timer.h"
#include "esp_debug_helpers.h"
bool Button::isr_service_installed = {false};
Button::Button(button_config_t button_conf, bool logging_en, const char *name):
event(Button::ButtonEvent::quick_press),
button_conf(button_conf),
logging_en(logging_en),
pending_scan(false),
name(name)
{
gpio_config_t gpio_conf;
//error checking:
//check to see if button gpio has been assigned
if(button_conf.gpio_num == GPIO_NUM_NC)
{
//print error and dump backtrace
ESP_LOGE(TAG, "Button not Initialized: GPIO unassigned.");
esp_backtrace_print(10);
}
//check to see if button is correctly assigned as active low or high
else if(button_conf.active_hi != !button_conf.active_lo)
{
//print error and dump backtrace
ESP_LOGE(TAG, "Button not Initialized: Button must be active high or active low, cannot be both.");
esp_backtrace_print(10);
}
//check to see that long-press event generation time is between 0 & 5 seconds
else if(!((button_conf.long_press_evt_time >= 10000) && (button_conf.long_press_evt_time <= 5000000)))
{
//print error and dump backtrace
ESP_LOGE(TAG, "Button not Initialized: Long press event time out of range, must be between 10000us to 5000000us");
esp_backtrace_print(10);
}
//check to see that held event generation time is between 0 & 5 seconds
else if(!((button_conf.held_evt_time >= 10000) && (button_conf.held_evt_time <= 5000000)))
{
//print error and dump backtrace
ESP_LOGE(TAG, "Button not Initialized: Held press event time out of range, must be between 10000us to 5000000us");
esp_backtrace_print(10);
}
else
{
gpio_conf.pin_bit_mask = (0x1 << (uint16_t)button_conf.gpio_num); //assign button gpio number
gpio_conf.mode = GPIO_MODE_INPUT; //set gpio mode as input
//if active high button
if(button_conf.active_hi)
{
gpio_conf.intr_type = GPIO_INTR_POSEDGE; //set positive edge interrupt
//if internal pullups/pulldowns are enabled
if(button_conf.pull_en)
{
//active high, internal pullup disabled, internal pulldown enabled
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
}
else
{
//internal pullups/pulldowns diabled
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
}
}
//if active low button
else if (button_conf.active_lo)
{
gpio_conf.intr_type = GPIO_INTR_NEGEDGE; //set negative edge interrupt
//if internal pullups/pulldowns are enabled
if(button_conf.pull_en)
{
//active low, internal pullup enabled, internal pulldown disabled
gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
}
else
{
//internal pullups/pulldowns diabled
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
}
}
gpio_config(&gpio_conf); //configure gpio pin
if(!isr_service_installed)
{
gpio_install_isr_service(0); //install isr service
isr_service_installed = true;
}
ESP_ERROR_CHECK(gpio_isr_handler_add(button_conf.gpio_num, button_handler, (void *)this)); //add button isr handler
button_task_hdl = NULL;
xTaskCreate(&button_task_trampoline, "button_task", 2048, this, 4, &button_task_hdl);
if(logging_en){
ESP_LOGI(TAG, "ButtonName: %s| GPIO[%2d]| ActiveLow: %s| ActiveHi: %s| InternalPullUpDown: %s| LongPressEvtTime: %lld us| HeldEvtTime: %lld us",
name, (int16_t)button_conf.gpio_num, ((button_conf.active_lo)?"true":"false"), ((button_conf.active_hi)?"true":"false"), ((button_conf.pull_en)?"true":"false"),
button_conf.long_press_evt_time, button_conf.held_evt_time);
}
}
}
bool Button::scan()
{
//if data has not been updated since last time scan() was called
if(!pending_scan)
return false;
//data has been updated since last time scan() was called
else{
pending_scan = false;
return true;
}
}
void Button::button_task(void){
while(1){
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); //block until notified by respective GPIO ISR
vTaskDelay(25/portTICK_PERIOD_MS); //debounce press
//check for press type, if button is not released (long press) then check for release event
if(!press_check())
released_check();
gpio_intr_enable(button_conf.gpio_num); //re-enable interrupts to resume task if button activity is detected
}
}
void Button::button_task_trampoline(void *arg)
{
Button *local_button = (Button *)arg; //cast to button pointer
local_button->button_task(); //launch button task
}
bool Button::press_check()
{
int64_t time_stamp = esp_timer_get_time();
bool press_check = false;
bool released = false;
//press check: look for long-press or quick-press event
while(!press_check)
{
//if button released
if(!get_button_level())
{
//generate quick press event and exit long-press check, and skip release check
press_check = true;
released = true;
generate_quick_press_evt(); //generate quick press event
vTaskDelay(25/portTICK_PERIOD_MS); //release debounce
}
//long-press event time exceeded
else if((esp_timer_get_time() - time_stamp) > button_conf.long_press_evt_time)
{
//generate long press event and exit long-press check, progress to release check
press_check = true;
generate_long_press_evt(); //generate long press event
}
vTaskDelay(15/portTICK_PERIOD_MS);
}
return released;
}
void Button::released_check()
{
bool released_check = false;
int64_t time_stamp = esp_timer_get_time();
//release check: generate held events until release event
while(!released_check)
{
//if button released
if(!get_button_level())
{
//generate release event and exit release check
released_check = true;
generate_released_evt();
vTaskDelay(25/portTICK_PERIOD_MS); //release debounce
}
//if held event generation time exceeded
else if((esp_timer_get_time() - time_stamp) > button_conf.held_evt_time)
{
//reset time stamp generate held event
time_stamp = esp_timer_get_time();
generate_held_evt();
}
vTaskDelay(15/portTICK_PERIOD_MS); //delay for idle task
}
}
bool Button::get_button_level()
{
if(button_conf.active_hi)
return gpio_get_level(button_conf.gpio_num); //if active-high button return true when gpio is high
else if(button_conf.active_lo)
return !gpio_get_level(button_conf.gpio_num); //if active-low button return true when gpio is low
else
return false; //if this statement is executed, user ignored error codes and backtrace dump from constructor
}
void Button::generate_quick_press_evt()
{
event.set(ButtonEvent::quick_press);
pending_scan = true;
if(logging_en)
ESP_LOGI(TAG, "%s -> quick-press", name);
}
void Button::generate_long_press_evt()
{
event.set(ButtonEvent::long_press);
pending_scan = true;
if(logging_en)
ESP_LOGI(TAG, "%s -> long-press", name);
}
void Button::generate_held_evt()
{
event.set(ButtonEvent::held);
pending_scan = true;
if(logging_en)
ESP_LOGI(TAG, "%s -> held", name);
}
void Button::generate_released_evt()
{
event.set(ButtonEvent::released);
pending_scan = true;
if(logging_en)
ESP_LOGI(TAG, "%s -> released", name);
}
void IRAM_ATTR Button::button_handler(void *arg)
{
BaseType_t xHighPriorityTaskWoken = pdFALSE;
Button *button = (Button *)arg;
gpio_intr_disable(button->button_conf.gpio_num); //disable interrupt until re-enabled in button task
vTaskNotifyGiveFromISR(button->button_task_hdl, &xHighPriorityTaskWoken); //notify button task button input was detected
portYIELD_FROM_ISR(xHighPriorityTaskWoken); //perform context switch if necessary
}