/* * Copyright (C) 2020-2025 Roger Clark, VK3KYY / G4KYF * Daniel Caujolle-Bert, F1RMB * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include "interfaces/adc.h" #include "io/buttons.h" #include "main.h" static uint32_t prevButtonState; static uint32_t mbuttons; volatile bool PTTLocked = false; #define MBUTTON_PRESSED (1 << 0) #define MBUTTON_LONG (1 << 1) #define MBUTTON_EXTRA_LONG (1 << 2) typedef enum { MBUTTON_ORANGE, MBUTTON_SK1, MBUTTON_SK2, MBUTTON_MAX } MBUTTON_t; void buttonsInit(void) { mbuttons = BUTTON_NONE; prevButtonState = BUTTON_NONE; } static bool isMButtonPressed(MBUTTON_t mbutton) { return (((mbuttons >> (mbutton * 3)) & MBUTTON_PRESSED) & MBUTTON_PRESSED); } static bool isMButtonLong(MBUTTON_t mbutton) { return (((mbuttons >> (mbutton * 3)) & MBUTTON_LONG) & MBUTTON_LONG); } static bool isMButtonExtraLong(MBUTTON_t mbutton) { return (((mbuttons >> (mbutton * 3)) & MBUTTON_EXTRA_LONG) & MBUTTON_EXTRA_LONG); } static void setMButtonsStateAndClearLong(uint32_t *buttons, MBUTTON_t mbutton, uint32_t buttonID) { if (*buttons & buttonID) { mbuttons |= (MBUTTON_PRESSED << (mbutton * 3)); } else { mbuttons &= ~(MBUTTON_PRESSED << (mbutton * 3)); } taskENTER_CRITICAL(); switch (mbutton) { case MBUTTON_SK1: case MBUTTON_SK2: case MBUTTON_ORANGE: timer_mbuttons[mbutton] = (*buttons & buttonID) ? (nonVolatileSettings.keypadTimerLong * 100) : 0; break; default: break; } taskEXIT_CRITICAL(); mbuttons &= ~(MBUTTON_LONG << (mbutton * 3)); mbuttons &= ~(MBUTTON_EXTRA_LONG << (mbutton * 3)); } static void checkMButtonState(uint32_t *buttons, MBUTTON_t mbutton, uint32_t buttonID) { if (isMButtonPressed(mbutton) == false) { setMButtonsStateAndClearLong(buttons, mbutton, buttonID); } } uint32_t buttonsRead(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; uint32_t result = BUTTON_NONE; uint32_t portE_pins = #if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) LCD_D5_Pin | #endif #if ! defined(PLATFORM_MD2017) LCD_D6_Pin | #endif LCD_D7_Pin; // VK3KYY // Extra code added to force drive the GPIO pins low, even though the FSMC should have left them in LOW // Possibly errata in the STM silicon when changing from FSMC to GPIO use of the ports seems to be causing a glitch on some radios // Which is momentarily driving the GPIO input pins High prior to them being read, which causes phantom key and button presses HAL_GPIO_WritePin(GPIOE, portE_pins, GPIO_PIN_RESET); GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Pull = GPIO_PULLDOWN; GPIO_InitStruct.Pin = portE_pins; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); for(volatile int xx = 0; xx < 10; xx++); // arbitrary settling delay GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pin = portE_pins; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); // Set ROW2 (K3) in OUTPUT mode, as keyboard code sets it to floating (avoiding Multiple key press combination problems). GPIO_InitStruct.Pin = KEYPAD_ROW2_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(KEYPAD_ROW2_GPIO_Port, &GPIO_InitStruct); //set the row pin high to select that row of keys HAL_GPIO_WritePin(KEYPAD_ROW2_GPIO_Port, KEYPAD_ROW2_Pin, GPIO_PIN_SET); for (volatile int xx = 0; xx < 100; xx++);// arbitrary settling delay #if defined(PLATFORM_MDUV380) if (HAL_GPIO_ReadPin(LCD_D7_GPIO_Port, LCD_D7_Pin) == GPIO_PIN_SET) #elif defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) // Top side button -> ORANGE if (HAL_GPIO_ReadPin(LCD_D5_GPIO_Port, LCD_D5_Pin) == GPIO_PIN_SET) #endif { #if defined(PLATFORM_MD380) result |= BUTTON_SK2; checkMButtonState(&result, MBUTTON_SK2, BUTTON_SK2); #elif defined(PLATFORM_RT84_DM1701) result |= BUTTON_ORANGE; checkMButtonState(&result, MBUTTON_ORANGE, BUTTON_ORANGE); #elif defined(PLATFORM_MD2017) // trackball button - use as SK1 result |= BUTTON_SK1; checkMButtonState(&result, MBUTTON_SK1, BUTTON_SK1); #else result |= BUTTON_SK1; checkMButtonState(&result, MBUTTON_SK1, BUTTON_SK1); #endif } #if !defined(PLATFORM_MD2017) if (HAL_GPIO_ReadPin(LCD_D6_GPIO_Port, LCD_D6_Pin) == GPIO_PIN_SET) { #if defined(PLATFORM_MD380) result |= BUTTON_SK1; checkMButtonState(&result, MBUTTON_SK1, BUTTON_SK1); #else result |= BUTTON_SK2; checkMButtonState(&result, MBUTTON_SK2, BUTTON_SK2); #endif } #endif #if defined(PLATFORM_RT84_DM1701) if (HAL_GPIO_ReadPin(LCD_D7_GPIO_Port, LCD_D7_Pin) == GPIO_PIN_SET) { result |= BUTTON_SK1; checkMButtonState(&result, MBUTTON_SK1, BUTTON_SK1); } #elif defined(PLATFORM_MD2017) if (HAL_GPIO_ReadPin(LCD_D7_GPIO_Port, LCD_D7_Pin) == GPIO_PIN_SET) { result |= BUTTON_SK2; checkMButtonState(&result, MBUTTON_SK2, BUTTON_SK2); } // MD2017 specific hardware button on the main PCB if (HAL_GPIO_ReadPin(BTN_ORANGE_GPIO_Port, BTN_ORANGE_Pin) == GPIO_PIN_RESET) { result |= BUTTON_ORANGE; checkMButtonState(&result, MBUTTON_ORANGE, BUTTON_ORANGE); } #endif //set the row2 pin back to floating. This prevents conflicts between multiple key presses. GPIO_InitStruct.Pin = KEYPAD_ROW2_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(KEYPAD_ROW2_GPIO_Port, &GPIO_InitStruct); if ((HAL_GPIO_ReadPin(PTT_GPIO_Port, PTT_Pin) == GPIO_PIN_RESET) || (HAL_GPIO_ReadPin(PTT_EXTERNAL_GPIO_Port, PTT_EXTERNAL_Pin) == GPIO_PIN_RESET)) { result |= BUTTON_PTT; } return result; } static void checkMButtons(uint32_t *buttons, MBUTTON_t mbutton, uint32_t buttonID, uint32_t buttonShortUp, uint32_t buttonLong, uint32_t buttonExtraLong) { taskENTER_CRITICAL(); uint32_t tmp_timer_mbutton = timer_mbuttons[mbutton]; taskEXIT_CRITICAL(); // Note: Short press are send async if ((*buttons & buttonID) && isMButtonPressed(mbutton) && isMButtonLong(mbutton) && (isMButtonExtraLong(mbutton) == false)) { // button is still down *buttons |= buttonLong; if (tmp_timer_mbutton == 0) { // Long extra long press mbuttons |= (MBUTTON_EXTRA_LONG << (mbutton * 3)); // Clear LONG and set EXTRA_LONG bits *buttons &= ~buttonLong; *buttons |= buttonExtraLong; } } else if ((*buttons & buttonID) && isMButtonPressed(mbutton) && isMButtonLong(mbutton) && isMButtonExtraLong(mbutton)) { // button is still down *buttons |= buttonLong; // Clear LONG and set EXTRA_LONG bits *buttons &= ~buttonLong; *buttons |= buttonExtraLong; } else if ((*buttons & buttonID) && isMButtonPressed(mbutton) && (isMButtonLong(mbutton) == false)) { if (tmp_timer_mbutton == 0) { // Long press mbuttons |= (MBUTTON_LONG << (mbutton * 3)); // Set LONG bit *buttons |= buttonLong; // Reset the timer for extra long down usage taskENTER_CRITICAL(); timer_mbuttons[mbutton] = (((nonVolatileSettings.keypadTimerLong * 3) >> 1) * 100); taskEXIT_CRITICAL(); } } else if (((*buttons & buttonID) == 0) && isMButtonPressed(mbutton) && (isMButtonLong(mbutton) == false) && (tmp_timer_mbutton != 0)) { // Short press/release cycle mbuttons &= ~(MBUTTON_PRESSED << (mbutton * 3)); mbuttons &= ~(MBUTTON_LONG << (mbutton * 3)); mbuttons &= ~(MBUTTON_EXTRA_LONG << (mbutton * 3)); taskENTER_CRITICAL(); timer_mbuttons[mbutton] = 0; taskEXIT_CRITICAL(); // Set SHORT press *buttons |= buttonShortUp; *buttons &= ~buttonLong; *buttons &= ~buttonExtraLong; } else if (((*buttons & buttonID) == 0) && isMButtonPressed(mbutton) && isMButtonLong(mbutton)) { // Button was still down after a long press, now handle release mbuttons &= ~(MBUTTON_PRESSED << (mbutton * 3)); mbuttons &= ~(MBUTTON_LONG << (mbutton * 3)); mbuttons &= ~(MBUTTON_EXTRA_LONG << (mbutton * 3)); // Remove LONG and EXTRA_LONG *buttons &= ~buttonLong; *buttons &= ~buttonExtraLong; } } void buttonsCheckButtonsEvent(uint32_t *buttons, int *event, bool keyIsDown) { *buttons = buttonsRead(); // Handles buttons states if ((*buttons != BUTTON_NONE) || (mbuttons & BUTTON_WAIT_NEW_STATE)) { // A key is down, just leave DOWN bit if (keyIsDown) { mbuttons |= BUTTON_WAIT_NEW_STATE; // Clear stored states setMButtonsStateAndClearLong(buttons, MBUTTON_SK1, BUTTON_SK1); setMButtonsStateAndClearLong(buttons, MBUTTON_SK2, BUTTON_SK2); #if ! (defined(PLATFORM_RD5R) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380)) setMButtonsStateAndClearLong(buttons, MBUTTON_ORANGE, BUTTON_ORANGE); #endif // Won't send a CHANGE event, as the key turns to be a modifier now prevButtonState = *buttons; *event = EVENT_BUTTON_NONE; return; } else { if (mbuttons & BUTTON_WAIT_NEW_STATE) { if (*buttons != prevButtonState) { mbuttons &= ~BUTTON_WAIT_NEW_STATE; // Clear stored states setMButtonsStateAndClearLong(buttons, MBUTTON_SK1, BUTTON_SK1); setMButtonsStateAndClearLong(buttons, MBUTTON_SK2, BUTTON_SK2); #if ! (defined(PLATFORM_RD5R) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380)) setMButtonsStateAndClearLong(buttons, MBUTTON_ORANGE, BUTTON_ORANGE); #endif prevButtonState = *buttons; *event = EVENT_BUTTON_CHANGE; return; } *event = EVENT_BUTTON_NONE; return; } } } // Check state for every single button #if ! (defined(PLATFORM_RD5R) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380)) checkMButtons(buttons, MBUTTON_ORANGE, BUTTON_ORANGE, BUTTON_ORANGE_SHORT_UP, BUTTON_ORANGE_LONG_DOWN, BUTTON_ORANGE_EXTRA_LONG_DOWN); #endif // ! PLATFORM_RD5R checkMButtons(buttons, MBUTTON_SK1, BUTTON_SK1, BUTTON_SK1_SHORT_UP, BUTTON_SK1_LONG_DOWN, BUTTON_SK1_EXTRA_LONG_DOWN); checkMButtons(buttons, MBUTTON_SK2, BUTTON_SK2, BUTTON_SK2_SHORT_UP, BUTTON_SK2_LONG_DOWN, BUTTON_SK2_EXTRA_LONG_DOWN); if (prevButtonState != *buttons) { prevButtonState = *buttons; *event = EVENT_BUTTON_CHANGE; } else { *event = EVENT_BUTTON_NONE; } }