Line data Source code
1 : /*
2 : This program is free software: you can redistribute it and/or modify
3 : it under the terms of the GNU General Public License as published by
4 : the Free Software Foundation, either version 3 of the License, or
5 : (at your option) any later version.
6 :
7 : This program is distributed in the hope that it will be useful,
8 : but WITHOUT ANY WARRANTY; without even the implied warranty of
9 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 : GNU General Public License for more details.
11 :
12 : You should have received a copy of the GNU General Public License
13 : along with this program. If not, see <http://www.gnu.org/licenses/>.
14 : */
15 :
16 :
17 : #include "AP_Button.h"
18 :
19 : #if HAL_BUTTON_ENABLED
20 :
21 : #include <GCS_MAVLink/GCS_MAVLink.h>
22 : #include <GCS_MAVLink/GCS.h>
23 : #include <RC_Channel/RC_Channel.h>
24 :
25 : // very crude debounce method
26 : #define DEBOUNCE_MS 50
27 :
28 : extern const AP_HAL::HAL& hal;
29 :
30 : AP_Button *AP_Button::_singleton;
31 :
32 : const AP_Param::GroupInfo AP_Button::var_info[] = {
33 :
34 : // @Param: ENABLE
35 : // @DisplayName: Enable button reporting
36 : // @Description: This enables the button checking module. When this is disabled the parameters for setting button inputs are not visible
37 : // @Values: 0:Disabled, 1:Enabled
38 : // @User: Advanced
39 : AP_GROUPINFO_FLAGS("ENABLE", 0, AP_Button, enable, 0, AP_PARAM_FLAG_ENABLE),
40 :
41 : // @Param: PIN1
42 : // @DisplayName: First button Pin
43 : // @Description: Digital pin number for first button input. Some common values are given, but see the Wiki's "GPIOs" page for how to determine the pin number for a given autopilot.
44 : // @User: Standard
45 : // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6
46 : // @Range: -1 127
47 : AP_GROUPINFO("PIN1", 1, AP_Button, pin[0], -1),
48 :
49 : // @Param: PIN2
50 : // @DisplayName: Second button Pin
51 : // @Description: Digital pin number for second button input. Some common values are given, but see the Wiki's "GPIOs" page for how to determine the pin number for a given autopilot.
52 : // @User: Standard
53 : // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6
54 : // @Range: -1 127
55 : AP_GROUPINFO("PIN2", 2, AP_Button, pin[1], -1),
56 :
57 : // @Param: PIN3
58 : // @DisplayName: Third button Pin
59 : // @Description: Digital pin number for third button input. Some common values are given, but see the Wiki's "GPIOs" page for how to determine the pin number for a given autopilot.
60 : // @User: Standard
61 : // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6
62 : // @Range: -1 127
63 : AP_GROUPINFO("PIN3", 3, AP_Button, pin[2], -1),
64 :
65 : // @Param: PIN4
66 : // @DisplayName: Fourth button Pin
67 : // @Description: Digital pin number for fourth button input. Some common values are given, but see the Wiki's "GPIOs" page for how to determine the pin number for a given autopilot.
68 : // @User: Standard
69 : // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6
70 : // @Range: -1 127
71 : AP_GROUPINFO("PIN4", 4, AP_Button, pin[3], -1),
72 :
73 : // @Param: REPORT_SEND
74 : // @DisplayName: Report send time
75 : // @Description: The duration in seconds that a BUTTON_CHANGE report is repeatedly sent to the GCS regarding a button changing state. Note that the BUTTON_CHANGE message is MAVLink2 only.
76 : // @User: Standard
77 : // @Range: 0 3600
78 : AP_GROUPINFO("REPORT_SEND", 5, AP_Button, report_send_time, 10),
79 :
80 : // @Param: OPTIONS1
81 : // @DisplayName: Button Pin 1 Options
82 : // @Description: Options for Pin 1. PWM input detects PWM above or below 1800/1200us instead of logic level. If PWM is not detected or is less than 800us or above 2200us the button will interpreted as low. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input.
83 : // @User: Standard
84 : // @Bitmask: 0:PWM Input,1:InvertInput
85 : AP_GROUPINFO("OPTIONS1", 6, AP_Button, options[0], 0),
86 :
87 : // @Param: OPTIONS2
88 : // @DisplayName: Button Pin 2 Options
89 : // @Description: Options for Pin 2. PWM input detects PWM above or below 1800/1200us instead of logic level. If PWM is not detected or is less than 800us or above 2200us the button will interpreted as low. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input.
90 : // @User: Standard
91 : // @Bitmask: 0:PWM Input,1:InvertInput
92 : AP_GROUPINFO("OPTIONS2", 7, AP_Button, options[1], 0),
93 :
94 : // @Param: OPTIONS3
95 : // @DisplayName: Button Pin 3 Options
96 : // @Description: Options for Pin 3. PWM input detects PWM above or below 1800/1200us instead of logic level. If PWM is not detected or is less than 800us or above 2200us the button will interpreted as low. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input.
97 : // @Bitmask: 0:PWM Input,1:InvertInput
98 : AP_GROUPINFO("OPTIONS3", 8, AP_Button, options[2], 0),
99 :
100 : // @Param: OPTIONS4
101 : // @DisplayName: Button Pin 4 Options
102 : // @Description: Options for Pin 4. PWM input detects PWM above or below 1800/1200us instead of logic level. If PWM is not detected or is less than 800us or above 2200us the button will interpreted as low. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input.
103 : // @User: Standard
104 : // @Bitmask: 0:PWM Input,1:InvertInput
105 : AP_GROUPINFO("OPTIONS4", 9, AP_Button, options[3], 0),
106 :
107 : // @Param: FUNC1
108 : // @CopyFieldsFrom: RC1_OPTION
109 : // @DisplayName: Button Pin 1 RC Channel function
110 : // @Description: Auxiliary RC Options function executed on pin change
111 : // @User: Standard
112 : AP_GROUPINFO("FUNC1", 10, AP_Button, pin_func[0], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING),
113 :
114 : // @Param: FUNC2
115 : // @CopyFieldsFrom: BTN_FUNC1
116 : // @DisplayName: Button Pin 2 RC Channel function
117 : AP_GROUPINFO("FUNC2", 11, AP_Button, pin_func[1], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING),
118 :
119 : // @Param: FUNC3
120 : // @CopyFieldsFrom: BTN_FUNC1
121 : // @DisplayName: Button Pin 3 RC Channel function
122 : AP_GROUPINFO("FUNC3", 12, AP_Button, pin_func[2], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING),
123 :
124 : // @Param: FUNC4
125 : // @CopyFieldsFrom: BTN_FUNC1
126 : // @DisplayName: Button Pin 4 RC Channel function
127 : AP_GROUPINFO("FUNC4", 13, AP_Button, pin_func[3], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING),
128 :
129 : AP_GROUPEND
130 : };
131 :
132 :
133 : // constructor
134 35666 : AP_Button::AP_Button(void)
135 : {
136 2098 : AP_Param::setup_object_defaults(this, var_info);
137 :
138 2098 : if (_singleton != nullptr) {
139 0 : AP_HAL::panic("AP_Button must be singleton");
140 : }
141 2098 : _singleton = this;
142 2098 : }
143 :
144 : /*
145 : update and report, called from main loop
146 : */
147 715806 : void AP_Button::update(void)
148 : {
149 715806 : if (!enable) {
150 715072 : return;
151 : }
152 :
153 : // call setup pins at update rate (5Hz) to allow for runtime parameter change of pins
154 734 : setup_pins();
155 :
156 734 : if (!initialised) {
157 7 : initialised = true;
158 :
159 : // get initial mask
160 7 : last_mask = get_mask();
161 7 : debounce_mask = last_mask;
162 :
163 : // register 1kHz timer callback
164 7 : hal.scheduler->register_timer_process(FUNCTOR_BIND_MEMBER(&AP_Button::timer_update, void));
165 : }
166 :
167 : // act on any changes in state
168 : {
169 734 : WITH_SEMAPHORE(last_debounced_change_ms_sem);
170 734 : if (last_debounced_change_ms > last_debounce_ms) {
171 16 : last_debounce_ms = last_debounced_change_ms;
172 : }
173 734 : }
174 :
175 : // update the PWM state:
176 734 : uint8_t new_pwm_state = pwm_state;
177 3670 : for (uint8_t i=0; i<AP_BUTTON_NUM_PINS; i++) {
178 2936 : const uint8_t mask = (1U << i);
179 2936 : if (!is_pwm_input(i)) {
180 : // not a PWM input
181 2936 : new_pwm_state &= ~mask;
182 2936 : continue;
183 : }
184 0 : const uint16_t pwm_us = pwm_pin_source[i].get_pwm_us();
185 0 : if (pwm_us < RC_Channel::RC_MIN_LIMIT_PWM || pwm_us > RC_Channel::RC_MAX_LIMIT_PWM) {
186 : // invalid pulse width, trigger low
187 0 : if (pwm_state & mask) {
188 0 : new_pwm_state &= ~mask;
189 : }
190 0 : continue;
191 : }
192 : // these values are the same as used in RC_Channel:
193 0 : if (pwm_state & mask) {
194 : // currently asserted; check to see if we should de-assert
195 0 : if (pwm_us < RC_Channel::AUX_SWITCH_PWM_TRIGGER_LOW) {
196 0 : new_pwm_state &= ~mask;
197 : }
198 : } else {
199 : // currently not asserted; check to see if we should assert
200 0 : if (pwm_us > RC_Channel::AUX_SWITCH_PWM_TRIGGER_HIGH) {
201 0 : new_pwm_state |= mask;
202 : }
203 : }
204 : }
205 734 : const uint64_t now_ms = AP_HAL::millis64();
206 734 : if (new_pwm_state != pwm_state) {
207 0 : if (new_pwm_state != tentative_pwm_state) {
208 0 : tentative_pwm_state = new_pwm_state;
209 0 : pwm_start_debounce_ms = now_ms;
210 0 : } else if (now_ms - pwm_start_debounce_ms > DEBOUNCE_MS) {
211 0 : pwm_state = new_pwm_state;
212 0 : last_debounce_ms = now_ms;
213 : }
214 : } else {
215 734 : tentative_pwm_state = pwm_state;
216 734 : pwm_start_debounce_ms = now_ms;
217 : }
218 :
219 : #if HAL_GCS_ENABLED
220 2086 : if (last_debounce_ms != 0 &&
221 1067 : (AP_HAL::millis() - last_report_ms) > AP_BUTTON_REPORT_PERIOD_MS &&
222 333 : (AP_HAL::millis64() - last_debounce_ms) < report_send_time*1000ULL) {
223 : // send a change report
224 63 : last_report_ms = AP_HAL::millis();
225 :
226 : // send a report to GCS
227 63 : send_report();
228 : }
229 : #endif
230 :
231 734 : if (!aux_functions_initialised) {
232 7 : run_aux_functions(true);
233 7 : aux_functions_initialised = true;
234 : }
235 :
236 734 : if (last_debounce_ms != 0 &&
237 618 : last_debounce_ms != last_action_time_ms) {
238 16 : last_action_time_ms = last_debounce_ms;
239 16 : run_aux_functions(false);
240 : }
241 : }
242 :
243 23 : void AP_Button::run_aux_functions(bool force)
244 : {
245 23 : RC_Channel *rc_channel = rc().channel(1);
246 23 : if (rc_channel == nullptr) {
247 0 : return;
248 : }
249 :
250 115 : for (uint8_t i=0; i<AP_BUTTON_NUM_PINS; i++) {
251 92 : const RC_Channel::AUX_FUNC func = RC_Channel::AUX_FUNC(pin_func[i].get());
252 92 : if (func == RC_Channel::AUX_FUNC::DO_NOTHING) {
253 80 : continue;
254 : }
255 12 : const uint8_t value_mask = (1U<<i);
256 : bool value;
257 12 : if (is_pwm_input(i)) {
258 0 : value = (pwm_state & value_mask) != 0;
259 : } else {
260 12 : value = (debounce_mask & value_mask) != 0;
261 : }
262 12 : if (is_input_inverted(i)) {
263 4 : value = !value;
264 : }
265 12 : const bool actioned = ((state_actioned_mask & value_mask) != 0);
266 12 : if (!force && value == actioned) {
267 : // no change on this pin
268 0 : continue;
269 : }
270 : // mark action as done:
271 12 : if (value) {
272 7 : state_actioned_mask |= value_mask;
273 : } else {
274 5 : state_actioned_mask &= ~value_mask;
275 : }
276 :
277 12 : const RC_Channel::AuxSwitchPos pos = value ? RC_Channel::AuxSwitchPos::HIGH : RC_Channel::AuxSwitchPos::LOW;
278 : // I wonder if we can do better here:
279 : #if AP_RC_CHANNEL_AUX_FUNCTION_STRINGS_ENABLED
280 12 : const char *str = rc_channel->string_for_aux_function(func);
281 12 : if (str != nullptr) {
282 4 : GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Button %i: executing (%s %s)", i+1, str, rc_channel->string_for_aux_pos(pos));
283 : }
284 : #endif
285 12 : rc_channel->run_aux_function(func, pos, RC_Channel::AuxFuncTrigger::Source::BUTTON, i);
286 : }
287 : }
288 :
289 : // get state of a button
290 : // used by scripting
291 0 : bool AP_Button::get_button_state(uint8_t number)
292 : {
293 : // pins params are 1 indexed not zero
294 0 : if (number == 0 || number > AP_BUTTON_NUM_PINS) {
295 0 : return false;
296 : }
297 :
298 0 : if (is_pwm_input(number-1)) {
299 0 : return (pwm_state & (1U<<(number-1)));
300 : }
301 :
302 0 : return ( ((1 << (number - 1)) & debounce_mask) != 0);
303 : };
304 :
305 : /*
306 : get current mask
307 : */
308 176018 : uint8_t AP_Button::get_mask(void)
309 : {
310 176018 : uint8_t mask = 0;
311 880090 : for (uint8_t i=0; i<AP_BUTTON_NUM_PINS; i++) {
312 704072 : if (pin[i] == -1) {
313 528817 : continue;
314 : }
315 175255 : if (is_pwm_input(i)) {
316 0 : continue;
317 : }
318 175255 : mask |= hal.gpio->read(pin[i]) << i;
319 : }
320 :
321 176018 : return mask;
322 : }
323 :
324 : /*
325 : called at 1kHz to check for button state change
326 : */
327 247749 : void AP_Button::timer_update(void)
328 : {
329 247749 : if (!enable) {
330 71738 : return;
331 : }
332 176011 : uint8_t mask = get_mask();
333 176011 : uint64_t now = AP_HAL::millis64();
334 176011 : if (mask != last_mask) {
335 16 : last_mask = mask;
336 16 : last_change_time_ms = now;
337 : }
338 176011 : if (debounce_mask != last_mask &&
339 997 : (now - last_change_time_ms) > DEBOUNCE_MS) {
340 : // crude de-bouncing, debounces all buttons as one, not individually
341 16 : debounce_mask = last_mask;
342 16 : WITH_SEMAPHORE(last_debounced_change_ms_sem);
343 16 : last_debounced_change_ms = now;
344 16 : }
345 : }
346 :
347 : #if HAL_GCS_ENABLED
348 : /*
349 : send a BUTTON_CHANGE report to the GCS
350 : */
351 63 : void AP_Button::send_report(void) const
352 : {
353 63 : const uint8_t mask = last_mask | pwm_state;
354 : const mavlink_button_change_t packet{
355 63 : time_boot_ms: AP_HAL::millis(),
356 63 : last_change_ms: uint32_t(last_debounce_ms),
357 : state: mask,
358 63 : };
359 63 : gcs().send_to_active_channels(MAVLINK_MSG_ID_BUTTON_CHANGE,
360 : (const char *)&packet);
361 63 : }
362 : #endif
363 :
364 : /*
365 : setup the pins as input with pullup. We need pullup to give reliable
366 : input with a pulldown button
367 : */
368 734 : void AP_Button::setup_pins(void)
369 : {
370 3670 : for (uint8_t i=0; i<AP_BUTTON_NUM_PINS; i++) {
371 2936 : if (is_pwm_input(i)) {
372 0 : pwm_pin_source[i].set_pin(pin[i], "Button");
373 0 : continue;
374 : }
375 2936 : if (pin[i] == -1) {
376 2206 : continue;
377 : }
378 :
379 730 : hal.gpio->pinMode(pin[i], HAL_GPIO_INPUT);
380 : // setup pullup
381 730 : hal.gpio->write(pin[i], 1);
382 : }
383 734 : }
384 :
385 : // check settings are valid
386 66481 : bool AP_Button::arming_checks(size_t buflen, char *buffer) const
387 : {
388 66481 : if (!enable) {
389 66223 : return true;
390 : }
391 1290 : for (uint8_t i=0; i<AP_BUTTON_NUM_PINS; i++) {
392 1032 : if (pin[i] != -1 && !hal.gpio->valid_pin(pin[i])) {
393 : uint8_t servo_ch;
394 0 : if (hal.gpio->pin_to_servo_channel(pin[i], servo_ch)) {
395 0 : hal.util->snprintf(buffer, buflen, "BTN_PIN%u=%d, set SERVO%u_FUNCTION=-1", unsigned(i + 1), int(pin[i].get()), unsigned(servo_ch+1));
396 : } else {
397 0 : hal.util->snprintf(buffer, buflen, "BTN_PIN%u=%d invalid", unsigned(i + 1), int(pin[i].get()));
398 : }
399 0 : return false;
400 : }
401 : }
402 258 : return true;
403 : }
404 :
405 : namespace AP {
406 :
407 66481 : AP_Button &button()
408 : {
409 66481 : return *AP_Button::get_singleton();
410 : }
411 :
412 : }
413 :
414 : #endif
|