Skip to content

Commit 0e74ca2

Browse files
committed
Update USB config tool for fm and midi
1 parent 50f198a commit 0e74ca2

5 files changed

Lines changed: 499 additions & 9 deletions

File tree

src/device_config.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,24 @@
55
66
use crate::player::{DeviceConfigCmd, DeviceConfigEdit};
77
use crate::ui::DeviceConfigSnapshot;
8+
use usbsid_pico_config::protocol::{cfg as cfg_op, encode_packet};
89
use usbsid_pico_config::transport::Transport;
910
use usbsid_pico_config::Device;
1011

12+
/// Send a single fire-and-forget config opcode (no response expected).
13+
/// Used for the diagnostic / hardware action commands the crate doesn't
14+
/// expose as named wrappers (TEST_SID, RESET_USBSID, MIDI state, etc.).
15+
fn send_cfg_opcode<T: Transport>(
16+
dev: &mut Device<T>,
17+
cmd: u8,
18+
args: [u8; 4],
19+
) -> Result<(), String> {
20+
let packet = encode_packet(cmd, args);
21+
dev.transport_mut()
22+
.send(&packet)
23+
.map_err(|e| format!("send opcode {cmd:#04X}: {e}"))
24+
}
25+
1126
/// Execute `op` against the device wrapped by `transport`. Returns
1227
/// `Some(snapshot)` only for `Refresh` (and as an after-effect of
1328
/// auto-detect, since callers will want the resulting config). For
@@ -73,6 +88,85 @@ pub fn run<T: Transport>(
7388
std::thread::sleep(std::time::Duration::from_millis(3500));
7489
refresh(&mut dev).map(Some)
7590
}
91+
92+
DeviceConfigCmd::Confirm => {
93+
dev.confirm_config()
94+
.map_err(|e| format!("confirm_config: {e}"))?;
95+
refresh(&mut dev).map(Some)
96+
}
97+
98+
DeviceConfigCmd::DetectSids => {
99+
dev.detect_sids().map_err(|e| format!("detect_sids: {e}"))?;
100+
std::thread::sleep(std::time::Duration::from_millis(1500));
101+
refresh(&mut dev).map(Some)
102+
}
103+
104+
DeviceConfigCmd::DetectClones => {
105+
dev.detect_clones()
106+
.map_err(|e| format!("detect_clones: {e}"))?;
107+
std::thread::sleep(std::time::Duration::from_millis(2500));
108+
refresh(&mut dev).map(Some)
109+
}
110+
111+
DeviceConfigCmd::TestSid(which) => {
112+
let cmd = match *which {
113+
0 => cfg_op::TEST_ALLSIDS,
114+
1 => cfg_op::TEST_SID1,
115+
2 => cfg_op::TEST_SID2,
116+
3 => cfg_op::TEST_SID3,
117+
4 => cfg_op::TEST_SID4,
118+
other => return Err(format!("TestSid: invalid SID index {other}")),
119+
};
120+
send_cfg_opcode(&mut dev, cmd, [0, 0, 0, 0])?;
121+
Ok(None)
122+
}
123+
124+
DeviceConfigCmd::StopTests => {
125+
send_cfg_opcode(&mut dev, cfg_op::STOP_TESTS, [0, 0, 0, 0])?;
126+
Ok(None)
127+
}
128+
129+
DeviceConfigCmd::ResetUsbsid => {
130+
send_cfg_opcode(&mut dev, cfg_op::RESET_USBSID, [0, 0, 0, 0])?;
131+
// Device re-enumerates — drop the handle; the GUI will reconnect.
132+
Ok(None)
133+
}
134+
135+
DeviceConfigCmd::RestartBus => {
136+
send_cfg_opcode(&mut dev, cfg_op::RESTART_BUS, [0, 0, 0, 0])?;
137+
Ok(None)
138+
}
139+
140+
DeviceConfigCmd::RestartBusClk => {
141+
send_cfg_opcode(&mut dev, cfg_op::RESTART_BUS_CLK, [0, 0, 0, 0])?;
142+
Ok(None)
143+
}
144+
145+
DeviceConfigCmd::SyncPios => {
146+
send_cfg_opcode(&mut dev, cfg_op::SYNC_PIOS, [0, 0, 0, 0])?;
147+
Ok(None)
148+
}
149+
150+
DeviceConfigCmd::SocketDetect => {
151+
send_cfg_opcode(&mut dev, cfg_op::SOCKET_DETECT, [0, 0, 0, 0])?;
152+
std::thread::sleep(std::time::Duration::from_millis(500));
153+
refresh(&mut dev).map(Some)
154+
}
155+
156+
DeviceConfigCmd::MidiLoadState => {
157+
send_cfg_opcode(&mut dev, cfg_op::LOAD_MIDI_STATE, [0, 0, 0, 0])?;
158+
Ok(None)
159+
}
160+
161+
DeviceConfigCmd::MidiSaveState => {
162+
send_cfg_opcode(&mut dev, cfg_op::SAVE_MIDI_STATE, [0, 0, 0, 0])?;
163+
Ok(None)
164+
}
165+
166+
DeviceConfigCmd::MidiResetState => {
167+
send_cfg_opcode(&mut dev, cfg_op::RESET_MIDI_STATE, [0, 0, 0, 0])?;
168+
Ok(None)
169+
}
76170
}
77171
}
78172

@@ -106,8 +200,17 @@ fn apply_edit(cfg: &mut usbsid_pico_config::DeviceConfig, edit: DeviceConfigEdit
106200
Flipped(v) => cfg.flipped = v,
107201
Mixed(v) => cfg.mixed = v,
108202
FmoplEnabled(v) => cfg.protocols.fmopl_enabled = v,
203+
FmoplSidno(v) => cfg.protocols.fmopl_sidno = v,
109204
LockClockrate(v) => cfg.lock_clockrate = v,
110205
ExternalClock(v) => cfg.external_clock = v,
206+
LedEnabled(v) => cfg.led.enabled = v,
207+
LedIdleBreathe(v) => cfg.led.idle_breathe = v,
208+
RgbLedEnabled(v) => cfg.rgb_led.enabled = v,
209+
RgbLedIdleBreathe(v) => cfg.rgb_led.idle_breathe = v,
210+
RgbLedBrightness(v) => cfg.rgb_led.brightness = v,
211+
RgbLedSidToUse(v) => cfg.rgb_led.sid_to_use = v,
212+
NeedConfirmation(v) => cfg.need_confirmation = v,
213+
DisableChangeDetect(v) => cfg.disable_changedetect = v,
111214
}
112215
}
113216

src/main.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,28 @@ impl App {
13641364
));
13651365
}
13661366

1367+
Message::DeviceConfigAction(cmd) => {
1368+
use player::DeviceConfigCmd as C;
1369+
self.device_cfg_status = match &cmd {
1370+
C::Confirm => "Confirming config…".into(),
1371+
C::DetectSids => "Detecting SIDs…".into(),
1372+
C::DetectClones => "Detecting clones (≈2 s)…".into(),
1373+
C::TestSid(0) => "Test tone: all SIDs".into(),
1374+
C::TestSid(n) => format!("Test tone: SID{n}"),
1375+
C::StopTests => "Stopping test tones".into(),
1376+
C::ResetUsbsid => "Resetting USBSID — reconnecting…".into(),
1377+
C::RestartBus => "Restarting SID bus".into(),
1378+
C::RestartBusClk => "Restarting bus + clock".into(),
1379+
C::SyncPios => "Sync PIOs".into(),
1380+
C::SocketDetect => "Re-detecting sockets…".into(),
1381+
C::MidiLoadState => "Loading MIDI state".into(),
1382+
C::MidiSaveState => "Saving MIDI state".into(),
1383+
C::MidiResetState => "Resetting MIDI state".into(),
1384+
other => format!("Sending {other:?}…"),
1385+
};
1386+
let _ = self.cmd_tx.send(player::PlayerCmd::DeviceConfig(cmd));
1387+
}
1388+
13671389
Message::DeviceConfigResult(result) => match result {
13681390
Ok(snap) => {
13691391
self.device_cfg = Some(snap);

src/player/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,32 @@ pub enum DeviceConfigCmd {
7373
Reset,
7474
/// Trigger the firmware's full chip + SID detection.
7575
AutoDetect,
76+
/// PCB v1.5+: confirm pending config so sockets become active.
77+
Confirm,
78+
/// Probe SID positions (firmware-detected). Lightweight vs AutoDetect.
79+
DetectSids,
80+
/// Probe SID clone types (ARMSID / SKPico / FPGASID / etc).
81+
DetectClones,
82+
/// Play a test tone. 0 = all SIDs, 1..=4 = specific SID.
83+
TestSid(u8),
84+
/// Stop any running test tones.
85+
StopTests,
86+
/// Soft-reset the USBSID-Pico firmware (re-enumerates over USB).
87+
ResetUsbsid,
88+
/// Re-init the SID bus.
89+
RestartBus,
90+
/// Re-init the SID bus AND re-derive the clock.
91+
RestartBusClk,
92+
/// Re-sync the PIO state machines.
93+
SyncPios,
94+
/// Re-run the socket detect routine (PCB v1.5+ presence detection).
95+
SocketDetect,
96+
/// MIDI state: load the saved state from flash.
97+
MidiLoadState,
98+
/// MIDI state: persist the current state to flash.
99+
MidiSaveState,
100+
/// MIDI state: reset to factory MIDI state.
101+
MidiResetState,
76102
}
77103

78104
/// One discrete edit a Device-tab control can dispatch. Each variant maps
@@ -92,8 +118,21 @@ pub enum DeviceConfigEdit {
92118
Flipped(bool),
93119
Mixed(bool),
94120
FmoplEnabled(bool),
121+
/// FMopl SID selector. 0 = auto, 1..=4 = explicit SID.
122+
FmoplSidno(u8),
95123
LockClockrate(bool),
96124
ExternalClock(bool),
125+
// ── LED + RGB LED ──────────────────────────────────────────────
126+
LedEnabled(bool),
127+
LedIdleBreathe(bool),
128+
RgbLedEnabled(bool),
129+
RgbLedIdleBreathe(bool),
130+
RgbLedBrightness(u8),
131+
/// RGB LED → SID-to-use. -1 = off, 1..=4 = SID index.
132+
RgbLedSidToUse(i8),
133+
// ── PCB v1.5+ flags ────────────────────────────────────────────
134+
NeedConfirmation(bool),
135+
DisableChangeDetect(bool),
97136
}
98137

99138
/// Status updates sent from player thread → GUI.
@@ -1191,6 +1230,27 @@ fn handle_cmd(
11911230
let _ = device_cfg_tx.try_send(Err(e));
11921231
return;
11931232
}
1233+
// ── Quiesce playback before talking config ────────────────────
1234+
// The driver's threaded writer drains the ring buffer and
1235+
// locks the same USB endpoint our config commands use. If
1236+
// SID writes interleave with our SET_CONFIG packets the
1237+
// firmware can re-engage a socket we just disabled, or
1238+
// stall responding to READ_CONFIG — the "disable doesn't
1239+
// work / freezes" symptom reported via email.
1240+
//
1241+
// We're already on the player thread, so processing this
1242+
// command means no new frames are being pushed to the ring
1243+
// for the duration of the config op. Flushing the ring +
1244+
// a brief idle lets the writer thread drain whatever's
1245+
// still queued before we take over the endpoint. The next
1246+
// playback frame after we return will refill the ring.
1247+
if matches!(state, PlayState::Playing) {
1248+
if let Some(br) = bridge.as_mut() {
1249+
br.flush();
1250+
std::thread::sleep(std::time::Duration::from_millis(30));
1251+
}
1252+
}
1253+
11941254
let result = match bridge.as_mut() {
11951255
Some(br) => br.run_device_config(&op),
11961256
None => Err("device not initialised".into()),

0 commit comments

Comments
 (0)