FreeTRX/MDUV380_firmware/application/source/io/buttons.c
2026-07-06 08:45:33 +02:00

370 lines
12 KiB
C

/*
* 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 <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#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;
}
}