mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 11:08:20 +00:00
297 lines
8.9 KiB
C
297 lines
8.9 KiB
C
/* Copyright 2025 muzkr https://github.com/muzkr
|
|
* Copyright 2023 Manuel Jinger
|
|
* Copyright 2023 Dual Tachyon
|
|
* https://github.com/DualTachyon
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "driver/gpio.h"
|
|
#include "driver/keyboard.h"
|
|
#include "driver/systick.h"
|
|
#include "driver/i2c.h"
|
|
#include "misc.h"
|
|
|
|
KEY_Code_t gKeyReading0 = KEY_INVALID;
|
|
KEY_Code_t gKeyReading1 = KEY_INVALID;
|
|
uint16_t gDebounceCounter = 0;
|
|
bool gWasFKeyPressed = false;
|
|
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
// Short press: hold key for SERIAL_KEY_SHORT_POLLS calls.
|
|
// Must exceed key_debounce_10ms (2) to trigger ProcessKey(key, true, false).
|
|
#define SERIAL_KEY_SHORT_POLLS 5
|
|
|
|
// Long press: hold key for SERIAL_KEY_LONG_POLLS calls.
|
|
// Must exceed key_repeat_delay_10ms (40) to trigger ProcessKey(key, true, true).
|
|
#define SERIAL_KEY_LONG_POLLS 45
|
|
|
|
// Packet types for serial key injection (K5Viewer → radio)
|
|
#define SERIAL_KEY_TYPE 0x03
|
|
#define SERIAL_KEY_TYPE_LONG 0x04
|
|
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
|
#define SERIAL_FEATURE_TYPE 0x05
|
|
#endif
|
|
|
|
volatile KEY_Code_t gKeyFromSerial = KEY_INVALID;
|
|
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
|
volatile uint8_t gSerialViewerFeatures = 0;
|
|
#endif
|
|
static uint8_t gSerialKeyHoldCount = 0;
|
|
static uint8_t gSerialKeyLong = 0; // 0 = short press, 1 = long press
|
|
|
|
// Inject a short or long press from serial (UART or VCP).
|
|
// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial.
|
|
static inline void KEYBOARD_InjectKey(uint8_t keyCode, bool keyLong)
|
|
{
|
|
if (keyCode < KEY_INVALID && keyCode != KEY_PTT) {
|
|
gKeyFromSerial = (KEY_Code_t)keyCode;
|
|
gSerialKeyHoldCount = 0;
|
|
gSerialKeyLong = keyLong;
|
|
}
|
|
}
|
|
|
|
bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b)
|
|
{
|
|
bool connected = false;
|
|
|
|
switch (*state)
|
|
{
|
|
case STATE_IDLE:
|
|
if (b == 0x55) *state = STATE_KA_1;
|
|
else if (b == 0xAA) *state = STATE_KEY_1;
|
|
break;
|
|
|
|
case STATE_KA_1:
|
|
*state = (b == 0xAA) ? STATE_KA_2 : STATE_IDLE;
|
|
break;
|
|
|
|
case STATE_KA_2:
|
|
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
|
if (b == 0x00)
|
|
*state = STATE_KA_3;
|
|
else if (b == SERIAL_FEATURE_TYPE)
|
|
*state = STATE_KA_FEATURE;
|
|
else
|
|
*state = STATE_IDLE;
|
|
#else
|
|
*state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE;
|
|
#endif
|
|
break;
|
|
|
|
case STATE_KA_3:
|
|
if (b == 0x00) {
|
|
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
|
gSerialViewerFeatures = 0;
|
|
#endif
|
|
connected = true;
|
|
}
|
|
*state = STATE_IDLE;
|
|
break;
|
|
|
|
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
|
case STATE_KA_FEATURE:
|
|
gSerialViewerFeatures = b;
|
|
connected = true;
|
|
*state = STATE_IDLE;
|
|
break;
|
|
#endif
|
|
|
|
case STATE_KEY_1:
|
|
*state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE;
|
|
break;
|
|
|
|
case STATE_KEY_2:
|
|
if (b == SERIAL_KEY_TYPE) *state = STATE_KEY_3;
|
|
else if (b == SERIAL_KEY_TYPE_LONG) *state = STATE_KEY_3L;
|
|
else *state = STATE_IDLE;
|
|
break;
|
|
|
|
case STATE_KEY_3:
|
|
case STATE_KEY_3L:
|
|
KEYBOARD_InjectKey(b, *state == STATE_KEY_3L);
|
|
connected = true;
|
|
*state = STATE_IDLE;
|
|
break;
|
|
|
|
default:
|
|
*state = STATE_IDLE;
|
|
break;
|
|
}
|
|
|
|
return connected;
|
|
}
|
|
#endif
|
|
|
|
#define GPIOx GPIOB
|
|
#define PIN_MASK_COLS (LL_GPIO_PIN_6 | LL_GPIO_PIN_5 | LL_GPIO_PIN_4 | LL_GPIO_PIN_3)
|
|
#define PIN_COLS GPIO_MAKE_PIN(GPIOx, PIN_MASK_COLS)
|
|
#define PIN_COL(n) GPIO_MAKE_PIN(GPIOx, 1u << (6 - (n)))
|
|
|
|
#define PIN_MASK_ROWS (LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13 | LL_GPIO_PIN_12)
|
|
#define PIN_MASK_ROW(n) (1u << (15 - (n)))
|
|
|
|
static inline uint32_t read_rows()
|
|
{
|
|
return PIN_MASK_ROWS & LL_GPIO_ReadInputPort(GPIOx);
|
|
}
|
|
|
|
static const KEY_Code_t keyboard[5][4] = {
|
|
{ // Zero col
|
|
// Set to zero to handle special case of nothing pulled down
|
|
KEY_SIDE1,
|
|
KEY_SIDE2,
|
|
|
|
// Duplicate to fill the array with valid values
|
|
KEY_INVALID,
|
|
KEY_INVALID,
|
|
},
|
|
{ // First col
|
|
KEY_MENU,
|
|
KEY_1,
|
|
KEY_4,
|
|
KEY_7,
|
|
},
|
|
{ // Second col
|
|
KEY_UP,
|
|
KEY_2 ,
|
|
KEY_5 ,
|
|
KEY_8 ,
|
|
},
|
|
{ // Third col
|
|
KEY_DOWN,
|
|
KEY_3 ,
|
|
KEY_6 ,
|
|
KEY_9 ,
|
|
},
|
|
{ // Fourth col
|
|
KEY_EXIT,
|
|
KEY_STAR,
|
|
KEY_0 ,
|
|
KEY_F ,
|
|
}
|
|
};
|
|
|
|
KEY_Code_t KEYBOARD_Poll(void)
|
|
{
|
|
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
|
// Serial-injected key: hold it for SHORT or LONG polls depending on press type,
|
|
// so the debounce counter in app.c reaches the right threshold:
|
|
// - Short: key_debounce_10ms (2) → ProcessKey(key, true, false)
|
|
// - Long: key_repeat_delay_10ms (40) → ProcessKey(key, true, true)
|
|
// Once the hold count is exhausted we clear it — next call returns KEY_INVALID,
|
|
// which triggers the release path in app.c naturally.
|
|
if (gKeyFromSerial != KEY_INVALID) {
|
|
KEY_Code_t injected = gKeyFromSerial;
|
|
uint8_t threshold = gSerialKeyLong ? SERIAL_KEY_LONG_POLLS : SERIAL_KEY_SHORT_POLLS;
|
|
if (++gSerialKeyHoldCount >= threshold) {
|
|
gKeyFromSerial = KEY_INVALID;
|
|
gSerialKeyHoldCount = 0;
|
|
gSerialKeyLong = 0;
|
|
}
|
|
return injected;
|
|
}
|
|
#endif
|
|
|
|
KEY_Code_t Key = KEY_INVALID;
|
|
|
|
// Scan all 5 columns - never break early to avoid GPIO state issues
|
|
for (unsigned int j = 0; j < 5; j++)
|
|
{
|
|
uint32_t reg;
|
|
uint32_t match_count = 0; // Count consecutive matching reads
|
|
|
|
// Set all columns high first
|
|
GPIO_SetOutputPin(PIN_COLS);
|
|
|
|
// Clear the specific column we are selecting
|
|
if (j > 0)
|
|
{
|
|
GPIO_ResetOutputPin(PIN_COL(j - 1));
|
|
}
|
|
|
|
// Debounce: Read rows multiple times and look for stable reads
|
|
// CRITICAL FIX #1: Proper debounce logic (replaces confusing i *= syntax)
|
|
// CRITICAL FIX #2: Increased delay from 1µs to 10µs for real keyboard bounce capture
|
|
// CRITICAL FIX #3: Clear match_count if reads differ (noise detection)
|
|
reg = 0;
|
|
for (unsigned int k = 0; k < 8; k++)
|
|
{
|
|
SYSTICK_DelayUs(10); // FIX #2: Increased from 1µs to 10µs
|
|
uint32_t reg2 = read_rows();
|
|
|
|
// FIX #3: Clear match counter if values don't match
|
|
if (reg2 != reg)
|
|
{
|
|
match_count = 0;
|
|
reg = reg2;
|
|
}
|
|
else
|
|
{
|
|
match_count++;
|
|
}
|
|
|
|
// Success: We have 3 consecutive matching reads = stable signal
|
|
if (match_count >= 2)
|
|
{
|
|
break; // Debounce complete for this column
|
|
}
|
|
}
|
|
|
|
// FIX #1: Do NOT break on noise - continue scanning all columns
|
|
// Only skip key detection if debounce failed, but GPIO state is cleaned
|
|
if (match_count < 2)
|
|
{
|
|
// Debounce failed (too much noise), but continue to next column
|
|
// This prevents GPIO from staying in invalid state and blocking other columns
|
|
continue;
|
|
}
|
|
|
|
// Debounce successful, check which row is pressed in this column
|
|
for (unsigned int i = 0; i < 4; i++)
|
|
{
|
|
if (!(reg & PIN_MASK_ROW(i)))
|
|
{
|
|
Key = keyboard[j][i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Key != KEY_INVALID)
|
|
{
|
|
break; // Found a valid key, stop scanning
|
|
}
|
|
}
|
|
|
|
// CRITICAL FIX #4: Always clean up GPIO state - set all columns high at end
|
|
// This ensures GPIO pins are in a known state even if function exited early due to noise
|
|
GPIO_SetOutputPin(PIN_COLS);
|
|
|
|
return Key;
|
|
}
|
|
|
|
KEY_Code_t KEYBOARD_GetKey(void)
|
|
{
|
|
KEY_Code_t btn = KEYBOARD_Poll();
|
|
if (btn == KEY_INVALID && GPIO_IsPttPressed())
|
|
{
|
|
btn = KEY_PTT;
|
|
}
|
|
return btn;
|
|
}
|
|
|
|
void HideFKeyIcon(void) {
|
|
gWasFKeyPressed = false;
|
|
gUpdateStatus = true;
|
|
}
|