Add serial upload tool for rapid Lua game iteration

Implements a complete serial upload workflow that allows uploading and
immediately testing Lua games via USB serial connection.

New Components:
- SerialUploader: Receives files via serial, writes to SD card
- upload_game.py: Python tool for sending files from host computer
- Protocol: Text-based with base64 encoding for reliability

Key Features:
- Uploads file to /games folder on SD card
- Overwrites existing files (FA_CREATE_ALWAYS)
- Auto-launches uploaded game immediately
- Proper memory cleanup (prevents Lua state conflicts)

SD Card Fixes:
- Fixed SPI speed management (12.5MHz for SD, 32MHz for display)
- Fixed SD write protocol (poll for data response token)
- Added speed switching wrappers around all FatFS operations
- Cleaned up excessive debug output

Game Launcher Improvements:
- Added clear_games() to prevent duplicate registrations
- Added cleanup in select_game_by_name() to delete old instances
- Added exact match priority in game selection
- LuaGameLoader now has clear_factory_data() for memory cleanup

Integration:
- Added serial_uploader to CMakeLists.txt
- Integrated into main loop in basic1.cpp
- Re-scans games after upload to pick up new files

Documentation:
- UPLOAD_TOOL.md: Usage instructions
- sd_card_best_practices.md: Critical lessons learned

Known Issues:
- Game launch after upload occasionally causes freeze (needs investigation)
- Display may not refresh properly after upload

Usage:
  python upload_game.py games/lua_examples/2048.lua /dev/tty.usbmodem101

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adolfo Reyna
2026-02-12 22:52:57 -05:00
parent b26f3bf775
commit 84b009c33e
14 changed files with 1222 additions and 34 deletions

View File

@@ -323,47 +323,69 @@ bool sd_card_read_blocks(uint32_t block_addr, uint32_t num_blocks, uint8_t *buff
}
bool sd_card_write_block(uint32_t block_addr, const uint8_t *buffer) {
if (!g_card_info.initialized || buffer == NULL) return false;
if (!g_card_info.initialized || buffer == NULL) {
return false;
}
// For non-SDHC cards, convert block address to byte address
if (g_card_info.type != SD_CARD_TYPE_SDHC) {
block_addr *= SD_BLOCK_SIZE;
}
sd_card_select();
// Send write command
uint8_t r1 = sd_card_send_command(SD_CMD24, block_addr);
if (r1 != SD_R1_READY) {
sd_card_deselect();
return false;
}
// Wait for card to be ready to receive data
uint32_t timeout_count = 0;
uint8_t ready_byte;
do {
ready_byte = sd_card_transfer(0xFF);
timeout_count++;
if (timeout_count > 1000) {
sd_card_deselect();
return false;
}
} while (ready_byte != 0xFF);
// Send start token
sd_card_transfer(SD_START_TOKEN);
// Write data block
for (int i = 0; i < SD_BLOCK_SIZE; i++) {
sd_card_transfer(buffer[i]);
}
// Send dummy CRC (2 bytes)
sd_card_transfer(0xFF);
sd_card_transfer(0xFF);
// Check data response
uint8_t response = sd_card_transfer(0xFF);
// Check data response - may need to read several bytes before getting response
uint8_t response = 0xFF;
for (int i = 0; i < 10; i++) {
response = sd_card_transfer(0xFF);
if (response != 0xFF) {
break;
}
}
if ((response & 0x1F) != SD_DATA_ACCEPTED) {
sd_card_deselect();
return false;
}
// Wait for card to finish writing
if (!sd_card_wait_ready(500)) {
sd_card_deselect();
return false;
}
sd_card_deselect();
return true;
}