Created
October 30, 2025 13:03
-
-
Save chr15m/922391517223cb3b9166547a70e09baa to your computer and use it in GitHub Desktop.
Control the SIDBlaster USB (SID chip controller) with Python via the ftdi_sio module
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| SIDBlaster USB - Serial Interface Demo | |
| - Demonstrates reading and writing SID registers via serial port. | |
| - The ftdi_sio module should be loaded and presenting on /dev/ttyUSB0. | |
| - Tested on SIDBlaster-USB Tic Tac on Linux. | |
| - Raspberry Pi should work too. | |
| """ | |
| import time | |
| import serial | |
| def read_sid_register(port, register): | |
| """Read a value from a SID register.""" | |
| read_command = 0x80 | (1 << 5) | register | |
| port.write(bytes([read_command])) | |
| response = port.read(1) | |
| if len(response) == 1: | |
| return response[0] | |
| return None | |
| def write_sid_register(port, register, value): | |
| """Write a value to a SID register.""" | |
| write_command = 0xC0 | (1 << 5) | register | |
| port.write(bytes([write_command, value])) | |
| time.sleep(0.001) | |
| def main(): | |
| "SIDBlaster USB - Serial Interface Demo" | |
| print(main.__doc__) | |
| print("=" * len(main.__doc__)) | |
| port = serial.Serial( | |
| port='/dev/ttyUSB0', | |
| baudrate=500000, | |
| bytesize=serial.EIGHTBITS, | |
| parity=serial.PARITY_NONE, | |
| stopbits=serial.STOPBITS_ONE, | |
| timeout=1 | |
| ) | |
| print(f"Opened {port.name} at {port.baudrate} baud\n") | |
| # Test 1: Read POTX register | |
| print("Test 1: Reading POTX register (25)") | |
| print("-" * 40) | |
| value = read_sid_register(port, 25) | |
| if value is not None: | |
| print(f"✅ Register 25 = 0x{value:02x} ({value})") | |
| if value == 0xFF: | |
| print("🎉 Correct! Unconnected paddle reads 0xFF\n") | |
| else: | |
| print(f"⚠️ Expected 0xFF but got 0x{value:02x}\n") | |
| else: | |
| print("❌ Failed to read register\n") | |
| # Test 2: Play middle C | |
| print("Test 2: Playing middle C (261.63 Hz)") | |
| print("-" * 40) | |
| middle_c_freq = 261.63 | |
| freq_value = int((middle_c_freq * 16777216) / 1000000) | |
| freq_low = freq_value & 0xFF | |
| freq_high = (freq_value >> 8) & 0xFF | |
| print(f"Frequency value: {freq_value} (0x{freq_value:04x})") | |
| print(f" Low byte: 0x{freq_low:02x}") | |
| print(f" High byte: 0x{freq_high:02x}") | |
| print("\nConfiguring SID Voice 1...") | |
| write_sid_register(port, 0x00, freq_low) # Frequency low byte | |
| write_sid_register(port, 0x01, freq_high) # Frequency high byte | |
| write_sid_register(port, 0x02, 0x00) # Pulse width low byte | |
| write_sid_register(port, 0x03, 0x08) # Pulse width high byte (50% duty cycle) | |
| write_sid_register(port, 0x05, 0x09) # Attack/Decay (fast attack, medium decay) | |
| write_sid_register(port, 0x06, 0x00) # Sustain/Release (zero sustain, fast release) | |
| write_sid_register(port, 0x18, 0x0F) # Volume to maximum | |
| print("Playing sawtooth wave for 250ms...") | |
| write_sid_register(port, 0x04, 0x21) # Gate on, sawtooth waveform | |
| time.sleep(0.25) | |
| write_sid_register(port, 0x04, 0x20) # Gate off | |
| time.sleep(0.1) | |
| print("Playing pulse wave for 250ms...") | |
| write_sid_register(port, 0x04, 0x41) # Gate on, pulse waveform | |
| time.sleep(0.25) | |
| write_sid_register(port, 0x04, 0x40) # Gate off | |
| print("Notes stopped") | |
| print("\nSilencing SID...") | |
| write_sid_register(port, 0x18, 0x00) # Volume to zero | |
| port.close() | |
| print("\nDone!") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment