From 58a2d37b387c8d5ce13cac3523f801a6c1ac55d2 Mon Sep 17 00:00:00 2001 From: lucavanstraaten Date: Tue, 28 Apr 2026 21:03:59 +0200 Subject: [PATCH] Move SSI timing-critical read to core 1 via FIFO - Replace hardware/sync.h with pico/multicore.h - Implement ssi_read_core1(), setup1(), loop1() on core 1 - Core 1 owns the SSI bit-bang loop; no USB/timer IRQs interfere - Core 0 sends packed request word over FIFO, receives lo32/hi32/duration - Add requestSsiRead() helper on core 0 side - Remove noInterrupts / save_and_disable_interrupts (no longer needed) - Update startup banner to reflect dual-core architecture --- src/main.cpp | 100 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index bf90961..1ab47fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include // CLOCK module (TX) const uint8_t TX_DI = 0; @@ -23,47 +23,86 @@ Adafruit_NeoPixel led(1, LED_PIN, NEO_GRB + NEO_KHZ800); 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; +}; + void setStatus(uint8_t r, uint8_t g, uint8_t b) { led.setPixelColor(0, led.Color(r, g, b)); led.show(); } -// SSI read with direct register I/O and interrupts disabled. -// Single-cycle GPIO ops (~8 ns each) instead of ~1 us digitalWrite(). -// Interrupts off so USB/timer IRQs can't insert delays mid-frame. -uint64_t ssi_read(uint8_t bits, uint16_t half_us) { +// ========================================================================= +// CORE 1: SSI worker +// ========================================================================= + +uint64_t ssi_read_core1(uint8_t bits, uint16_t half_us) { uint64_t value = 0; - // Save interrupt state and disable. save_and_disable_interrupts() returns - // the previous state so we can restore it exactly (don't just blindly - // re-enable - a caller higher up the stack may have wanted them off). - uint32_t irq_state = save_and_disable_interrupts(); - - // Frame start: clock idles HIGH, drop to LOW = encoder latches position - sio_hw->gpio_clr = TX_DI_MASK; + sio_hw->gpio_clr = TX_DI_MASK; // first falling edge: latch busy_wait_us_32(half_us); for (uint8_t i = 0; i < bits; i++) { - sio_hw->gpio_set = TX_DI_MASK; // rising edge: encoder shifts bit + sio_hw->gpio_set = TX_DI_MASK; // rising: encoder shifts busy_wait_us_32(half_us); - sio_hw->gpio_clr = TX_DI_MASK; // falling edge: sample now + 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); } - // Return clock to idle HIGH - sio_hw->gpio_set = TX_DI_MASK; - - // Re-enable interrupts before the (long) monoflop wait - no need to - // hold them off any longer, the timing-critical part is done. - restore_interrupts(irq_state); - - busy_wait_us_32(30); // monoflop reset time + sio_hw->gpio_set = TX_DI_MASK; // back to idle + busy_wait_us_32(30); // monoflop return value; } +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 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 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; +} + void handleCommand(const String& cmd) { if (!cmd.startsWith("READ ")) { Serial.println("ERR unknown command. Use: READ "); @@ -90,13 +129,13 @@ void handleCommand(const String& cmd) { } setStatus(0, 0, 16); // blue = reading - uint32_t t0 = micros(); - uint64_t raw = ssi_read(bits, halfUs); - uint32_t elapsed = micros() - t0; + uint64_t value; + uint32_t took; + requestSsiRead((uint8_t)bits, (uint16_t)halfUs, value, took); setStatus(0, 16, 0); // green = idle Serial.printf("OK bits=%d half_us=%d hex=0x%llX dec=%llu took=%luus\n", - bits, halfUs, raw, raw, elapsed); + bits, halfUs, value, value, took); } void setup() { @@ -110,18 +149,17 @@ void setup() { pinMode(RX_DI, OUTPUT); pinMode(RX_RO, INPUT); - digitalWrite(TX_DE, HIGH); // CLK module = transmit + digitalWrite(TX_DE, HIGH); digitalWrite(TX_RE, HIGH); - digitalWrite(RX_DE, LOW); // DATA module = receive + digitalWrite(RX_DE, LOW); digitalWrite(RX_RE, LOW); digitalWrite(RX_DI, LOW); - digitalWrite(TX_DI, HIGH); // SSI clock idles HIGH + digitalWrite(TX_DI, HIGH); // SSI idle HIGH delay(200); while (!Serial && millis() < 3000) { delay(10); } - Serial.println("\nSSI bridge ready (B+C: direct registers, IRQs off during read)"); + Serial.println("\nSSI bridge ready (dual-core: core 1 dedicated to SSI)"); Serial.println("Send: READ "); - Serial.println("Example: READ 25 5"); setStatus(0, 16, 0); }