diff --git a/src/main.cpp b/src/main.cpp index 1ab47fa..ac873bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,8 @@ #include #include -#include -#include +#include +#include +#include "ssi.pio.h" // generated from ssi.pio at build time // CLOCK module (TX) const uint8_t TX_DI = 0; @@ -17,90 +18,69 @@ const uint8_t RX_RO = 29; const uint8_t LED_PIN = 16; Adafruit_NeoPixel led(1, LED_PIN, NEO_GRB + NEO_KHZ800); -// Pre-computed bit masks for the SIO registers. -// Writing to gpio_set sets a pin HIGH atomically in one cycle. -// Writing to gpio_clr clears it LOW. Reading gpio_in gives all GPIO states. -const uint32_t TX_DI_MASK = 1u << TX_DI; -const uint32_t RX_RO_MASK = 1u << RX_RO; - -// Inter-core protocol -// Request: [bits:8 | half_us:16 | reserved:8] -> core 1 -// Response: [hi32][lo32][duration_us] -> core 0 -struct SsiRequest { - uint8_t bits; - uint16_t half_us; -}; - -struct SsiResponse { - uint64_t value; - uint32_t duration_us; -}; +PIO ssi_pio = pio0; +uint ssi_sm = 0; +uint ssi_offset = 0; void setStatus(uint8_t r, uint8_t g, uint8_t b) { led.setPixelColor(0, led.Color(r, g, b)); led.show(); } -// ========================================================================= -// CORE 1: SSI worker -// ========================================================================= +void ssi_pio_init(uint half_us) { + // pioasm generates these helpers in ssi.pio.h + ssi_offset = pio_add_program(ssi_pio, &ssi_master_program); -uint64_t ssi_read_core1(uint8_t bits, uint16_t half_us) { - uint64_t value = 0; + pio_sm_config c = ssi_master_program_get_default_config(ssi_offset); - sio_hw->gpio_clr = TX_DI_MASK; // first falling edge: latch - busy_wait_us_32(half_us); + // Side-set drives the CLK pin (TX_DI) + sm_config_set_sideset_pins(&c, TX_DI); - for (uint8_t i = 0; i < bits; i++) { - sio_hw->gpio_set = TX_DI_MASK; // rising: encoder shifts - busy_wait_us_32(half_us); - sio_hw->gpio_clr = TX_DI_MASK; // falling: sample - uint32_t bit = (sio_hw->gpio_in & RX_RO_MASK) ? 1 : 0; - value = (value << 1) | bit; - busy_wait_us_32(half_us); - } + // 'in pins, 1' samples starting at RX_RO + sm_config_set_in_pins(&c, RX_RO); + sm_config_set_in_shift(&c, false /* shift_left */, false /* autopush */, 32); - sio_hw->gpio_set = TX_DI_MASK; // back to idle - busy_wait_us_32(30); // monoflop + // 'out x, 32' pulls 32 bits from OSR; shift direction doesn't matter for full word + sm_config_set_out_shift(&c, true, false, 32); - return value; + // Clock divider: 1 PIO cycle = half_us / 2 microseconds + // (so that 2 PIO cycles = half_us microseconds = one CLK half-period) + float div = (float)clock_get_hz(clk_sys) * ((float)half_us / 2.0f) / 1e6f; + sm_config_set_clkdiv(&c, div); + + // Hand the CLK pin to PIO and set as output + pio_gpio_init(ssi_pio, TX_DI); + pio_sm_set_consecutive_pindirs(ssi_pio, ssi_sm, TX_DI, 1, true); + + pio_sm_init(ssi_pio, ssi_sm, ssi_offset, &c); + pio_sm_set_enabled(ssi_pio, ssi_sm, true); } -void setup1() { - // Core 1 setup: nothing to do, GPIOs already configured by core 0. - // Importantly: no Serial, no USB, no millis IRQ active here by default - // when running in this dual-core mode. +void ssi_pio_reconfigure_speed(uint half_us) { + pio_sm_set_enabled(ssi_pio, ssi_sm, false); + + // Drain any leftover words from FIFOs + pio_sm_clear_fifos(ssi_pio, ssi_sm); + + // Restart SM at the wrap target + pio_sm_restart(ssi_pio, ssi_sm); + pio_sm_clkdiv_restart(ssi_pio, ssi_sm); + + float div = (float)clock_get_hz(clk_sys) * ((float)half_us / 2.0f) / 1e6f; + pio_sm_set_clkdiv(ssi_pio, ssi_sm, div); + + // Jump SM back to the program start + pio_sm_exec(ssi_pio, ssi_sm, pio_encode_jmp(ssi_offset)); + + pio_sm_set_enabled(ssi_pio, ssi_sm, true); } -void loop1() { - // Block until core 0 sends a packed request word. - // Word layout: bits in upper 8, half_us in next 16, 8 unused - uint32_t req = rp2040.fifo.pop(); - uint8_t bits = (req >> 24) & 0xFF; - uint16_t half_us = (req >> 8) & 0xFFFF; +uint32_t ssi_pio_read(uint8_t bits) { + pio_sm_put_blocking(ssi_pio, ssi_sm, bits - 1); + uint32_t result = pio_sm_get_blocking(ssi_pio, ssi_sm); - uint32_t t0 = time_us_32(); - uint64_t value = ssi_read_core1(bits, half_us); - uint32_t took = time_us_32() - t0; - - // Push three words back: low32, high32, duration - rp2040.fifo.push((uint32_t)(value & 0xFFFFFFFF)); - rp2040.fifo.push((uint32_t)(value >> 32)); - rp2040.fifo.push(took); -} - -// ========================================================================= -// CORE 0: serial command handler -// ========================================================================= - -void requestSsiRead(uint8_t bits, uint16_t half_us, uint64_t& outValue, uint32_t& outTook) { - uint32_t req = ((uint32_t)bits << 24) | ((uint32_t)half_us << 8); - rp2040.fifo.push(req); - - uint32_t lo = rp2040.fifo.pop(); - uint32_t hi = rp2040.fifo.pop(); - outTook = rp2040.fifo.pop(); - outValue = ((uint64_t)hi << 32) | lo; + // ISR shifts left → first bit in MSB. Right-align. + return result >> (32 - bits); } void handleCommand(const String& cmd) { @@ -119,8 +99,8 @@ void handleCommand(const String& cmd) { int bits = cmd.substring(firstSpace + 1, secondSpace).toInt(); int halfUs = cmd.substring(secondSpace + 1).toInt(); - if (bits < 1 || bits > 64) { - Serial.println("ERR bits must be 1..64"); + if (bits < 1 || bits > 32) { + Serial.println("ERR bits must be 1..32 (PIO ISR limit)"); return; } if (halfUs < 1 || halfUs > 10000) { @@ -128,13 +108,15 @@ void handleCommand(const String& cmd) { return; } - setStatus(0, 0, 16); // blue = reading - uint64_t value; - uint32_t took; - requestSsiRead((uint8_t)bits, (uint16_t)halfUs, value, took); - setStatus(0, 16, 0); // green = idle + ssi_pio_reconfigure_speed(halfUs); - Serial.printf("OK bits=%d half_us=%d hex=0x%llX dec=%llu took=%luus\n", + setStatus(0, 0, 16); + uint32_t t0 = micros(); + uint32_t value = ssi_pio_read((uint8_t)bits); + uint32_t took = micros() - t0; + setStatus(0, 16, 0); + + Serial.printf("OK bits=%d half_us=%d hex=0x%lX dec=%lu took=%luus\n", bits, halfUs, value, value, took); } @@ -143,7 +125,6 @@ void setup() { led.begin(); setStatus(8, 8, 0); - pinMode(TX_DI, OUTPUT); pinMode(TX_DE, OUTPUT); pinMode(TX_RE, OUTPUT); pinMode(RX_DE, OUTPUT); pinMode(RX_RE, OUTPUT); pinMode(RX_DI, OUTPUT); @@ -154,11 +135,14 @@ void setup() { digitalWrite(RX_DE, LOW); digitalWrite(RX_RE, LOW); digitalWrite(RX_DI, LOW); - digitalWrite(TX_DI, HIGH); // SSI idle HIGH + + // TX_DI is owned by PIO - don't pinMode it + + ssi_pio_init(5); // default 5 µs half-period delay(200); while (!Serial && millis() < 3000) { delay(10); } - Serial.println("\nSSI bridge ready (dual-core: core 1 dedicated to SSI)"); + Serial.println("\nSSI bridge ready (PIO state machine, .pio assembled)"); Serial.println("Send: READ "); setStatus(0, 16, 0); } diff --git a/src/ssi.pio b/src/ssi.pio new file mode 100644 index 0000000..9378a29 --- /dev/null +++ b/src/ssi.pio @@ -0,0 +1,24 @@ +.program ssi_master +.side_set 1 + +; Side-set bit drives CLK. Idle high. +; +; Protocol: +; TX FIFO: one word = (bit_count - 1) +; RX FIFO: one word = captured bits, left-aligned in 32-bit word +; +; Timing: 1 PIO cycle = half of one CLK level period. +; Each CLK level = 2 cycles, full bit period = 4 cycles. + +.wrap_target + pull block side 1 ; wait for CPU command, CLK idle high + out x, 32 side 1 ; X = bit_count - 1 + nop side 0 ; first falling edge: encoder latches + nop side 0 ; latch settle (CLK still low) +bit_loop: + nop side 1 ; CLK rises: encoder shifts new bit + nop side 1 ; data settles + in pins, 1 side 0 ; CLK falls, sample + jmp x--, bit_loop side 0 ; CLK still low, loop until done + push block side 1 ; CLK back to idle, return result +.wrap \ No newline at end of file diff --git a/src/ssi.pio.h b/src/ssi.pio.h new file mode 100644 index 0000000..cf220e2 --- /dev/null +++ b/src/ssi.pio.h @@ -0,0 +1,51 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +// ---------- // +// ssi_master // +// ---------- // + +#define ssi_master_wrap_target 0 +#define ssi_master_wrap 8 +#define ssi_master_pio_version 0 + +static const uint16_t ssi_master_program_instructions[] = { + // .wrap_target + 0x90a0, // 0: pull block side 1 + 0x7020, // 1: out x, 32 side 1 + 0xa042, // 2: nop side 0 + 0xa042, // 3: nop side 0 + 0xb042, // 4: nop side 1 + 0xb042, // 5: nop side 1 + 0x4001, // 6: in pins, 1 side 0 + 0x0044, // 7: jmp x--, 4 side 0 + 0x9020, // 8: push block side 1 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program ssi_master_program = { + .instructions = ssi_master_program_instructions, + .length = 9, + .origin = -1, + .pio_version = ssi_master_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config ssi_master_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + ssi_master_wrap_target, offset + ssi_master_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} +#endif +