-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathPIOKMbox.c
More file actions
437 lines (351 loc) · 15.1 KB
/
Copy pathPIOKMbox.c
File metadata and controls
437 lines (351 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
/*
* Hurricane PIOKMBox Firmware
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/uart.h"
#include "hardware/vreg.h"
#include "hardware/adc.h"
#include "pico/unique_id.h"
#include "tusb.h"
#include "defines.h"
#include "config.h"
#include "timing_config.h"
#include "led_control.h"
#include "usb_hid.h"
#include "watchdog.h"
#include "state_management.h"
#include "kmbox_serial_handler.h"
#include "smooth_injection.h"
#include "peri_clock.h"
#include "xbox_gip.h"
#include "xbox_device.h"
#include "xbox_host.h"
#if PIO_USB_AVAILABLE
#include "pio_usb.h"
#include "hardware/clocks.h"
#include "hardware/pll.h"
#include "pico/multicore.h"
#include "pico/flash.h"
#include "tusb.h"
#endif
//--------------------------------------------------------------------+
// Global state for flash operation coordination
//--------------------------------------------------------------------+
volatile bool g_flash_operation_in_progress = false;
volatile bool g_core1_flash_acknowledged = false;
//--------------------------------------------------------------------+
// Function Prototypes
//--------------------------------------------------------------------+
#if PIO_USB_AVAILABLE
static void core1_main(void);
static void core1_task_loop(void);
#endif
static bool initialize_system(void);
static bool initialize_usb_device(void);
static void main_application_loop(void);
// Button handling functions
static void process_button_input(system_state_t* state, uint32_t current_time);
// Utility functions
static inline bool is_time_elapsed(uint32_t current_time, uint32_t last_time, uint32_t interval);
//--------------------------------------------------------------------+
// Core1 Main (USB Host Task)
//--------------------------------------------------------------------+
#if PIO_USB_AVAILABLE
static void core1_main(void) {
// Small delay to let core0 stabilize
sleep_ms(10);
// Initialize flash_safe_execute support on Core1.
// This allows Core0's flash_safe_execute() to safely pause Core1
// during flash operations (replaces manual g_flash_operation_in_progress polling).
flash_safe_execute_core_init();
// CRITICAL: Configure PIO USB BEFORE tuh_init() - this is the key!
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
pio_cfg.pin_dp = PIN_USB_HOST_DP;
pio_cfg.pinout = PIO_USB_PINOUT_DPDM;
// Configure host stack with PIO USB configuration
tuh_configure(USB_HOST_PORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &pio_cfg);
// CRITICAL: Use Report protocol by default (not Boot protocol).
// Boot protocol is simpler but many devices (Logitech receivers, gaming mice)
// only work correctly in Report protocol mode with proper report descriptors.
// This must be set BEFORE tuh_init().
tuh_hid_set_default_protocol(HID_PROTOCOL_REPORT);
// Initialize host stack on core1
tuh_init(USB_HOST_PORT);
// Mark host as initialized
usb_host_mark_initialized();
// Start the main host task loop
core1_task_loop();
}
static void core1_task_loop(void) {
// Optimize heartbeat checking - use larger counter intervals
uint32_t heartbeat_counter = 0;
uint32_t last_heartbeat_ms = 0;
// Performance optimization: reduce heartbeat frequency checks
const uint32_t heartbeat_check_threshold = CORE1_HEARTBEAT_CHECK_LOOPS * 4; // 4x less frequent
while (true) {
// NOTE: Flash safety is handled automatically by flash_safe_execute_core_init().
// The SDK's multicore_lockout mechanism pauses this core via SIO FIFO IRQ
// when Core0 calls flash_safe_execute(), so no manual polling needed.
tuh_task();
// Drain SET_REPORT passthrough queue (device→host vendor reports)
hid_host_task();
// Xbox host task: forward console commands to controller, keepalive
if (g_xbox_mode) {
xbox_host_task();
}
// Heartbeat check optimization - much less frequent timing calls
if (++heartbeat_counter >= heartbeat_check_threshold) {
const uint32_t current_time = to_ms_since_boot(get_absolute_time());
if ((current_time - last_heartbeat_ms) >= WATCHDOG_HEARTBEAT_INTERVAL_MS) {
watchdog_core1_heartbeat();
last_heartbeat_ms = current_time;
}
heartbeat_counter = 0;
}
}
}
#endif // PIO_USB_AVAILABLE
//--------------------------------------------------------------------+
// System Initialization Functions
//--------------------------------------------------------------------+
static bool initialize_system(void) {
// Initialize stdio first for early debug output
stdio_init_all();
// Add startup delay for cold boot stability
sleep_ms(200);
// Overclock RP2350 to 240MHz, increase VREG voltage to 1.25V
vreg_set_voltage(VREG_VOLTAGE_1_25);
sleep_ms(10); // Let voltage stabilize
if (!set_sys_clock_khz(CPU_FREQ, true)) {
return false;
}
// CRITICAL: Configure stable peripheral clock BEFORE UART init
// This must happen before kmbox_serial_init() so uart_init() calculates correct divisor
peri_clock_configure_stable();
// Re-initialize stdio after clock change with proper delay
sleep_ms(100); // Allow clock to stabilize
stdio_init_all(); // No-op when both UART and USB stdio are disabled
sleep_ms(100); // Allow system to stabilize
// Initialize KMBox serial handler on UART0 (via RP2350 USB Bridge)
// UART will now use the stable 48MHz peri clock for accurate 2 Mbaud
kmbox_serial_init();
// Initialize ADC for temperature sensor
adc_init();
adc_set_temp_sensor_enabled(true);
// Initialize smooth injection system for seamless mouse movement blending
smooth_injection_init();
// Initialize LED control module (neopixel power OFF for now)
neopixel_init();
// Initialize USB HID module (USB host power OFF for now)
usb_hid_init();
// Initialize watchdog system (but don't start it yet)
watchdog_init();
// Initialization complete - proceed to USB init
return true;
}
static bool initialize_usb_device(void) {
const bool device_init_success = tud_init(USB_DEVICE_PORT);
if (device_init_success) {
usb_device_mark_initialized();
}
return device_init_success;
}
//--------------------------------------------------------------------+
// Button Handling Functions
//--------------------------------------------------------------------+
static void process_button_input(system_state_t* state, uint32_t current_time) {
// Performance optimization: single GPIO read per call
const bool button_currently_pressed = !gpio_get(PIN_BUTTON); // Button is active low
// Handle cooldown after USB reset - early exit for performance
if (state->usb_reset_cooldown) {
if (is_time_elapsed(current_time, state->usb_reset_cooldown_start, USB_RESET_COOLDOWN_MS)) {
state->usb_reset_cooldown = false;
}
state->button_pressed_last = button_currently_pressed;
return; // Skip button processing during cooldown
}
// Optimized state machine - avoid redundant checks
if (button_currently_pressed) {
if (!state->button_pressed_last) {
// Button just pressed
state->last_button_press_time = current_time;
} else {
// Button being held - check for reset trigger
if (is_time_elapsed(current_time, state->last_button_press_time, BUTTON_HOLD_TRIGGER_MS)) {
usb_stacks_reset();
state->usb_reset_cooldown = true;
state->usb_reset_cooldown_start = current_time;
}
}
} else if (state->button_pressed_last) {
// Button just released - check if it was a short press
uint32_t hold_duration = current_time - state->last_button_press_time;
if (hold_duration < BUTTON_HOLD_TRIGGER_MS) {
// Short press - cycle humanization mode
humanization_mode_t new_mode = smooth_cycle_humanization_mode();
// Send immediate notification to bridge
kmbox_send_info_to_bridge();
// Show mode with LED flash
uint32_t mode_color;
switch (new_mode) {
case HUMANIZATION_OFF: mode_color = COLOR_HUMANIZATION_OFF; break;
case HUMANIZATION_MICRO: mode_color = COLOR_HUMANIZATION_MICRO; break;
case HUMANIZATION_FULL: mode_color = COLOR_HUMANIZATION_FULL; break;
default: mode_color = COLOR_ERROR; break;
}
neopixel_set_color(mode_color);
neopixel_trigger_mode_flash(mode_color, 1500);
}
}
state->button_pressed_last = button_currently_pressed;
}
//--------------------------------------------------------------------+
// Utility Functions
//--------------------------------------------------------------------+
static __force_inline bool is_time_elapsed(uint32_t current_time, uint32_t last_time, uint32_t interval) {
return (current_time - last_time) >= interval;
}
//--------------------------------------------------------------------+
// Main Application Loop
//--------------------------------------------------------------------+
static void main_application_loop(void) {
system_state_t* state = get_system_state();
system_state_init(state);
// Cache frequently used intervals
const uint32_t watchdog_interval = WATCHDOG_TASK_INTERVAL_MS;
const uint32_t visual_interval = VISUAL_TASK_INTERVAL_MS;
const uint32_t error_interval = ERROR_CHECK_INTERVAL_MS;
const uint32_t button_interval = BUTTON_DEBOUNCE_MS;
const uint32_t status_interval = WATCHDOG_STATUS_REPORT_INTERVAL_MS;
// Performance optimization: reduce time sampling frequency
uint32_t current_time = to_ms_since_boot(get_absolute_time());
uint16_t loop_counter = 0;
// Initialize DMA after a delay to ensure USB is fully stable
bool dma_initialized = false;
uint32_t boot_complete_time = current_time;
// Batch time checks with bit flags for efficiency
uint8_t task_flags = 0;
#define WATCHDOG_FLAG (1 << 0)
#define VISUAL_FLAG (1 << 1)
#define BUTTON_FLAG (1 << 2)
#define STATUS_FLAG (1 << 3)
while (true) {
// TinyUSB device task - highest priority
tud_task();
// Initialize DMA after 3 seconds of stable USB operation
if (!dma_initialized && (current_time - boot_complete_time) > 3000) {
kmbox_serial_init_dma();
dma_initialized = true;
}
// KMBox serial task - process bridge commands
// Bridge movements are added to the shared accumulator and combined
// with physical mouse movements automatically
kmbox_serial_task();
// HID device task - processes physical mouse/keyboard and sends combined reports
// Xbox mode: run Xbox device task instead of HID device task
if (g_xbox_mode) {
xbox_device_task();
} else {
// Check if Xbox controller was just unplugged — re-enumerate
// so the console/PC sees updated descriptors (HID instead of Xbox)
if (xbox_host_check_and_clear_reenum()) {
force_usb_reenumeration();
}
hid_device_task();
}
// Sample time less frequently to reduce overhead
if (++loop_counter >= MAIN_LOOP_TIME_SAMPLE_INTERVAL) {
current_time = to_ms_since_boot(get_absolute_time());
loop_counter = 0;
// Batch all time checks into flags for efficiency
task_flags = 0;
if ((current_time - state->last_watchdog_time) >= watchdog_interval) {
task_flags |= WATCHDOG_FLAG;
}
if ((current_time - state->last_visual_time) >= visual_interval) {
task_flags |= VISUAL_FLAG;
}
if ((current_time - state->last_button_time) >= button_interval) {
task_flags |= BUTTON_FLAG;
}
if ((current_time - state->watchdog_status_timer) >= status_interval) {
task_flags |= STATUS_FLAG;
}
// Error check optimization - only when other tasks run
if (task_flags && (current_time - state->last_error_check_time) >= error_interval) {
state->last_error_check_time = current_time;
}
}
// Execute tasks based on flags (avoids repeated time checks)
if (task_flags & WATCHDOG_FLAG) {
watchdog_task();
watchdog_core0_heartbeat();
// Process deferred flash saves at watchdog interval (~100ms)
// so saves happen ~2s after mode change (SAVE_DEFER_MS),
// not at the 60s status boundary where the surprise 100ms
// interrupt blackout from flash_safe_execute kills USB.
smooth_process_deferred_save();
state->last_watchdog_time = current_time;
}
if (task_flags & BUTTON_FLAG) {
process_button_input(state, current_time);
state->last_button_time = current_time;
}
if (task_flags & VISUAL_FLAG) {
led_blinking_task();
neopixel_status_task();
state->last_visual_time = current_time;
}
if (task_flags & STATUS_FLAG) {
state->watchdog_status_timer = current_time;
// Send Xbox console mode status to bridge for TFT display
if (g_xbox_mode) {
kmbox_send_xbox_status_to_bridge();
}
}
}
}
//--------------------------------------------------------------------+
// Main Function
//--------------------------------------------------------------------+
int main(void) {
// Initialize basic GPIO (clock will be set by initialize_system)
#ifdef PIN_USB_5V
gpio_init(PIN_USB_5V);
gpio_set_dir(PIN_USB_5V, GPIO_OUT);
gpio_put(PIN_USB_5V, 0); // Keep USB power OFF initially
#endif
gpio_init(PIN_LED);
gpio_set_dir(PIN_LED, GPIO_OUT);
gpio_put(PIN_LED, 1); // Turn on LED
printf("=== PIOKMBox Starting ===\n");
// Initialize system components (includes clock configuration)
if (!initialize_system()) {
printf("CRITICAL: System initialization failed\n");
return -1;
}
// Enable USB host power
usb_host_enable_power();
sleep_ms(100); // Brief power stabilization
#if PIO_USB_AVAILABLE
multicore_reset_core1();
multicore_launch_core1(core1_main);
// Give core1 time to initialize before USB device init
sleep_ms(100);
#endif
if (!initialize_usb_device()) {
printf("CRITICAL: USB Device initialization failed\n");
return -1;
}
neopixel_enable_power();
printf("=== PIOKMBox Ready ===\n");
// Enter main application loop
main_application_loop();
return 0;
}