Compare commits
2 commits
main
...
pio-implem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a618cb238f | ||
|
|
a8633962f2 |
3 changed files with 165 additions and 81 deletions
171
src/main.cpp
171
src/main.cpp
|
|
@ -1,7 +1,8 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Adafruit_NeoPixel.h>
|
#include <Adafruit_NeoPixel.h>
|
||||||
#include <hardware/structs/sio.h>
|
#include <hardware/pio.h>
|
||||||
#include <pico/multicore.h>
|
#include <hardware/clocks.h>
|
||||||
|
#include "ssi.pio.h" // generated from ssi.pio at build time
|
||||||
|
|
||||||
// CLOCK module (TX)
|
// CLOCK module (TX)
|
||||||
const uint8_t TX_DI = 0;
|
const uint8_t TX_DI = 0;
|
||||||
|
|
@ -17,94 +18,93 @@ const uint8_t RX_RO = 29;
|
||||||
const uint8_t LED_PIN = 16;
|
const uint8_t LED_PIN = 16;
|
||||||
Adafruit_NeoPixel led(1, LED_PIN, NEO_GRB + NEO_KHZ800);
|
Adafruit_NeoPixel led(1, LED_PIN, NEO_GRB + NEO_KHZ800);
|
||||||
|
|
||||||
// Pre-computed bit masks for the SIO registers.
|
PIO ssi_pio = pio0;
|
||||||
// Writing to gpio_set sets a pin HIGH atomically in one cycle.
|
uint ssi_sm = 0;
|
||||||
// Writing to gpio_clr clears it LOW. Reading gpio_in gives all GPIO states.
|
uint ssi_offset = 0;
|
||||||
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) {
|
void setStatus(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
led.setPixelColor(0, led.Color(r, g, b));
|
led.setPixelColor(0, led.Color(r, g, b));
|
||||||
led.show();
|
led.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// Map encoder position to a colour on the HSV wheel (brightness fixed at ~20/255).
|
||||||
// CORE 1: SSI worker
|
// hue 0-359: 0=red, 120=green, 240=blue
|
||||||
// =========================================================================
|
void setPositionColor(uint32_t value, uint32_t maxVal) {
|
||||||
|
if (maxVal == 0) { setStatus(16, 0, 16); return; } // magenta = unknown range
|
||||||
uint64_t ssi_read_core1(uint8_t bits, uint16_t half_us) {
|
uint16_t hue = (uint16_t)(((uint32_t)value * 360) / maxVal); // 0-359
|
||||||
uint64_t value = 0;
|
// HSV → RGB with S=1, V=20
|
||||||
|
uint8_t sector = hue / 60;
|
||||||
sio_hw->gpio_clr = TX_DI_MASK; // first falling edge: latch
|
uint8_t frac = (uint8_t)(((hue % 60) * 255) / 60);
|
||||||
busy_wait_us_32(half_us);
|
uint8_t v = 20, p = 0, q = (uint8_t)(v * (255 - frac) / 255), t = (uint8_t)(v * frac / 255);
|
||||||
|
switch (sector % 6) {
|
||||||
for (uint8_t i = 0; i < bits; i++) {
|
case 0: setStatus(v, t, p); break;
|
||||||
sio_hw->gpio_set = TX_DI_MASK; // rising: encoder shifts
|
case 1: setStatus(q, v, p); break;
|
||||||
busy_wait_us_32(half_us);
|
case 2: setStatus(p, v, t); break;
|
||||||
sio_hw->gpio_clr = TX_DI_MASK; // falling: sample
|
case 3: setStatus(p, q, v); break;
|
||||||
uint32_t bit = (sio_hw->gpio_in & RX_RO_MASK) ? 1 : 0;
|
case 4: setStatus(t, p, v); break;
|
||||||
value = (value << 1) | bit;
|
case 5: setStatus(v, p, q); break;
|
||||||
busy_wait_us_32(half_us);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sio_hw->gpio_set = TX_DI_MASK; // back to idle
|
|
||||||
busy_wait_us_32(30); // monoflop
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup1() {
|
void ssi_pio_init(uint half_us) {
|
||||||
// Core 1 setup: nothing to do, GPIOs already configured by core 0.
|
// pioasm generates these helpers in ssi.pio.h
|
||||||
// Importantly: no Serial, no USB, no millis IRQ active here by default
|
ssi_offset = pio_add_program(ssi_pio, &ssi_master_program);
|
||||||
// when running in this dual-core mode.
|
|
||||||
|
pio_sm_config c = ssi_master_program_get_default_config(ssi_offset);
|
||||||
|
|
||||||
|
// Side-set drives the CLK pin (TX_DI)
|
||||||
|
sm_config_set_sideset_pins(&c, TX_DI);
|
||||||
|
|
||||||
|
// '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);
|
||||||
|
|
||||||
|
// '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);
|
||||||
|
|
||||||
|
// 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 loop1() {
|
void ssi_pio_reconfigure_speed(uint half_us) {
|
||||||
// Block until core 0 sends a packed request word.
|
pio_sm_set_enabled(ssi_pio, ssi_sm, false);
|
||||||
// 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();
|
// Drain any leftover words from FIFOs
|
||||||
uint64_t value = ssi_read_core1(bits, half_us);
|
pio_sm_clear_fifos(ssi_pio, ssi_sm);
|
||||||
uint32_t took = time_us_32() - t0;
|
|
||||||
|
|
||||||
// Push three words back: low32, high32, duration
|
// Restart SM at the wrap target
|
||||||
rp2040.fifo.push((uint32_t)(value & 0xFFFFFFFF));
|
pio_sm_restart(ssi_pio, ssi_sm);
|
||||||
rp2040.fifo.push((uint32_t)(value >> 32));
|
pio_sm_clkdiv_restart(ssi_pio, ssi_sm);
|
||||||
rp2040.fifo.push(took);
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
uint32_t ssi_pio_read(uint8_t bits) {
|
||||||
// CORE 0: serial command handler
|
pio_sm_put_blocking(ssi_pio, ssi_sm, bits - 1);
|
||||||
// =========================================================================
|
uint32_t result = pio_sm_get_blocking(ssi_pio, ssi_sm);
|
||||||
|
|
||||||
void requestSsiRead(uint8_t bits, uint16_t half_us, uint64_t& outValue, uint32_t& outTook) {
|
// ISR shifts left → first bit in MSB. Right-align.
|
||||||
uint32_t req = ((uint32_t)bits << 24) | ((uint32_t)half_us << 8);
|
return result >> (32 - bits);
|
||||||
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) {
|
void handleCommand(const String& cmd) {
|
||||||
if (!cmd.startsWith("READ ")) {
|
if (!cmd.startsWith("READ ")) {
|
||||||
|
setStatus(16, 0, 0); // red = error
|
||||||
Serial.println("ERR unknown command. Use: READ <bits> <half_us>");
|
Serial.println("ERR unknown command. Use: READ <bits> <half_us>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -112,6 +112,7 @@ void handleCommand(const String& cmd) {
|
||||||
int firstSpace = cmd.indexOf(' ');
|
int firstSpace = cmd.indexOf(' ');
|
||||||
int secondSpace = cmd.indexOf(' ', firstSpace + 1);
|
int secondSpace = cmd.indexOf(' ', firstSpace + 1);
|
||||||
if (secondSpace < 0) {
|
if (secondSpace < 0) {
|
||||||
|
setStatus(16, 0, 0); // red = error
|
||||||
Serial.println("ERR usage: READ <bits> <half_us>");
|
Serial.println("ERR usage: READ <bits> <half_us>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -119,22 +120,28 @@ void handleCommand(const String& cmd) {
|
||||||
int bits = cmd.substring(firstSpace + 1, secondSpace).toInt();
|
int bits = cmd.substring(firstSpace + 1, secondSpace).toInt();
|
||||||
int halfUs = cmd.substring(secondSpace + 1).toInt();
|
int halfUs = cmd.substring(secondSpace + 1).toInt();
|
||||||
|
|
||||||
if (bits < 1 || bits > 64) {
|
if (bits < 1 || bits > 32) {
|
||||||
Serial.println("ERR bits must be 1..64");
|
setStatus(16, 0, 0); // red = error
|
||||||
|
Serial.println("ERR bits must be 1..32 (PIO ISR limit)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (halfUs < 1 || halfUs > 10000) {
|
if (halfUs < 1 || halfUs > 10000) {
|
||||||
|
setStatus(16, 0, 0); // red = error
|
||||||
Serial.println("ERR half_us must be 1..10000");
|
Serial.println("ERR half_us must be 1..10000");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(0, 0, 16); // blue = reading
|
ssi_pio_reconfigure_speed(halfUs);
|
||||||
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",
|
setStatus(0, 0, 16); // blue = reading
|
||||||
|
uint32_t t0 = micros();
|
||||||
|
uint32_t value = ssi_pio_read((uint8_t)bits);
|
||||||
|
uint32_t took = micros() - t0;
|
||||||
|
|
||||||
|
// Show encoder position as hue (full range = 2^bits - 1)
|
||||||
|
setPositionColor(value, (1ul << bits) - 1);
|
||||||
|
|
||||||
|
Serial.printf("OK bits=%d half_us=%d hex=0x%lX dec=%lu took=%luus\n",
|
||||||
bits, halfUs, value, value, took);
|
bits, halfUs, value, value, took);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +150,6 @@ void setup() {
|
||||||
led.begin();
|
led.begin();
|
||||||
setStatus(8, 8, 0);
|
setStatus(8, 8, 0);
|
||||||
|
|
||||||
pinMode(TX_DI, OUTPUT);
|
|
||||||
pinMode(TX_DE, OUTPUT); pinMode(TX_RE, OUTPUT);
|
pinMode(TX_DE, OUTPUT); pinMode(TX_RE, OUTPUT);
|
||||||
pinMode(RX_DE, OUTPUT); pinMode(RX_RE, OUTPUT);
|
pinMode(RX_DE, OUTPUT); pinMode(RX_RE, OUTPUT);
|
||||||
pinMode(RX_DI, OUTPUT);
|
pinMode(RX_DI, OUTPUT);
|
||||||
|
|
@ -154,11 +160,14 @@ void setup() {
|
||||||
digitalWrite(RX_DE, LOW);
|
digitalWrite(RX_DE, LOW);
|
||||||
digitalWrite(RX_RE, LOW);
|
digitalWrite(RX_RE, LOW);
|
||||||
digitalWrite(RX_DI, 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);
|
delay(200);
|
||||||
while (!Serial && millis() < 3000) { delay(10); }
|
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 <bits> <half_us>");
|
Serial.println("Send: READ <bits> <half_us>");
|
||||||
setStatus(0, 16, 0);
|
setStatus(0, 16, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/ssi.pio
Normal file
24
src/ssi.pio
Normal file
|
|
@ -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
|
||||||
51
src/ssi.pio.h
Normal file
51
src/ssi.pio.h
Normal file
|
|
@ -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
|
||||||
|
|
||||||
Loading…
Reference in a new issue