Files
basic1/upload_game.py
Adolfo Reyna 84b009c33e 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>
2026-02-12 22:52:57 -05:00

150 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Lua Game Uploader for RP2350
Rapidly upload and execute Lua games via USB serial for quick iteration.
Usage:
python upload_game.py <lua_file> [serial_port]
Example:
python upload_game.py my_game.lua /dev/ttyACM0
python upload_game.py my_game.lua COM3
"""
import sys
import os
import base64
import serial
import time
from pathlib import Path
def find_serial_port():
"""Auto-detect the RP2350 serial port"""
import serial.tools.list_ports
# Look for Pico/RP2350 devices
ports = list(serial.tools.list_ports.comports())
for port in ports:
# Common VID/PID for Raspberry Pi Pico
if 'Pico' in port.description or \
'RP2350' in port.description or \
'RP2040' in port.description or \
(port.vid == 0x2E8A): # Raspberry Pi VID
return port.device
# Fallback: return first available port
if ports:
print(f"Warning: Could not find RP2350, using first available port: {ports[0].device}")
return ports[0].device
return None
def upload_game(lua_file, serial_port=None):
"""Upload a Lua game file to the RP2350 and execute it"""
# Check if file exists
if not os.path.exists(lua_file):
print(f"Error: File '{lua_file}' not found")
return False
# Read the file
with open(lua_file, 'rb') as f:
file_data = f.read()
file_size = len(file_data)
filename = os.path.basename(lua_file)
# Ensure filename has .lua extension
if not filename.endswith('.lua'):
filename += '.lua'
print(f"Uploading {filename} ({file_size} bytes)...")
# Auto-detect serial port if not provided
if serial_port is None:
serial_port = find_serial_port()
if serial_port is None:
print("Error: No serial port found. Please specify manually.")
return False
print(f"Using serial port: {serial_port}")
try:
# Open serial connection
ser = serial.Serial(serial_port, 115200, timeout=5)
time.sleep(0.1) # Wait for connection to stabilize
# Encode file data as base64
base64_data = base64.b64encode(file_data).decode('ascii')
# Send upload command
command = f"UPLOAD {filename} {file_size}\n"
print(f"Sending command: {command.strip()}")
ser.write(command.encode('ascii'))
# Send base64-encoded file data
print("Sending file data...")
ser.write(base64_data.encode('ascii'))
ser.write(b'\n')
# Send END marker
ser.write(b'END\n')
print("Upload complete, waiting for confirmation...")
# Read response from device
start_time = time.time()
response_lines = []
while time.time() - start_time < 10: # 10 second timeout
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8', errors='ignore').strip()
if line:
print(f" << {line}")
response_lines.append(line)
# Check for success
if line.startswith('OK'):
print(f"✓ File written successfully!")
if line.startswith('LAUNCHED'):
game_name = line.split(' ', 1)[1] if ' ' in line else filename
print(f"✓ Game '{game_name}' launched!")
ser.close()
return True
# Check for error
if line.startswith('ERROR'):
print(f"✗ Upload failed: {line}")
ser.close()
return False
print("Warning: No response received from device")
ser.close()
return False
except serial.SerialException as e:
print(f"Serial error: {e}")
return False
except Exception as e:
print(f"Unexpected error: {e}")
return False
def main():
if len(sys.argv) < 2:
print(__doc__)
print("\nAvailable serial ports:")
try:
import serial.tools.list_ports
for port in serial.tools.list_ports.comports():
print(f" {port.device}: {port.description}")
except ImportError:
print(" (install pyserial to see available ports)")
sys.exit(1)
lua_file = sys.argv[1]
serial_port = sys.argv[2] if len(sys.argv) > 2 else None
success = upload_game(lua_file, serial_port)
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()