diff --git a/firmware/libsi/CMakeLists.txt b/firmware/libsi/CMakeLists.txt index 879244e..3b4d74e 100644 --- a/firmware/libsi/CMakeLists.txt +++ b/firmware/libsi/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION "3.21") project(si LANGUAGES C) # Define the library -add_library(si STATIC "src/crc8.c" "src/commands.c" "src/device/gc_controller.c") +add_library(si STATIC "src/crc8.c" "src/device/commands.c" "src/device/gc_controller.c") # Configure SI peripheral selection if(DEFINED SI_RX_TIMER_IDX) diff --git a/firmware/libsi/include/si/commands.h b/firmware/libsi/include/si/device/commands.h similarity index 60% rename from firmware/libsi/include/si/commands.h rename to firmware/libsi/include/si/device/commands.h index 2c46ccb..ea5cdb8 100644 --- a/firmware/libsi/include/si/commands.h +++ b/firmware/libsi/include/si/device/commands.h @@ -3,7 +3,7 @@ #include #include -#include "si.h" +#include "si/si.h" /** * Function type for command handlers. @@ -14,42 +14,45 @@ * * @return 0 on success, negative error code on failure */ -typedef int (*si_command_handler_fn)(const uint8_t *command, si_callback_fn callback, void *context); +typedef int (*si_command_handler_fn)(const uint8_t *command, si_complete_cb_t callback, void *context); + +/** + * Command structure representing a registered command. + */ +struct si_command { + uint8_t command; + uint8_t length; + si_command_handler_fn handler; + void *user_data; +}; /** * Register a command handler for commands from an SI host. * * @param command the command to handle - * @param command_length the length of the command + * @param command_length the length of the command in bytes * @param handler the command handler function * */ void si_command_register(uint8_t command, uint8_t length, si_command_handler_fn handler, void *context); /** - * Get the expected length of an SI command. - * - * @param command the command to check - * - * @return the expected length of the command, in bytes, or 0 if the command is unknown - */ -uint8_t si_command_get_length(uint8_t command); - -/** - * Get the command handler for an SI command. + * Look up a command structure by command ID. * - * @param command the command to check + * @param command the command ID to look up * - * @return the command handler function, or NULL if the command is unknown + * @return pointer to the command structure, or NULL if not found */ -si_command_handler_fn si_command_get_handler(uint8_t command); +struct si_command *si_command_find_by_id(uint8_t command); /** - * Process an SI command. + * Process a single SI command on the bus. * + * This will read a command from the SI bus and call the registered handler. * + * @param await_bus_idle if true, will wait for the SI bus to be idle before reading commands */ -void si_command_process(); +void si_command_process(bool await_bus_idle); /** * Enable automatic command processing. diff --git a/firmware/libsi/include/si/device/gc_controller.h b/firmware/libsi/include/si/device/gc_controller.h index d8a8ea6..eeee110 100644 --- a/firmware/libsi/include/si/device/gc_controller.h +++ b/firmware/libsi/include/si/device/gc_controller.h @@ -7,6 +7,31 @@ #include #include +// GameCube controller commands +#define SI_CMD_GC_SHORT_POLL 0x40 +#define SI_CMD_GC_SHORT_POLL_LEN 3 +#define SI_CMD_GC_SHORT_POLL_RESP 8 + +#define SI_CMD_GC_READ_ORIGIN 0x41 +#define SI_CMD_GC_READ_ORIGIN_LEN 1 +#define SI_CMD_GC_READ_ORIGIN_RESP 10 + +#define SI_CMD_GC_CALIBRATE 0x42 +#define SI_CMD_GC_CALIBRATE_LEN 3 +#define SI_CMD_GC_CALIBRATE_RESP 10 + +#define SI_CMD_GC_LONG_POLL 0x43 +#define SI_CMD_GC_LONG_POLL_LEN 3 +#define SI_CMD_GC_LONG_POLL_RESP 10 + +#define SI_CMD_GC_PROBE_DEVICE 0x4D +#define SI_CMD_GC_PROBE_DEVICE_LEN 3 +#define SI_CMD_GC_PROBE_DEVICE_RESP 8 + +#define SI_CMD_GC_FIX_DEVICE 0x4E +#define SI_CMD_GC_FIX_DEVICE_LEN 3 +#define SI_CMD_GC_FIX_DEVICE_RESP 3 + /** * Rumble motor states. */ @@ -88,7 +113,6 @@ struct si_device_gc_controller { * * @param device the device to initialize * @param type the device type flags - * @param input_state pointer to an input state buffer */ void si_device_gc_init(struct si_device_gc_controller *device, uint8_t type); diff --git a/firmware/libsi/include/si/si.h b/firmware/libsi/include/si/si.h index 7233f2e..2b83ec7 100644 --- a/firmware/libsi/include/si/si.h +++ b/firmware/libsi/include/si/si.h @@ -41,6 +41,7 @@ #pragma once +#include #include #include @@ -56,31 +57,6 @@ #define SI_CMD_INFO_LEN 1 #define SI_CMD_INFO_RESP 3 -// GameCube controller commands -#define SI_CMD_GC_SHORT_POLL 0x40 -#define SI_CMD_GC_SHORT_POLL_LEN 3 -#define SI_CMD_GC_SHORT_POLL_RESP 8 - -#define SI_CMD_GC_READ_ORIGIN 0x41 -#define SI_CMD_GC_READ_ORIGIN_LEN 1 -#define SI_CMD_GC_READ_ORIGIN_RESP 10 - -#define SI_CMD_GC_CALIBRATE 0x42 -#define SI_CMD_GC_CALIBRATE_LEN 3 -#define SI_CMD_GC_CALIBRATE_RESP 10 - -#define SI_CMD_GC_LONG_POLL 0x43 -#define SI_CMD_GC_LONG_POLL_LEN 3 -#define SI_CMD_GC_LONG_POLL_RESP 10 - -#define SI_CMD_GC_PROBE_DEVICE 0x4D -#define SI_CMD_GC_PROBE_DEVICE_LEN 3 -#define SI_CMD_GC_PROBE_DEVICE_RESP 8 - -#define SI_CMD_GC_FIX_DEVICE 0x4E -#define SI_CMD_GC_FIX_DEVICE_LEN 3 -#define SI_CMD_GC_FIX_DEVICE_RESP 3 - // SI device info flags // On wireless controllers 0x00C0FF is reserved for the controller ID @@ -118,12 +94,21 @@ enum { SI_ERR_TRANSFER_TIMEOUT, }; +/** + * Function type for per-byte callbacks during receive operations. + * + * @param byte the received byte + * @param byte_index the zero-based index of the byte (0 for first byte) + * @return true to continue the transfer, false to stop it + */ +typedef bool (*si_byte_cb_t)(uint8_t byte, uint8_t byte_index); + /** * Function type for transfer completion callbacks. * - * @param result 0 on success, negative error code on failure + * @param result positive number of bytes read on success, negative error code on failure */ -typedef void (*si_callback_fn)(int result); +typedef void (*si_complete_cb_t)(int result); /** * Initialize the SI bus. @@ -143,24 +128,17 @@ void si_init(uint8_t port, uint8_t pin, uint8_t mode, uint32_t rx_freq, uint32_t * @param length the length of the data * @param callback function to call when the transfer is complete */ -void si_write_bytes(const uint8_t *data, uint8_t length, si_callback_fn callback); +void si_write_bytes(const uint8_t *data, uint8_t length, si_complete_cb_t callback); /** * Read data from the SI bus. * * @param buffer the buffer to read into - * @param length the number of bytes expected - * @param callback function to call when the transfer is complete - */ -void si_read_bytes(uint8_t *buffer, uint8_t length, si_callback_fn callback); - -/** - * Read a single command from the SI bus. - * - * @param buffer the buffer to read into - * @param callback function to call when the command has been read + * @param max_length the maximum number of bytes to read (buffer size) + * @param byte_callback optional function to call for each received byte (can be NULL) + * @param complete_callback function to call when the transfer is complete */ -void si_read_command(uint8_t *buffer, si_callback_fn callback); +void si_read_bytes(uint8_t *buffer, uint8_t max_length, si_byte_cb_t byte_callback, si_complete_cb_t complete_callback); /** * Wait for the SI bus to be idle. diff --git a/firmware/libsi/src/commands.c b/firmware/libsi/src/commands.c deleted file mode 100644 index eddb5da..0000000 --- a/firmware/libsi/src/commands.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "si/commands.h" - -#define COMMAND_TABLE_SIZE 18 - -enum { - BUS_STATE_UNKNOWN = 0, - BUS_STATE_IDLE, - BUS_STATE_BUSY, - BUS_STATE_ERROR, -}; - -struct command_entry { - uint8_t command; - uint8_t length; - si_command_handler_fn handler; - void *context; -}; - -static uint8_t bus_state = BUS_STATE_UNKNOWN; -static struct command_entry command_table[COMMAND_TABLE_SIZE] = {0}; -static uint8_t command_buffer[SI_BLOCK_SIZE]; -static bool auto_tx_rx_transition = false; - -// Vaguely context-aware hash function -static inline uint8_t hash_command(uint8_t command) -{ - // Reserve hash slots for universal commands - if (command == 0x00) - return 0; - if (command == 0xFF) - return 1; - - // Hash all other commands to remaining slots - return (command % (COMMAND_TABLE_SIZE - 2)) + 2; -} - -// Find a command entry by command id -static struct command_entry *find_command(uint8_t command) -{ - uint8_t index = hash_command(command); - - while (command_table[index].handler != NULL) { - if (command_table[index].command == command) { - return &command_table[index]; - } - index = (index + 1) % COMMAND_TABLE_SIZE; - } - return NULL; -} - -// Command handler TX completion callback -static void on_tx_complete(int result) -{ - // Update bus state based on result - bus_state = (result < 0) ? BUS_STATE_ERROR : BUS_STATE_IDLE; - - // Transition back to RX mode if auto transition is enabled - if (auto_tx_rx_transition) - si_command_process(); -} - -// Command handler RX completion callback -static void on_rx_complete(int result) -{ - if (result == 0) { - // Look up the command in the table - struct command_entry *command = find_command(command_buffer[0]); - if (command && command->handler) { - // Call the command handler - bus_state = BUS_STATE_BUSY; - command->handler(command_buffer, on_tx_complete, command->context); - return; - } - } - - // Error during command read or handler not found - bus_state = BUS_STATE_ERROR; - - // Transition back to RX mode if auto transition is enabled - if (auto_tx_rx_transition) - si_command_process(); -} - -void si_command_register(uint8_t command, uint8_t length, si_command_handler_fn handler, void *context) -{ - uint8_t index = hash_command(command); - - // Linear probing for collision resolution - while (command_table[index].handler != NULL && command_table[index].command != command) { - index = (index + 1) % COMMAND_TABLE_SIZE; - } - - command_table[index].command = command; - command_table[index].length = length; - command_table[index].handler = handler; - command_table[index].context = context; -} - -uint8_t si_command_get_length(uint8_t command) -{ - struct command_entry *entry = find_command(command); - if (entry == NULL) - return 0; - - return entry->length; -} - -si_command_handler_fn si_command_get_handler(uint8_t command) -{ - struct command_entry *entry = find_command(command); - if (entry == NULL) - return NULL; - - return entry->handler; -} - -void si_command_process() -{ - if (bus_state != BUS_STATE_IDLE) - si_await_bus_idle(); - - bus_state = BUS_STATE_BUSY; - si_read_command(command_buffer, on_rx_complete); -} - -void si_command_processing_enable() -{ - auto_tx_rx_transition = true; - si_command_process(); -} - -void si_command_processing_disable() -{ - auto_tx_rx_transition = false; - bus_state = BUS_STATE_UNKNOWN; -} \ No newline at end of file diff --git a/firmware/libsi/src/device/commands.c b/firmware/libsi/src/device/commands.c new file mode 100644 index 0000000..27cc915 --- /dev/null +++ b/firmware/libsi/src/device/commands.c @@ -0,0 +1,130 @@ +#include "si/device/commands.h" + +#define COMMAND_TABLE_SIZE 18 + +// Command table for registered SI commands +static struct si_command command_table[COMMAND_TABLE_SIZE] = {0}; +static struct si_command *current_command = NULL; + +// Buffer for reading/writing commands +static uint8_t command_buffer[SI_BLOCK_SIZE]; + +// Automatically transition back to reading commands +static bool command_processing_enabled = false; + +// Vaguely context-aware hash function +// We're hashing to reduce memory usage while keeping O(1) lookup time in most cases +static inline uint8_t hash_command(uint8_t command) +{ + // Reserve hash slots for universal commands + if (command == 0x00) + return 0; + if (command == 0xFF) + return 1; + + // Hash all other commands to remaining slots + return (command % (COMMAND_TABLE_SIZE - 2)) + 2; +} + +// Command handler TX completion callback +static void on_tx_complete(int result) +{ + // Begin listening for the next command if command processing is enabled + // Wait for bus idle if there was an error + if (command_processing_enabled) + si_command_process(result < 0); +} + +// Command handler RX completion callback +static void on_rx_complete(int result) +{ + // If we successfully read a command and have a handler, call it + if (result >= 0 && current_command && current_command->handler) { + current_command->handler(command_buffer, on_tx_complete, current_command->user_data); + return; + } + + // Begin listening for the next command if command processing is enabled + if (command_processing_enabled) + si_command_process(true); +} + +static bool command_byte_cb(uint8_t byte, uint8_t byte_index) +{ + // If this is the first byte, check if there is a registered command + if (byte_index == 0) { + current_command = si_command_find_by_id(byte); + if (current_command == NULL) + return false; + } + + // Stop reading when we reach the expected length + if (byte_index == current_command->length - 1) + return false; + + return true; +} + +void si_command_register(uint8_t command, uint8_t length, si_command_handler_fn handler, void *user_data) +{ + uint8_t index = hash_command(command); + + // Linear probing for collision resolution + // If the slot is already occupied, find the next available slot + // This has high clustering, but is simple and effective for our use case + while (command_table[index].handler != NULL && command_table[index].command != command) { + index = (index + 1) % COMMAND_TABLE_SIZE; + } + + // Store the command + command_table[index] = (struct si_command){ + .command = command, + .length = length, + .handler = handler, + .user_data = user_data, + }; +} + +struct si_command *si_command_find_by_id(uint8_t command) +{ + uint8_t index = hash_command(command); + + while (command_table[index].handler != NULL) { + if (command_table[index].command == command) { + return &command_table[index]; + } + index = (index + 1) % COMMAND_TABLE_SIZE; + } + return NULL; +} + +void si_command_process(bool await_bus_idle) +{ + // Await for the bus to be idle if requested + if (await_bus_idle) + si_await_bus_idle(); + + // Begin reading the next command from the SI bus + si_read_bytes(command_buffer, SI_BLOCK_SIZE, command_byte_cb, on_rx_complete); +} + +void si_command_processing_enable() +{ + if (command_processing_enabled) + return; + + // Mark command processing as enabled + command_processing_enabled = true; + + // Start processing commands, waiting for the bus to be idle + si_command_process(true); +} + +void si_command_processing_disable() +{ + if (!command_processing_enabled) + return; + + // Mark command processing as disabled + command_processing_enabled = false; +} \ No newline at end of file diff --git a/firmware/libsi/src/device/gc_controller.c b/firmware/libsi/src/device/gc_controller.c index e4f7374..fa98c44 100644 --- a/firmware/libsi/src/device/gc_controller.c +++ b/firmware/libsi/src/device/gc_controller.c @@ -1,6 +1,6 @@ #include -#include "si/commands.h" +#include "si/device/commands.h" #include "si/device/gc_controller.h" /* @@ -74,7 +74,7 @@ static uint8_t *pack_input_state(struct si_device_gc_input_state *src, uint8_t a * Command: {0x00} * Response: A 3-byte device info. */ -static int handle_info(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_info(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -90,7 +90,7 @@ static int handle_info(const uint8_t *command, si_callback_fn callback, void *co * Command: {0xFF} * Response: A 3-byte device info. */ -static int handle_reset(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_reset(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -108,7 +108,7 @@ static int handle_reset(const uint8_t *command, si_callback_fn callback, void *c * Command: {0x40, analog_mode, motor_state} * Response: An 8-byte packed input state, see `pack_input_state` for details */ -static int handle_short_poll(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_short_poll(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -150,7 +150,7 @@ static int handle_short_poll(const uint8_t *command, si_callback_fn callback, vo * Command: {0x41} * Response: A 10-byte input state representing the current origin. */ -static int handle_read_origin(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_read_origin(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -174,7 +174,7 @@ static int handle_read_origin(const uint8_t *command, si_callback_fn callback, v * Command: {0x42, 0x00, 0x00} * Response: A 10-byte input state representing the current origin. */ -static int handle_calibrate(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_calibrate(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -205,7 +205,7 @@ static int handle_calibrate(const uint8_t *command, si_callback_fn callback, voi * * NOTE: This command is not used by any games, but is included for completeness. */ -static int handle_long_poll(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_long_poll(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -238,7 +238,7 @@ static int handle_long_poll(const uint8_t *command, si_callback_fn callback, voi * Command: {0x4D, 0x??, 0x??} - 2nd and 3rd bytes seem to differ every time * Response: 8 bytes of zeroes. */ -static int handle_probe_device(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_probe_device(const uint8_t *command, si_complete_cb_t callback, void *context) { // Respond with 8 bytes of zeroes uint8_t response[8] = {0}; @@ -255,7 +255,7 @@ static int handle_probe_device(const uint8_t *command, si_callback_fn callback, * Command: {0x4E, wireless_id_h | SI_WIRELESS_FIX_ID, wireless_id_l} * Response: A 3-byte device info. */ -static int handle_fix_device(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_fix_device(const uint8_t *command, si_complete_cb_t callback, void *context) { struct si_device_gc_controller *device = (struct si_device_gc_controller *)context; @@ -302,11 +302,11 @@ void si_device_gc_init(struct si_device_gc_controller *device, uint8_t type) // Register the SI commands handled by GameCube controllers si_command_register(SI_CMD_INFO, SI_CMD_INFO_LEN, handle_info, device); + si_command_register(SI_CMD_RESET, SI_CMD_RESET_LEN, handle_reset, device); si_command_register(SI_CMD_GC_SHORT_POLL, SI_CMD_GC_SHORT_POLL_LEN, handle_short_poll, device); si_command_register(SI_CMD_GC_READ_ORIGIN, SI_CMD_GC_READ_ORIGIN_LEN, handle_read_origin, device); si_command_register(SI_CMD_GC_CALIBRATE, SI_CMD_GC_CALIBRATE_LEN, handle_calibrate, device); si_command_register(SI_CMD_GC_LONG_POLL, SI_CMD_GC_LONG_POLL_LEN, handle_long_poll, device); - si_command_register(SI_CMD_RESET, SI_CMD_RESET_LEN, handle_reset, device); // Register additional commands handled by WaveBird receivers if (type & SI_GC_WIRELESS) { diff --git a/firmware/libsi/src/platform/efr32/si_efr32_emlib.c b/firmware/libsi/src/platform/efr32/si_efr32_emlib.c index a4f92c5..3168e87 100644 --- a/firmware/libsi/src/platform/efr32/si_efr32_emlib.c +++ b/firmware/libsi/src/platform/efr32/si_efr32_emlib.c @@ -8,7 +8,7 @@ #include "dmadrv.h" -#include "si/commands.h" +#include "si/device/commands.h" #ifdef __ZEPHYR__ #include @@ -96,8 +96,9 @@ static unsigned int tx_dma_channel; // Transfer state static struct { uint8_t *data; - uint8_t length; - si_callback_fn callback; + uint8_t max_length; + si_byte_cb_t byte_callback; + si_complete_cb_t complete_callback; } si_xfer; // RX LDMA configuration @@ -147,12 +148,13 @@ void si_init(uint8_t port, uint8_t pin, uint8_t mode, uint32_t rx_freq, uint32_t si_mode = mode; } -void si_write_bytes(const uint8_t *bytes, uint8_t length, si_callback_fn callback) +void si_write_bytes(const uint8_t *bytes, uint8_t length, si_complete_cb_t callback) { // Save the transfer state - si_xfer.data = (uint8_t *)bytes; - si_xfer.length = length; - si_xfer.callback = callback; + si_xfer.data = (uint8_t *)bytes; + si_xfer.max_length = length; + si_xfer.byte_callback = NULL; + si_xfer.complete_callback = callback; // Convert the bytes to appropriate line coding and add to the buffer uint8_t *buf_ptr = tx_buffer; @@ -169,12 +171,13 @@ void si_write_bytes(const uint8_t *bytes, uint8_t length, si_callback_fn callbac DMADRV_LdmaStartTransfer(tx_dma_channel, &ldma_tx_config, ldma_tx_descriptors, NULL, NULL); } -void si_read_bytes(uint8_t *buffer, uint8_t length, si_callback_fn callback) +void si_read_bytes(uint8_t *buffer, uint8_t max_length, si_byte_cb_t byte_callback, si_complete_cb_t complete_callback) { // Save the transfer state - si_xfer.data = buffer; - si_xfer.length = length; - si_xfer.callback = callback; + si_xfer.data = buffer; + si_xfer.max_length = max_length; + si_xfer.byte_callback = byte_callback; + si_xfer.complete_callback = complete_callback; // Clear the RX buffer while (TIMER_CaptureGet(SI_RX_TIMER, 0)) @@ -187,11 +190,6 @@ void si_read_bytes(uint8_t *buffer, uint8_t length, si_callback_fn callback) DMADRV_LdmaStartTransfer(rx_dma_channel, &ldma_rx_config, ldma_rx_descriptors, ldma_callback_rx, NULL); } -void si_read_command(uint8_t *buffer, si_callback_fn callback) -{ - si_read_bytes(buffer, 0, callback); -} - void si_await_bus_idle(void) { // Start the timer @@ -199,6 +197,7 @@ void si_await_bus_idle(void) while (1) { // Wait for the line to go high + // TODO: Add a timeout while (GPIO_PinInGet(si_data_port, si_data_pin) == 0) ; @@ -206,6 +205,7 @@ void si_await_bus_idle(void) TIMER_CounterSet(SI_RX_TIMER, 0); // Wait for either the bus idle period to elapse or line to go low + // TODO: Add a timeout while (GPIO_PinInGet(si_data_port, si_data_pin) == 1) { if (TIMER_CounterGet(SI_RX_TIMER) >= rx_bus_idle_period) goto idle_detected; @@ -340,32 +340,20 @@ static bool ldma_callback_rx(unsigned int chan, unsigned int iteration, void *us // Process the received pulses into the byte buffer decode_edge_timings(&si_xfer.data[byte_idx], rx_edge_timings[byte_idx % 2]); - // If this is the first byte, determine how many bytes are expected - if (si_xfer.length == 0 && iteration == 1) { - si_xfer.length = si_command_get_length(si_xfer.data[0]); - - // Unknown command, stop the transfer - if (si_xfer.length == 0) { - // Don't clock in any more data - TIMER_Enable(SI_RX_TIMER, false); - - // Call the transfer callback if one is set - if (si_xfer.callback) - si_xfer.callback(-SI_ERR_UNKNOWN_COMMAND); - - // Stop the LDMA chain - return false; - } + // Call the byte callback if provided + bool continue_transfer = true; + if (si_xfer.byte_callback) { + continue_transfer = si_xfer.byte_callback(si_xfer.data[byte_idx], byte_idx); } - // We have all the bytes we expected - if (iteration == si_xfer.length) { - // Don't clock in any more data + // If we have reached the maximum length, stop the transfer + if (!continue_transfer || byte_idx == si_xfer.max_length - 1) { + // Stop the timer TIMER_Enable(SI_RX_TIMER, false); - // Call the transfer callback if one is set - if (si_xfer.callback) - si_xfer.callback(0); + // Call the complete callback if provided + if (si_xfer.complete_callback) + si_xfer.complete_callback(byte_idx + 1); // Stop the LDMA chain return false; @@ -382,7 +370,7 @@ void SI_TX_USART_IRQHandler() uint32_t flags = USART_IntGet(SI_TX_USART); USART_IntClear(SI_TX_USART, flags); - // Call the transfer callback if one is set - if (si_xfer.callback) - si_xfer.callback(0); + // Call the transfer callback with the number of bytes written + if (si_xfer.complete_callback) + si_xfer.complete_callback(si_xfer.max_length); } \ No newline at end of file diff --git a/firmware/libsi/test/test_commands.c b/firmware/libsi/test/test_commands.c index 107301e..9de632a 100644 --- a/firmware/libsi/test/test_commands.c +++ b/firmware/libsi/test/test_commands.c @@ -1,6 +1,6 @@ #include "unity.h" -#include "si/commands.h" +#include "si/device/commands.h" #include "si/si.h" // Stub functions @@ -8,31 +8,37 @@ void si_await_bus_idle(void) { } -static int handle_info(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_info(const uint8_t *command, si_complete_cb_t callback, void *context) { return 3; } -static int handle_reset(const uint8_t *command, si_callback_fn callback, void *context) +static int handle_reset(const uint8_t *command, si_complete_cb_t callback, void *context) { return 3; } static void test_register_command() { + struct si_command *cmd = NULL; + si_command_register(0x00, 1, handle_info, NULL); - TEST_ASSERT_EQUAL(1, si_command_get_length(0x00)); - TEST_ASSERT_EQUAL(handle_info, si_command_get_handler(0x00)); + cmd = si_command_find_by_id(0x00); + TEST_ASSERT_NOT_NULL(cmd); + TEST_ASSERT_EQUAL(1, cmd->length); + TEST_ASSERT_EQUAL(handle_info, cmd->handler); si_command_register(0xFF, 3, handle_reset, NULL); - TEST_ASSERT_EQUAL(3, si_command_get_length(0xFF)); - TEST_ASSERT_EQUAL(handle_reset, si_command_get_handler(0xFF)); + cmd = si_command_find_by_id(0xFF); + TEST_ASSERT_NOT_NULL(cmd); + TEST_ASSERT_EQUAL(3, cmd->length); + TEST_ASSERT_EQUAL(handle_reset, cmd->handler); } static void test_register_command_missing() { - TEST_ASSERT_EQUAL(0, si_command_get_length(0x69)); - TEST_ASSERT_NULL(si_command_get_handler(0x69)); + struct si_command *cmd = si_command_find_by_id(0x69); + TEST_ASSERT_NULL(cmd); } void test_commands(void) diff --git a/firmware/libsi/test/test_gc_controller.c b/firmware/libsi/test/test_gc_controller.c index 2cfc691..882cfc7 100644 --- a/firmware/libsi/test/test_gc_controller.c +++ b/firmware/libsi/test/test_gc_controller.c @@ -3,7 +3,7 @@ #include "unity.h" -#include "si/commands.h" +#include "si/device/commands.h" #include "si/device/gc_controller.h" #include "si/si.h" @@ -11,21 +11,21 @@ static uint8_t response_buf[SI_BLOCK_SIZE] = {0}; static uint8_t response_len = 0; -void si_write_bytes(const uint8_t *data, uint8_t length, si_callback_fn callback) +void si_write_bytes(const uint8_t *data, uint8_t length, si_complete_cb_t callback) { memcpy(response_buf, data, length); response_len = length; } -void si_read_command(uint8_t *data, si_callback_fn callback) +void si_read_bytes(uint8_t *buffer, uint8_t max_length, si_byte_cb_t byte_callback, si_complete_cb_t complete_callback) { } // Simulate receiving a command static int simulate_command(struct si_device_gc_controller *device, uint8_t *command) { - si_command_handler_fn handler = si_command_get_handler(command[0]); - return handler(command, NULL, device); + struct si_command *cmd = si_command_find_by_id(command[0]); + return cmd->handler(command, NULL, device); } // Test that the device info response is correct for a standard GameCube controller diff --git a/firmware/receiver/src/main.c b/firmware/receiver/src/main.c index f0375a9..d5d140b 100644 --- a/firmware/receiver/src/main.c +++ b/firmware/receiver/src/main.c @@ -5,7 +5,7 @@ #include "em_cmu.h" #include "em_gpio.h" -#include "si/commands.h" +#include "si/device/commands.h" #include "si/device/gc_controller.h" #include "wavebird/message.h" #include "wavebird/packet.h" @@ -78,7 +78,6 @@ struct { // SI state static struct si_device_gc_controller si_device = {0}; -static bool enable_si_command_handling = true; // Buttons, switches, and LEDs static struct led *status_led = NULL; @@ -107,7 +106,7 @@ static void initialize_controller(uint8_t controller_type) if (controller_type == WP_CONT_TYPE_GC_WAVEBIRD) { // Present as an OEM WaveBird receiver si_device_gc_init(&si_device, SI_TYPE_GC | SI_GC_WIRELESS | SI_GC_NOMOTOR); - enable_si_command_handling = true; + si_command_processing_enable(); } else if (controller_type == WP_CONT_TYPE_GC_WIRED_NOMOTOR) { // Present as a wired GameCube controller without rumble si_device_gc_init(&si_device, SI_TYPE_GC | SI_GC_STANDARD | SI_GC_NOMOTOR); @@ -208,7 +207,7 @@ static void handle_wavebird_packet(const uint8_t *packet) memcpy(&si_device.input.stick_x, &message[4], 6); // We have a good input state, enable SI command handling if it was disabled - enable_si_command_handling = true; + si_command_processing_enable(); // Set the input state as valid input_valid_until = millis + INPUT_VALID_MS; @@ -246,7 +245,7 @@ static void handle_pairing_started(void) pairing_active = true; // Disable SI command handling during pairing - enable_si_command_handling = false; + si_command_processing_disable(); // Set the LED effect to indicate pairing mode if (status_led) @@ -281,7 +280,7 @@ static void handle_pairing_finished(uint8_t status, uint8_t channel) led_effect_blink(status_led, 500, 3); // Immediately reenable SI command handling - enable_si_command_handling = true; + si_command_processing_enable(); } else { DEBUG_PRINT("Pairing cancelled\n"); @@ -290,7 +289,7 @@ static void handle_pairing_finished(uint8_t status, uint8_t channel) led_off(status_led); // Immediately reenable SI command handling - enable_si_command_handling = true; + si_command_processing_enable(); } } @@ -421,15 +420,8 @@ int main(void) : "Wired (no motor)"); DEBUG_PRINT("\n"); - // Wait for the SI bus to be idle before starting the main loop - si_await_bus_idle(); - // Main loop while (1) { - // Check if we need to initiate the next SI transfer - if (enable_si_command_handling) - si_command_process(); - // Check for new wavebird packets wavebird_radio_process();