Add virtual keyboard

This commit is contained in:
Armel FAUVEAU committed 2026-02-25 02:24:39 +01:00
1 parent 6ed0bc3436
commit f072b3eb02
4 files changed
+170 -11

No files matched your search

+36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}