From ed5b08fe58a724d52b32b4964a4c98cfc89132a3 Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Mon, 18 May 2026 16:27:02 +0200 Subject: [PATCH 01/10] Spooky entry and autodetect values --- src/dmdreader.cpp | 7 ++++++- src/dmdreader.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index c5327f5..f9cd6bd 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -384,12 +384,17 @@ DmdType detect_dmd() { (rclk < 4850) && (rdata > 135) && (rdata < 155)) { return DMD_SLEIC; + // Spooky -> DOTCLK: 3211000 | RCLK: 25100 | RDATA: 784 + } else if ((dotclk > 3100000) && (dotclk < 3300000) && (rclk > 24000) && + (rclk < 26000) && (rdata > 750) && (rdata < 820)) { + return DMD_SPOOKY; + // Capcom -> DOTCLK: 4168000 | RCLK: 16280 | RDATA: 510 } else if ((dotclk > 4000000) && (dotclk < 4300000) && (rclk > 16000) && (rclk < 16500) && (rdata > 490) && (rdata < 530)) { return DMD_CAPCOM; - // Capcom HD -> DOTCLK: 4168000 | RCLK: 16280 | RDATA: 255 + // Capcom HD & ROMSTAR -> DOTCLK: 4168000 | RCLK: 16280 | RDATA: 255 } else if ((dotclk > 3900000) && (dotclk < 4300000) && (rclk > 15500) && (rclk < 16500) && (rdata > 240) && (rdata < 270)) { return DMD_CAPCOM_HD; diff --git a/src/dmdreader.h b/src/dmdreader.h index d1cf3e1..21e19b8 100644 --- a/src/dmdreader.h +++ b/src/dmdreader.h @@ -32,6 +32,7 @@ enum DmdType : uint8_t { DMD_HOMEPIN, DMD_SPINBALL, DMD_SLEIC, + DMD_SPOOKY, // CAPCOM needs to be the last two entries: DMD_CAPCOM, DMD_CAPCOM_HD, From f9655fea0ebac8aeb84978476bb8c87d90283cde Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Mon, 18 May 2026 17:11:44 +0200 Subject: [PATCH 02/10] experimental Spooky support --- src/dmdreader.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index f9cd6bd..479a358 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -61,7 +61,7 @@ DmdType dmd_type; #define MAX_WIDTH 256 #define MAX_HEIGHT 64 #define MAX_BITSPERPIXEL 4 -#define MAX_PLANESPERFRAME 6 +#define MAX_PLANESPERFRAME 15 #define MAX_OVERSAMPLING LINEOVERSAMPLING_4X // Use uint16_t for all of these variables to erase calculations: @@ -1260,6 +1260,25 @@ bool dmdreader_init(bool return_on_no_detection) { break; } + case DMD_SPOOKY: { + uint input_pins[] = {RDATA, DE, DOTCLK}; + dmdreader_programs_init(&dmd_reader_wpc_program, + dmd_reader_wpc_program_get_default_config, + &dmd_framedetect_wpc_program, + dmd_framedetect_wpc_program_get_default_config, + input_pins, 3, 0, SDATA); + + source_width = 128; + source_height = 32; + source_bitsperpixel = 4; + target_bitsperpixel = 4; + source_planesperframe = 15; + source_planehistoryperframe = 14; + source_lineoversampling = LINEOVERSAMPLING_NONE; + source_mergeplanes = MERGEPLANES_ADD; + break; + } + case DMD_CAPCOM: { uint input_pins[] = {RDATA, RCLK}; dmdreader_programs_init(&dmd_reader_capcom_program, From cc1d5c299b2bef56eeb1b53d34de271359304727 Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Tue, 19 May 2026 01:07:15 +0200 Subject: [PATCH 03/10] spooky gets its own reader --- src/dmd_interface_spooky.pio | 40 ++++++++++++++++++++++++++++++++++++ src/dmdreader.cpp | 12 +++++------ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 src/dmd_interface_spooky.pio diff --git a/src/dmd_interface_spooky.pio b/src/dmd_interface_spooky.pio new file mode 100644 index 0000000..c6b9ef6 --- /dev/null +++ b/src/dmd_interface_spooky.pio @@ -0,0 +1,40 @@ +.define DE 7 +.define RDATA 6 +.define RCLK 5 +.define COLLAT 4 +.define DOTCLK 3 +.define SDATA 2 +.define FRAME_START_IRQ 5 + +.program dmd_reader_spooky + ; initialize y with 16383, number of pixels (128x32x4) - 1 because counting starts at 0. + set x, 31 ; x = 31 (max 5-bit value) + in x, 5 ; shift in 5 bits, isr = 31 + set x, 31 ; x = 31 + in x, 5 ; shift in 5 bits, isr = 1023 + set x, 15 ; x = 15 + in x, 4 ; shift in 4 bits, isr = 16383 + mov y, isr ; y = 16383 + +.wrap_target + mov x, y ; load number of pixels + mov isr, null ; clear ISR and reset shift counter + + irq clear FRAME_START_IRQ + wait irq FRAME_START_IRQ + +dotloop: + wait 0 gpio DOTCLK ; falling edge + in null, 3 ; left padding with 3 zeros + wait 1 gpio DOTCLK ; raising edge + in pins 1 ; read pin data + jmp x-- dotloop +.wrap + +; Frame detection program runs in parallel to the reader program and signals the start of a new frame using an IRQ. +.program dmd_framedetect_spooky +.wrap_target + wait 0 gpio RDATA + wait 1 gpio RDATA + irq FRAME_START_IRQ +.wrap diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index 479a358..c4f792b 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -1262,18 +1262,18 @@ bool dmdreader_init(bool return_on_no_detection) { case DMD_SPOOKY: { uint input_pins[] = {RDATA, DE, DOTCLK}; - dmdreader_programs_init(&dmd_reader_wpc_program, - dmd_reader_wpc_program_get_default_config, - &dmd_framedetect_wpc_program, - dmd_framedetect_wpc_program_get_default_config, + dmdreader_programs_init(&dmd_reader_spooky_program, + dmd_reader_spooky_program_get_default_config, + &dmd_framedetect_spooky_program, + dmd_framedetect_spooky_program_get_default_config, input_pins, 3, 0, SDATA); source_width = 128; source_height = 32; source_bitsperpixel = 4; target_bitsperpixel = 4; - source_planesperframe = 15; - source_planehistoryperframe = 14; + source_planesperframe = 15; // Spooky quickly merges 15 planes -> 4bpp + source_planehistoryperframe = 0; source_lineoversampling = LINEOVERSAMPLING_NONE; source_mergeplanes = MERGEPLANES_ADD; break; From 24a53486ed821caad2c356b02cb01ca21609c397 Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Tue, 19 May 2026 01:09:36 +0200 Subject: [PATCH 04/10] spooky pio.h --- src/dmd_interface.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dmd_interface.h b/src/dmd_interface.h index e1b243b..e670602 100644 --- a/src/dmd_interface.h +++ b/src/dmd_interface.h @@ -17,6 +17,7 @@ #include "dmd_interface_gottlieb.pio.h" #include "dmd_interface_homepin.pio.h" #include "dmd_interface_sleic.pio.h" +#include "dmd_interface_spooky.pio.h" #include "dmd_interface_sega_hd.pio.h" #include "dmd_interface_whitestar.pio.h" #include "dmd_interface_wpc.pio.h" From dc5ee56bad21d65b8523798d02226d80778fb41a Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Wed, 20 May 2026 16:39:57 +0200 Subject: [PATCH 05/10] Spooky pio can be deleted because of new structure. --- src/dmd_interface_spooky.pio | 40 ------------------------------------ src/dmdreader.cpp | 16 +++++++-------- 2 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 src/dmd_interface_spooky.pio diff --git a/src/dmd_interface_spooky.pio b/src/dmd_interface_spooky.pio deleted file mode 100644 index c6b9ef6..0000000 --- a/src/dmd_interface_spooky.pio +++ /dev/null @@ -1,40 +0,0 @@ -.define DE 7 -.define RDATA 6 -.define RCLK 5 -.define COLLAT 4 -.define DOTCLK 3 -.define SDATA 2 -.define FRAME_START_IRQ 5 - -.program dmd_reader_spooky - ; initialize y with 16383, number of pixels (128x32x4) - 1 because counting starts at 0. - set x, 31 ; x = 31 (max 5-bit value) - in x, 5 ; shift in 5 bits, isr = 31 - set x, 31 ; x = 31 - in x, 5 ; shift in 5 bits, isr = 1023 - set x, 15 ; x = 15 - in x, 4 ; shift in 4 bits, isr = 16383 - mov y, isr ; y = 16383 - -.wrap_target - mov x, y ; load number of pixels - mov isr, null ; clear ISR and reset shift counter - - irq clear FRAME_START_IRQ - wait irq FRAME_START_IRQ - -dotloop: - wait 0 gpio DOTCLK ; falling edge - in null, 3 ; left padding with 3 zeros - wait 1 gpio DOTCLK ; raising edge - in pins 1 ; read pin data - jmp x-- dotloop -.wrap - -; Frame detection program runs in parallel to the reader program and signals the start of a new frame using an IRQ. -.program dmd_framedetect_spooky -.wrap_target - wait 0 gpio RDATA - wait 1 gpio RDATA - irq FRAME_START_IRQ -.wrap diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index 45ac911..b2343db 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -950,12 +950,12 @@ bool dmdreader_init(bool return_on_no_detection) { // Initialize DMD reader switch (dmd_type) { case DMD_WPC: { - uint input_pins[] = {RDATA, DE, DOTCLK}; + uint input_pins[] = {RDATA}; dmdreader_programs_init(&dmd_reader_2bpp_program, dmd_reader_2bpp_program_get_default_config, &dmd_framedetect_generic_program, dmd_framedetect_generic_program_get_default_config, - input_pins, 3, 0, SDATA); + input_pins, 1, 0, SDATA); // load 4096 - 1 pixels directly to TX fifo pio_sm_put(dmd_pio, dmd_sm, 4095); @@ -1294,12 +1294,12 @@ bool dmdreader_init(bool return_on_no_detection) { } case DMD_SPOOKY: { - uint input_pins[] = {RDATA, DE, DOTCLK}; - dmdreader_programs_init(&dmd_reader_spooky_program, - dmd_reader_spooky_program_get_default_config, - &dmd_framedetect_spooky_program, - dmd_framedetect_spooky_program_get_default_config, - input_pins, 3, 0, SDATA); + uint input_pins[] = {RDATA}; + dmdreader_programs_init(&dmd_reader_4bpp_program, + dmd_reader_4bpp_program_get_default_config, + &dmd_framedetect_generic_program, + dmd_framedetect_generic_program_get_default_config, + input_pins, 1, 0, SDATA); source_width = 128; source_height = 32; From b66a1730befb107fad4a97f75339078765488898 Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Wed, 20 May 2026 16:43:14 +0200 Subject: [PATCH 06/10] x16: use the created variable which holds the value instead of hardcoding --- src/dmdreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index b2343db..be50bdb 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -763,7 +763,7 @@ void dmd_dma_handler() { // merge the rows and convert from 4bpp to 2bpp with a LUT uint32_t *dst, *src1, *src2; dst = framebuf + 64; // start in the middle of 128x32 frame - src1 = framebuf + 511; // everything is stored from here onwards + src1 = framebuf + offset_x16; // everything is stored from here onwards src2 = src1 + source_dwordsperline; if (dmd_type == DMD_DE_X16_V1) { From c9881aa91331a1d2d385ce37a93c9c2bfc971990 Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Wed, 20 May 2026 16:47:25 +0200 Subject: [PATCH 07/10] initialize spooky osr with 15 planes of 128x32 - 1 pixel --- src/dmdreader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index be50bdb..46c9a2e 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -1301,6 +1301,9 @@ bool dmdreader_init(bool return_on_no_detection) { dmd_framedetect_generic_program_get_default_config, input_pins, 1, 0, SDATA); + // load 61440 - 1 pixels directly to TX fifo + pio_sm_put(dmd_pio, dmd_sm, 61439); + source_width = 128; source_height = 32; source_bitsperpixel = 4; From 84c7bebecc6ce37f17af82deb316ecce833cf1b2 Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Wed, 20 May 2026 17:01:09 +0200 Subject: [PATCH 08/10] remove debug LED line --- src/dmdreader.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index 46c9a2e..85aa93d 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -682,7 +682,6 @@ void dmd_dma_handler() { // detect sync. So we could avoid bitschifiting of the uint32_t value to // check every single pixel. if (dmd_type == DMD_CAPCOM && !locked_in && !plane0_shifted) { - digitalWrite(LED_BUILTIN, HIGH); uint8_t value = pixval & 0x0F; if (value == 2 && (planebuf[px] & 0x0F) != 1 && (planebuf[offset[2] + px] & 0x0F) != 1) { From 54bb3dd44d567b22b4a8aa36330141df11c80c5b Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Wed, 20 May 2026 18:10:40 +0200 Subject: [PATCH 09/10] spooky lock in algo --- src/dmdreader.cpp | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index 85aa93d..c3004ff 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -598,6 +598,14 @@ void dmd_dma_reset() { dmd_set_and_enable_new_dma_target(); } +// Skips exactly one plane by jumping out of the ongoing dotloop +void dmd_skip_plane() { + pio_sm_set_enabled(dmd_pio, dmd_sm, false); + dmd_dma_reset(); + pio_sm_exec(dmd_pio, dmd_sm, pio_encode_jmp(dmd_offset)); + pio_sm_set_enabled(dmd_pio, dmd_sm, true); +} + /** * @brief Handles DMD DMA requests by switching between the buffers * @@ -709,10 +717,29 @@ void dmd_dma_handler() { // An unsynchronized has been found. // Disable the state machine, clean the DMA channel and restart. // As a result, we will skip exactly one plane. - pio_sm_set_enabled(dmd_pio, dmd_sm, false); - dmd_dma_reset(); - pio_sm_exec(dmd_pio, dmd_sm, pio_encode_jmp(dmd_offset)); - pio_sm_set_enabled(dmd_pio, dmd_sm, true); + dmd_skip_plane(); + plane0_shifted = true; + } + } + + // SPOOKY has 15 valid planes and 1 garbage plane. + // The first valid plane always contains pixel data unless the frame is + // fully black, The last plane (16th) never contains valid data. Since we + // only record 15 planes (not to forget it is 4bpp after all), we detect + // misalignment by checking if plane 0 is empty but plane 1 contains data. + // If so, we skip one extra plane and lock into the stream. + if (dmd_type == DMD_SPOOKY && !locked_in && !plane0_shifted) { + uint8_t value = pixval & 0x0F; + if (value >= 1) { + if ((planebuf[px] & 0x0F) != 1 && + (planebuf[offset[1] + px] & 0x0F) == 1) { + // 0 in first plane but 1 in second plane + // skip one more plane and then lock-in + locked_in = true; + } + // as long as value >= 1 always skip a plane + // the above if statement will decide whether we lock in. + dmd_skip_plane(); plane0_shifted = true; } } From 54aa2333dd6c3afc387cf5b4553e1e22f6abf98b Mon Sep 17 00:00:00 2001 From: Jan Vos Date: Wed, 20 May 2026 19:22:56 +0200 Subject: [PATCH 10/10] adjust documentation --- src/dmdreader.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dmdreader.cpp b/src/dmdreader.cpp index c3004ff..3c94919 100644 --- a/src/dmdreader.cpp +++ b/src/dmdreader.cpp @@ -722,12 +722,12 @@ void dmd_dma_handler() { } } - // SPOOKY has 15 valid planes and 1 garbage plane. - // The first valid plane always contains pixel data unless the frame is - // fully black, The last plane (16th) never contains valid data. Since we - // only record 15 planes (not to forget it is 4bpp after all), we detect - // misalignment by checking if plane 0 is empty but plane 1 contains data. - // If so, we skip one extra plane and lock into the stream. + // SPOOKY has 15 valid planes plus 1 garbage plane. + // The first plane should contain lit pixels unless the frame is fully black. + // The garbage plane is fully black no matter what. + // Since only 15 planes are recorded (4bpp), we detect misalignment when the + // garbage plane appears as plane 0. If the next plane contains valid data, + // then one more plane must be skipped to lock in. if (dmd_type == DMD_SPOOKY && !locked_in && !plane0_shifted) { uint8_t value = pixval & 0x0F; if (value >= 1) {