From 9f9d089e08edde53dbfe99617f444c4302a76aad Mon Sep 17 00:00:00 2001 From: radex Date: Mon, 27 May 2024 10:15:22 +0200 Subject: [PATCH] optimize: use PWM interrupt based delays for precision and pipelining --- firmware/src/leds.cpp | 84 +++++++++++++++++++++++++++++++---------- firmware/src/leds.pio | 4 +- firmware/src/leds.pio.h | 11 +++--- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/firmware/src/leds.cpp b/firmware/src/leds.cpp index 7b5c23b..d4f701e 100644 --- a/firmware/src/leds.cpp +++ b/firmware/src/leds.cpp @@ -3,6 +3,7 @@ #include "mbed_wait_api.h" #include "pico/multicore.h" #include "hardware/pio.h" +#include "hardware/irq.h" #include "leds.h" #include "leds.pio.h" @@ -10,6 +11,9 @@ PIO pusher_pio = pio0; uint pusher_sm = 255; // invalid +#define PWM_SLICE 0 +volatile bool delayFinished = true; + // NOTE: RCLK, SRCLK capture on *rising* edge inline void pulsePin(uint8_t pin) { gpio_put(pin, HIGH); @@ -32,7 +36,8 @@ inline void outputEnable(uint8_t pin, bool enable) { // we have COLOR_BITS-bit color depth, so 2^COLOR_BITS levels of brightness // we go from phase 0 to phase (COLOR_BITS-1) uint8_t brightnessPhase = 0; -uint8_t brightnessPhaseDelays[COLOR_BITS] = {0, 1, 6, 20, 60}; +// in nanoseconds +uint16_t brightnessPhaseDelays[COLOR_BITS] = {500, 1500, 3000, 20000, 60000}; // NOTE: Alignment required to allow 4-byte reads uint8_t framebuffer[ROW_COUNT * COL_COUNT] __attribute__((aligned(32))) = {0}; @@ -77,22 +82,24 @@ void leds_disable() { outputEnable(ROW_OE, false); } +void leds_initPusher(); +void leds_init_pwm(); + void main2() { - // where we're going, we don't need no interrupts - noInterrupts(); + leds_initPusher(); + leds_init_pwm(); while (true) { leds_render(); } } -void leds_initPusher(); - void leds_initRenderer() { - leds_initPusher(); multicore_reset_core1(); multicore_launch_core1(main2); } +void leds_start_delay(); + void leds_render() { if (!ledBufferReady) { outputEnable(ROW_OE, false); @@ -100,9 +107,14 @@ void leds_render() { } // brightness phase - bool brightPhase = brightnessPhase >= 3; auto buffer = ledBuffer[brightnessPhase + 3]; + // configure delays + pwm_set_clkdiv_int_frac(PWM_SLICE, 1, 0); + // 8ns per cycle at 125MHz + auto delayTicks = brightnessPhaseDelays[brightnessPhase] / 8; + pwm_set_wrap(PWM_SLICE, delayTicks); + // hide output outputEnable(ROW_OE, false); @@ -115,11 +127,6 @@ void leds_render() { int bufferOffset = 0; for (int yModule = 0; yModule < ROW_MODULES; yModule++) { for (int moduleY = 0; moduleY < 20; moduleY++) { - // brigthness - pushing data takes time, so to maximize brightness (at high brightness phases) - // we want to keep the matrix on during update (except during latch). At low brightness phases, - // we want it off to actually be dim - outputEnable(ROW_OE, brightPhase); - // next row pulsePin(ROW_SRCLK); // only one row @@ -146,22 +153,33 @@ void leds_render() { pio_sm_put_blocking(pusher_pio, pusher_sm, pxValues); } - // wait until pushing and RCLK latch are done - while (!pio_interrupt_get(pusher_pio, 0)) { + // wait until previous row's delay is done + while (!delayFinished) { tight_loop_contents(); } - pio_interrupt_clear(pusher_pio, pusher_sm); - // show for a certain period - outputEnable(ROW_OE, true); - busy_wait_us_32(brightnessPhaseDelays[brightnessPhase]); - outputEnable(ROW_OE, false); + // allow pusher to latch data + pusher_pio->irq_force = 1 << 0; + + // wait until pushing and RCLK latch are done + while (!pio_interrupt_get(pusher_pio, 1)) { + tight_loop_contents(); + } + pio_interrupt_clear(pusher_pio, 1); + + // enable output for specified time + leds_start_delay(); // next row bufferOffset += COL_MODULES; } } + // wait until last row's delay is done + while (!delayFinished) { + tight_loop_contents(); + } + // next brightness phase brightnessPhase = (brightnessPhase + 1) % COLOR_BITS; } @@ -204,3 +222,31 @@ void leds_initPusher() { pio_sm_init(pio, sm, offset, &config); pio_sm_set_enabled(pio, sm, true); } + +void leds_start_delay() { + // enable output, start PWM counter + delayFinished = false; + outputEnable(ROW_OE, true); + pwm_set_enabled(PWM_SLICE, true); +} + +void leds_pwm_interrupt_handler() { + // disable output + outputEnable(ROW_OE, false); + // stop PWM counter + pwm_set_enabled(PWM_SLICE, false); + // acknowledge interrupt + pwm_clear_irq(PWM_SLICE); + delayFinished = true; +} + +// We will use PWM to control OE signal with a precise delay +// Note that we only use PWM for timing, it doesn't actually drive the OE GPIO +// (timers are microsecond-resolution, and we need better than this) +void leds_init_pwm() { + irq_set_exclusive_handler(PWM_IRQ_WRAP, leds_pwm_interrupt_handler); + irq_set_enabled(PWM_IRQ_WRAP, true); + + pwm_clear_irq(PWM_SLICE); + pwm_set_irq_enabled(PWM_SLICE, true); +} diff --git a/firmware/src/leds.pio b/firmware/src/leds.pio index b3c4b5e..bf56d8e 100644 --- a/firmware/src/leds.pio +++ b/firmware/src/leds.pio @@ -22,8 +22,10 @@ end: jmp x-- end_of_row .wrap end_of_row: + ; wait for permission to latch onto output + wait 1 irq 0 ; indicate to main processor that row was processed - irq set 0 + irq set 1 ; clock RCLK (latch onto register output stage) set pins, 1 [3] set pins, 0 diff --git a/firmware/src/leds.pio.h b/firmware/src/leds.pio.h index c5eb55d..6abf1d3 100644 --- a/firmware/src/leds.pio.h +++ b/firmware/src/leds.pio.h @@ -25,16 +25,17 @@ static const uint16_t leds_px_pusher_program_instructions[] = { 0x7028, // 3: out x, 8 side 0 0x0045, // 4: jmp x--, 5 // .wrap - 0xc000, // 5: irq nowait 0 - 0xe301, // 6: set pins, 1 [3] - 0xe000, // 7: set pins, 0 - 0x0000, // 8: jmp 0 + 0x20c0, // 5: wait 1 irq, 0 + 0xc001, // 6: irq nowait 1 + 0xe301, // 7: set pins, 1 [3] + 0xe000, // 8: set pins, 0 + 0x0000, // 9: jmp 0 }; #if !PICO_NO_HARDWARE static const struct pio_program leds_px_pusher_program = { .instructions = leds_px_pusher_program_instructions, - .length = 9, + .length = 10, .origin = -1, };