LCOV - code coverage report
Current view: top level - AP_Button - AP_Button.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 107 140 76.4 %
Date: 2026-09-09 11:02:08 Functions: 9 10 90.0 %

          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

Generated by: LCOV version 1.14