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>
150 lines
4.5 KiB
Python
Executable File
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()
|