mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-05 04:27:40 +00:00
Add virtual keyboard
This commit is contained in:
1 parent
6ed0bc3436
commit
f072b3eb02
4 files changed
+170
-11
No files matched your search
@@ -27,6 +27,27 @@ KEY_Code_t gKeyReading1 = KEY_INVALID;
|
||||
uint16_t gDebounceCounter = 0;
|
||||
bool gWasFKeyPressed = false;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
// Number of consecutive KEYBOARD_Poll() calls that return the injected key.
|
||||
// Must exceed key_debounce_10ms (typically 2) to pass the debounce gate in app.c.
|
||||
// After HOLD polls the key is cleared, which triggers a natural release cycle.
|
||||
#define SERIAL_KEY_HOLD_POLLS 5
|
||||
|
||||
volatile KEY_Code_t gKeyFromSerial = KEY_INVALID;
|
||||
static uint8_t gSerialKeyHoldCount = 0;
|
||||
|
||||
// Inject a key received from serial (UART or VCP).
|
||||
// keyCode is the raw packet byte; validated against KEY_Code_e range before use.
|
||||
// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial.
|
||||
void KEYBOARD_InjectKey(uint8_t keyCode)
|
||||
{
|
||||
if (keyCode < KEY_INVALID && keyCode != KEY_PTT) {
|
||||
gKeyFromSerial = (KEY_Code_t)keyCode;
|
||||
gSerialKeyHoldCount = 0;
|
||||
}
|
||||
}
|
||||
#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)
|
||||
@@ -78,6 +99,21 @@ static const KEY_Code_t keyboard[5][4] = {
|
||||
|
||||
KEY_Code_t KEYBOARD_Poll(void)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
// Serial-injected key: hold it for SERIAL_KEY_HOLD_POLLS consecutive calls
|
||||
// so the debounce counter in app.c has time to reach key_debounce_10ms.
|
||||
// Once the hold count is exhausted we clear it — the next call returns
|
||||
// KEY_INVALID, which triggers the release path in app.c naturally.
|
||||
if (gKeyFromSerial != KEY_INVALID) {
|
||||
KEY_Code_t injected = gKeyFromSerial;
|
||||
if (++gSerialKeyHoldCount >= SERIAL_KEY_HOLD_POLLS) {
|
||||
gKeyFromSerial = KEY_INVALID;
|
||||
gSerialKeyHoldCount = 0;
|
||||
}
|
||||
return injected;
|
||||
}
|
||||
#endif
|
||||
|
||||
KEY_Code_t Key = KEY_INVALID;
|
||||
|
||||
// Scan all 5 columns - never break early to avoid GPIO state issues
|
||||
|
||||
+11
-1
@@ -50,7 +50,17 @@ extern KEY_Code_t gKeyReading1;
|
||||
extern uint16_t gDebounceCounter;
|
||||
extern bool gWasFKeyPressed;
|
||||
|
||||
KEY_Code_t KEYBOARD_Poll(void);
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
// Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll).
|
||||
// Held for SERIAL_KEY_HOLD_POLLS calls to satisfy the debounce logic in app.c.
|
||||
extern volatile KEY_Code_t gKeyFromSerial;
|
||||
|
||||
// Inject a key received from serial (UART or VCP), bypasses physical matrix.
|
||||
// keyCode is the raw byte from the serial packet — validated against KEY_Code_e range.
|
||||
// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial.
|
||||
void KEYBOARD_InjectKey(uint8_t keyCode);
|
||||
#endif
|
||||
|
||||
KEY_Code_t KEYBOARD_Poll(void);
|
||||
|
||||
#endif
|
||||
+38
-5
@@ -22,7 +22,12 @@
|
||||
#include "py32f071_ll_dma.h"
|
||||
#include "py32f071_ll_gpio.h"
|
||||
#include "py32f071_ll_usart.h"
|
||||
#include "driver/uart.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "driver/keyboard.h"
|
||||
// Packet type for serial key injection (K5Viewer → radio)
|
||||
#define UART_TYPE_KEY 0x03
|
||||
#endif
|
||||
|
||||
#define USARTx USART1
|
||||
#define DMA_CHANNEL LL_DMA_CHANNEL_2
|
||||
@@ -149,12 +154,40 @@ void UART_LogSend(const void *pBuffer, uint32_t Size)
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
bool UART_IsCableConnected(void) {
|
||||
bool connected = false;
|
||||
|
||||
for (size_t i = 0; i < sizeof(UART_DMA_Buffer); i++) {
|
||||
if (UART_DMA_Buffer[i] == 0x55) {
|
||||
UART_DMA_Buffer[i] = 0x00; // Clear only the matched byte
|
||||
return true;
|
||||
uint8_t b = UART_DMA_Buffer[i];
|
||||
|
||||
if (b == 0x55) {
|
||||
// Keepalive byte — viewer is alive
|
||||
UART_DMA_Buffer[i] = 0x00;
|
||||
connected = true;
|
||||
}
|
||||
else if (b == 0xAA) {
|
||||
// Possible start of a key packet: 0xAA 0x55 0x03 <keycode>
|
||||
// Check if the next 3 bytes are available and match
|
||||
size_t i1 = (i + 1) % sizeof(UART_DMA_Buffer);
|
||||
size_t i2 = (i + 2) % sizeof(UART_DMA_Buffer);
|
||||
size_t i3 = (i + 3) % sizeof(UART_DMA_Buffer);
|
||||
|
||||
if (UART_DMA_Buffer[i1] == 0x55 &&
|
||||
UART_DMA_Buffer[i2] == UART_TYPE_KEY)
|
||||
{
|
||||
uint8_t keyCode = UART_DMA_Buffer[i3];
|
||||
|
||||
// Consume all 4 bytes
|
||||
UART_DMA_Buffer[i] = 0x00;
|
||||
UART_DMA_Buffer[i1] = 0x00;
|
||||
UART_DMA_Buffer[i2] = 0x00;
|
||||
UART_DMA_Buffer[i3] = 0x00;
|
||||
|
||||
KEYBOARD_InjectKey(keyCode);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return connected;
|
||||
}
|
||||
#endif
|
||||
+85
-5
@@ -18,6 +18,12 @@
|
||||
#include "usb_config.h"
|
||||
#include "py32f071_ll_bus.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "driver/keyboard.h"
|
||||
// Packet type for serial key injection (K5Viewer → radio)
|
||||
#define VCP_TYPE_KEY 0x03
|
||||
#endif
|
||||
|
||||
uint8_t VCP_RxBuf[VCP_RX_BUF_SIZE];
|
||||
volatile uint32_t VCP_RxBufPointer = 0;
|
||||
|
||||
@@ -40,22 +46,96 @@ void VCP_Init()
|
||||
|
||||
bool VCP_ScreenshotPing(void)
|
||||
{
|
||||
static uint32_t read_ptr = 0;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
// State machine for parsing incoming packets:
|
||||
// Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive
|
||||
// Key packet: 0xAA 0x55 0x03 <key> → inject key
|
||||
//
|
||||
// State transitions:
|
||||
// IDLE → 0x55 → KA_1
|
||||
// KA_1 → 0xAA → KA_2 (else IDLE)
|
||||
// KA_2 → 0x00 → KA_3 (else IDLE)
|
||||
// KA_3 → 0x00 → keepalive OK, IDLE
|
||||
//
|
||||
// IDLE → 0xAA → KEY_1
|
||||
// KEY_1 → 0x55 → KEY_2 (else IDLE)
|
||||
// KEY_2 → 0x03 → KEY_3 (else IDLE)
|
||||
// KEY_3 → <b> → inject key, IDLE
|
||||
|
||||
typedef enum {
|
||||
STATE_IDLE = 0,
|
||||
STATE_KA_1,
|
||||
STATE_KA_2,
|
||||
STATE_KA_3,
|
||||
STATE_KEY_1,
|
||||
STATE_KEY_2,
|
||||
STATE_KEY_3,
|
||||
} ParseState_t;
|
||||
|
||||
static uint32_t read_ptr = 0;
|
||||
static ParseState_t state = STATE_IDLE;
|
||||
|
||||
bool connected = false;
|
||||
uint32_t write_ptr = VCP_RxBufPointer;
|
||||
|
||||
while (read_ptr != write_ptr)
|
||||
{
|
||||
uint8_t b = VCP_RxBuf[read_ptr];
|
||||
|
||||
read_ptr++;
|
||||
if (read_ptr >= VCP_RX_BUF_SIZE)
|
||||
read_ptr = 0;
|
||||
|
||||
if (b == 0x55)
|
||||
switch (state)
|
||||
{
|
||||
return true;
|
||||
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:
|
||||
state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE;
|
||||
break;
|
||||
|
||||
case STATE_KA_3:
|
||||
if (b == 0x00) connected = true;
|
||||
state = STATE_IDLE;
|
||||
break;
|
||||
|
||||
case STATE_KEY_1:
|
||||
state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE;
|
||||
break;
|
||||
|
||||
case STATE_KEY_2:
|
||||
state = (b == VCP_TYPE_KEY) ? STATE_KEY_3 : STATE_IDLE;
|
||||
break;
|
||||
|
||||
case STATE_KEY_3:
|
||||
KEYBOARD_InjectKey(b);
|
||||
connected = true;
|
||||
state = STATE_IDLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return connected;
|
||||
|
||||
#else
|
||||
// Simple ping: just detect any incoming byte from the viewer
|
||||
static uint32_t read_ptr = 0;
|
||||
uint32_t write_ptr = VCP_RxBufPointer;
|
||||
|
||||
if (read_ptr != write_ptr) {
|
||||
read_ptr = write_ptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user