Fix spectrum: reliable serial key input + USB-C unplug freeze fix (issue #212)

This commit is contained in:
Armel FAUVEAU committed 2026-03-14 05:34:17 +01:00
1 parent fcf94e34ce
commit 9f0547f6af
10 files changed
+106 -55

No files matched your search

+3 -3
View File
@@ -139,7 +139,7 @@ static void AIRCOPY_clear()
crc[i] = 0;
}
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(true);
SCREENSHOT_Update(true);
#endif
}
@@ -167,7 +167,7 @@ static inline void AIRCOPY_CheckComplete(void)
{
gAircopyState = AIRCOPY_COMPLETE;
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(false);
SCREENSHOT_Update(false);
#endif
}
}
@@ -212,7 +212,7 @@ bool AIRCOPY_SendMessage(void)
if (CurrentSegmentIndex >= map->num_segments) {
gAircopyState = AIRCOPY_COMPLETE;
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(false);
SCREENSHOT_Update(false);
#endif
return 0;
}
+1 -1
View File
@@ -1406,7 +1406,7 @@ void APP_TimeSlice10ms(void)
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
if (gUpdateDisplayCurrent || gUpdateStatusCurrent) {
getScreenShot(false);
SCREENSHOT_Update(false);
}
#endif
+1 -1
View File
@@ -484,7 +484,7 @@ void APP_RunBreakout(void) {
// For screenshot
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(false);
SCREENSHOT_Update(false);
#endif
}
+7 -1
View File
@@ -1667,6 +1667,12 @@ static void UpdateListening()
static void Tick()
{
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
// Parse incoming packets on every tick so serial keys are never missed,
// regardless of whether the screen needs redrawing.
SCREENSHOT_ParseInput();
#endif
if (gNextTimeslice)
{
gNextTimeslice = false;
@@ -1737,7 +1743,7 @@ static void Tick()
Render();
// For screenshot
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(false);
SCREENSHOT_Update(false);
#endif
redrawScreen = false;
}
+1 -1
View File
@@ -165,7 +165,7 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u
{
ST7565_BlitScreen(line + 1);
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(true); // Force immediate capture
SCREENSHOT_Update(true); // Force immediate capture
#endif
}
+59 -32
View File
@@ -155,43 +155,70 @@ void UART_LogSend(const void *pBuffer, uint32_t Size)
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
bool UART_IsCableConnected(void) {
typedef enum {
STATE_IDLE = 0,
STATE_KA_1,
STATE_KA_2,
STATE_KA_3,
STATE_KEY_1,
STATE_KEY_2,
STATE_KEY_3,
STATE_KEY_3L,
} ParseState_t;
static uint8_t read_ptr = 0;
static ParseState_t state = STATE_IDLE;
bool connected = false;
for (size_t i = 0; i < sizeof(UART_DMA_Buffer); i++) {
uint8_t b = UART_DMA_Buffer[i];
// DMA write position: NbData counts DOWN from 256
uint8_t write_ptr = (uint8_t)(sizeof(UART_DMA_Buffer) -
LL_DMA_GetDataLength(DMA1, DMA_CHANNEL));
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 <type> <keycode>
// type 0x03 = short press, 0x04 = long press
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 ||
UART_DMA_Buffer[i2] == UART_TYPE_KEY_LONG))
{
uint8_t type = UART_DMA_Buffer[i2];
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;
if (type == UART_TYPE_KEY_LONG)
KEYBOARD_InjectKeyLong(keyCode);
else
KEYBOARD_InjectKey(keyCode);
uint8_t processed = 0;
while (read_ptr != write_ptr && processed < sizeof(UART_DMA_Buffer))
{
uint8_t b = UART_DMA_Buffer[read_ptr++];
// read_ptr wraps naturally at 256 since it's uint8_t
processed++;
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:
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:
if (b == UART_TYPE_KEY) state = STATE_KEY_3;
else if (b == UART_TYPE_KEY_LONG) state = STATE_KEY_3L;
else state = STATE_IDLE;
break;
case STATE_KEY_3:
KEYBOARD_InjectKey(b);
connected = true;
}
state = STATE_IDLE;
break;
case STATE_KEY_3L:
KEYBOARD_InjectKeyLong(b);
connected = true;
state = STATE_IDLE;
break;
default:
state = STATE_IDLE;
break;
}
}
+25 -13
View File
@@ -21,7 +21,26 @@
#include "driver/vcp.h"
#include "driver/keyboard.h"
static void Screenshot_Send(const uint8_t *buf, uint16_t len)
// SRAM optimization: minimize static allocations
// - previousFrame: 1024 bytes (REQUIRED - need to compare for delta)
// - No currentFrame or deltaFrame static buffers
static uint8_t previousFrame[1024] = {0};
static uint8_t forcedBlock = 0;
static uint8_t keepAlive = 10;
void SCREENSHOT_ParseInput(void)
{
if (UART_IsCableConnected()) {
keepAlive = 10;
gUSB_ScreenshotEnabled = false;
}
if (VCP_ScreenshotPing()) {
keepAlive = 10;
gUSB_ScreenshotEnabled = true;
}
}
static void SCREENSHOT_Send(const uint8_t *buf, uint16_t len)
{
if (gUSB_ScreenshotEnabled) {
cdc_acm_data_send_with_dtr(buf, len);
@@ -30,14 +49,7 @@ static void Screenshot_Send(const uint8_t *buf, uint16_t len)
}
}
// SRAM optimization: minimize static allocations
// - previousFrame: 1024 bytes (REQUIRED - need to compare for delta)
// - No currentFrame or deltaFrame static buffers
static uint8_t previousFrame[1024] = {0};
static uint8_t forcedBlock = 0;
static uint8_t keepAlive = 10;
void getScreenShot(bool force)
void SCREENSHOT_Update(bool force)
{
// Build frame in a temporary stack buffer
// This is 1024 bytes but it's temporary and gets freed after the function
@@ -135,7 +147,7 @@ void getScreenShot(bool force)
// New format: sends 0xFF before header
// Old format: doesn't exist, so viewers can differentiate
uint8_t versionMarker = 0xFF;
Screenshot_Send(&versionMarker, 1);
SCREENSHOT_Send(&versionMarker, 1);
// ==== Send header ====
uint8_t header[5] = {
@@ -144,7 +156,7 @@ void getScreenShot(bool force)
(uint8_t)(deltaLen & 0xFF)
};
Screenshot_Send(header, 5);
SCREENSHOT_Send(header, 5);
// ==== SECOND PASS: Send only changed chunks ====
uint8_t chunk[9];
@@ -157,12 +169,12 @@ void getScreenShot(bool force)
chunk[0] = chunkIdx;
memcpy(&chunk[1], cur, 8);
Screenshot_Send(chunk, 9);
SCREENSHOT_Send(chunk, 9);
// Update previousFrame for next comparison
memcpy(prev, cur, 8);
}
uint8_t end = 0x0A;
Screenshot_Send(&end, 1);
SCREENSHOT_Send(&end, 1);
}
+2 -1
View File
@@ -17,6 +17,7 @@
#ifndef SCREENSHOT_H
#define SCREENSHOT_H
void getScreenShot(bool force);
void SCREENSHOT_Update(bool force);
void SCREENSHOT_ParseInput(void);
#endif
+1 -1
View File
@@ -222,7 +222,7 @@ void UI_DisplayWelcome(void)
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
getScreenShot(true);
SCREENSHOT_Update(true);
#endif
}
}
+6 -1
View File
@@ -190,8 +190,13 @@ void cdc_acm_data_send_with_dtr(const uint8_t *buf, uint32_t size)
{
ep_tx_busy_flag = true;
usbd_ep_start_write(CDC_IN_EP, buf, size);
while (ep_tx_busy_flag)
uint32_t timeout = 100000;
while (ep_tx_busy_flag && --timeout)
;
if (!timeout) {
ep_tx_busy_flag = false;
dtr_enable = 0; // Consider USB disconnected
}
}
}