/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "AP_Button.h" #if HAL_BUTTON_ENABLED #include #include // very crude debounce method #define DEBOUNCE_MS 50 extern const AP_HAL::HAL& hal; AP_Button *AP_Button::_singleton; const AP_Param::GroupInfo AP_Button::var_info[] = { // @Param: ENABLE // @DisplayName: Enable button reporting // @Description: This enables the button checking module. When this is disabled the parameters for setting button inputs are not visible // @Values: 0:Disabled, 1:Enabled // @User: Advanced AP_GROUPINFO_FLAGS("ENABLE", 0, AP_Button, enable, 0, AP_PARAM_FLAG_ENABLE), // @Param: PIN1 // @DisplayName: First button Pin // @Description: Digital pin number for first button input. // @User: Standard // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6 AP_GROUPINFO("PIN1", 1, AP_Button, pin[0], -1), // @Param: PIN2 // @DisplayName: Second button Pin // @Description: Digital pin number for second button input. // @User: Standard // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6 AP_GROUPINFO("PIN2", 2, AP_Button, pin[1], -1), // @Param: PIN3 // @DisplayName: Third button Pin // @Description: Digital pin number for third button input. // @User: Standard // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6 AP_GROUPINFO("PIN3", 3, AP_Button, pin[2], -1), // @Param: PIN4 // @DisplayName: Fourth button Pin // @Description: Digital pin number for fourth button input. // @User: Standard // @Values: -1:Disabled,50:AUXOUT1,51:AUXOUT2,52:AUXOUT3,53:AUXOUT4,54:AUXOUT5,55:AUXOUT6 AP_GROUPINFO("PIN4", 4, AP_Button, pin[3], -1), // @Param: REPORT_SEND // @DisplayName: Report send time // @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. // @User: Standard // @Range: 0 3600 AP_GROUPINFO("REPORT_SEND", 5, AP_Button, report_send_time, 10), // @Param: OPTIONS1 // @DisplayName: Button Pin 1 Options // @Description: Options for Pin 1. PWM input detects PWM above or below 1800/1200us instead of logic level. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input. // @User: Standard // @Bitmask: 0:PWM Input,1:InvertInput AP_GROUPINFO("OPTIONS1", 6, AP_Button, options[0], 0), // @Param: OPTIONS2 // @DisplayName: Button Pin 2 Options // @Description: Options for Pin 2. PWM input detects PWM above or below 1800/1200us instead of logic level. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input. // @User: Standard // @Bitmask: 0:PWM Input,1:InvertInput AP_GROUPINFO("OPTIONS2", 7, AP_Button, options[1], 0), // @Param: OPTIONS3 // @DisplayName: Button Pin 3 Options // @Description: Options for Pin 3. PWM input detects PWM above or below 1800/1200us instead of logic level. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input. // @Bitmask: 0:PWM Input,1:InvertInput AP_GROUPINFO("OPTIONS3", 8, AP_Button, options[2], 0), // @Param: OPTIONS4 // @DisplayName: Button Pin 4 Options // @Description: Options for Pin 4. PWM input detects PWM above or below 1800/1200us instead of logic level. Invert changes HIGH state to be logic low voltage on pin, or below 1200us, if PWM input. // @User: Standard // @Bitmask: 0:PWM Input,1:InvertInput AP_GROUPINFO("OPTIONS4", 9, AP_Button, options[3], 0), // @Param: FUNC1 // @DisplayName: Button Pin 1 RC Channel function // @Description: Auxiliary RC Options function executed on pin change // @CopyValuesFrom: RC1_OPTION // @User: Standard AP_GROUPINFO("FUNC1", 10, AP_Button, pin_func[0], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING), // @Param: FUNC2 // @DisplayName: Button Pin 2 RC Channel function // @Description: Auxiliary RC Options function executed on pin change // @CopyValuesFrom: RC1_OPTION // @User: Standard AP_GROUPINFO("FUNC2", 11, AP_Button, pin_func[1], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING), // @Param: FUNC3 // @DisplayName: Button Pin 3 RC Channel function // @Description: Auxiliary RC Options function executed on pin change // @CopyValuesFrom: RC1_OPTION // @User: Standard AP_GROUPINFO("FUNC3", 12, AP_Button, pin_func[2], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING), // @Param: FUNC4 // @DisplayName: Button Pin 4 RC Channel function // @Description: Auxiliary RC Options function executed on pin change // @CopyValuesFrom: RC1_OPTION // @User: Standard AP_GROUPINFO("FUNC4", 13, AP_Button, pin_func[3], (uint16_t)RC_Channel::AUX_FUNC::DO_NOTHING), AP_GROUPEND }; // constructor AP_Button::AP_Button(void) { AP_Param::setup_object_defaults(this, var_info); if (_singleton != nullptr) { AP_HAL::panic("AP_Button must be singleton"); } _singleton = this; } /* update and report, called from main loop */ void AP_Button::update(void) { if (!enable) { return; } // call setup pins at update rate (5Hz) to allow for runtime parameter change of pins setup_pins(); if (!initialised) { initialised = true; // get initial mask last_mask = get_mask(); debounce_mask = last_mask; // register 1kHz timer callback hal.scheduler->register_timer_process(FUNCTOR_BIND_MEMBER(&AP_Button::timer_update, void)); } // act on any changes in state { WITH_SEMAPHORE(last_debounced_change_ms_sem); if (last_debounced_change_ms > last_debounce_ms) { last_debounce_ms = last_debounced_change_ms; } } // update the PWM state: uint8_t new_pwm_state = pwm_state; for (uint8_t i=0; i RC_Channel::AUX_SWITCH_PWM_TRIGGER_HIGH) { new_pwm_state |= mask; } } } const uint64_t now_ms = AP_HAL::millis64(); if (new_pwm_state != pwm_state) { if (new_pwm_state != tentative_pwm_state) { tentative_pwm_state = new_pwm_state; pwm_start_debounce_ms = now_ms; } else if (now_ms - pwm_start_debounce_ms > DEBOUNCE_MS) { pwm_state = new_pwm_state; last_debounce_ms = now_ms; } } else { tentative_pwm_state = pwm_state; pwm_start_debounce_ms = now_ms; } if (last_debounce_ms != 0 && (AP_HAL::millis() - last_report_ms) > AP_BUTTON_REPORT_PERIOD_MS && (AP_HAL::millis64() - last_debounce_ms) < report_send_time*1000ULL) { // send a change report last_report_ms = AP_HAL::millis(); // send a report to GCS send_report(); } if (!aux_functions_initialised) { run_aux_functions(true); aux_functions_initialised = true; } if (last_debounce_ms != 0 && last_debounce_ms != last_action_time_ms) { last_action_time_ms = last_debounce_ms; run_aux_functions(false); } } void AP_Button::run_aux_functions(bool force) { RC_Channel *rc_channel = rc().channel(1); if (rc_channel == nullptr) { return; } for (uint8_t i=0; istring_for_aux_function(func); if (str != nullptr) { gcs().send_text(MAV_SEVERITY_INFO, "Button: executing (%s)", str); } #endif rc_channel->run_aux_function(func, pos, RC_Channel::AuxFuncTriggerSource::BUTTON); } } // get state of a button // used by scripting bool AP_Button::get_button_state(uint8_t number) { // pins params are 1 indexed not zero if (number == 0 || number > AP_BUTTON_NUM_PINS) { return false; } if (is_pwm_input(number-1)) { return (pwm_state & (1U<<(number-1))); } return ( ((1 << (number - 1)) & debounce_mask) != 0); }; /* get current mask */ uint8_t AP_Button::get_mask(void) { uint8_t mask = 0; for (uint8_t i=0; iread(pin[i]) << i; } return mask; } /* called at 1kHz to check for button state change */ void AP_Button::timer_update(void) { if (!enable) { return; } uint8_t mask = get_mask(); uint64_t now = AP_HAL::millis64(); if (mask != last_mask) { last_mask = mask; last_change_time_ms = now; } if (debounce_mask != last_mask && (now - last_change_time_ms) > DEBOUNCE_MS) { // crude de-bouncing, debounces all buttons as one, not individually debounce_mask = last_mask; WITH_SEMAPHORE(last_debounced_change_ms_sem); last_debounced_change_ms = now; } } /* send a BUTTON_CHANGE report to the GCS */ void AP_Button::send_report(void) const { const uint8_t mask = last_mask | pwm_state; const mavlink_button_change_t packet{ time_boot_ms: AP_HAL::millis(), last_change_ms: uint32_t(last_debounce_ms), state: mask, }; gcs().send_to_active_channels(MAVLINK_MSG_ID_BUTTON_CHANGE, (const char *)&packet); } /* setup the pins as input with pullup. We need pullup to give reliable input with a pulldown button */ void AP_Button::setup_pins(void) { for (uint8_t i=0; ipinMode(pin[i], HAL_GPIO_INPUT); // setup pullup hal.gpio->write(pin[i], 1); } } // check settings are valid bool AP_Button::arming_checks(size_t buflen, char *buffer) const { if (!enable) { return true; } for (uint8_t i=0; ivalid_pin(pin[i])) { uint8_t servo_ch; if (hal.gpio->pin_to_servo_channel(pin[i], servo_ch)) { 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)); } else { hal.util->snprintf(buffer, buflen, "BTN_PIN%u=%d invalid", unsigned(i + 1), int(pin[i].get())); } return false; } } return true; } namespace AP { AP_Button &button() { return *AP_Button::get_singleton(); } } #endif