1
0
Fork 0

Compare commits

...

7 Commits

Author SHA1 Message Date
radex c16581103d
new readme 2024-05-30 20:24:37 +02:00
radex 91b144ff83
perf tweaks 2024-05-30 20:17:43 +02:00
radex 9cd5e00309
8 bits 2024-05-30 18:51:49 +02:00
radex 3c42453975
wip: pio-based everythin 2024-05-30 18:26:22 +02:00
radex be5f1abb08
wip: pio-based row selection 2024-05-30 15:44:31 +02:00
radex dfaf256926
clean up pio 2024-05-30 11:47:15 +02:00
radex d43bdba36e
wip: pio-based delay 2024-05-30 11:39:56 +02:00
6 changed files with 390 additions and 148 deletions

View File

@ -1,11 +1,62 @@
# pico-led-matrix
# radmatrix
Tiny modular LED matrix. Idea is to have a matrix of 50x50mm modules:
Smol modular LED matrix, consisting of 3 types of 50x50mm modules:
- LED modules with a 20x20 matrix of 0603 red LEDs.
- Multiplexer/driver modules at the end of rows and columns
- Microcontroller (RP2040) board in the corner.
- LED modules with a 20x20 matrix of 0603 red LEDs
- Driver modules at the end of rows and columns (bottom and right)
- Microcontroller (RP2040) board in the bottom-right corner
JLCPCB Assembly optimized.
This was built as my first "serious" electronics project and its main purpose was to learn electronics a bit better. Some RP2040 embedded knowledge and a blinky lights object are nice side effects.
This is an electronics learning experiment, will likely fail. I do not know what I'm doing
## Results
TODO: Photos, video, PCB renders
## Design details
The LED matrix and driver module PCB design were optimized for JLCPCB Assembly - i.e. only using parts available in its (LCSC) catalog and strongly preferring Basic parts to avoid paying feeder fees. The LEDs chosen were the cheapest available in JLC's catalog (they work fine btw).
The microcontroller board was optimized for JLC PCBA to a lesser extent -- I used multiple parts that would incur a feeder fee if not for the fact that I hand-soldered them.
The display is capable of:
- 8 bit (TODO, 5-bit demonstrated) red color
- 60fps (TODO, 30fps demonstrated)
Additionally, the MCU board can:
- Read multiple "videos" (specially encoded for this firmware) from an SD card
- Pipe in 8-bit 44kHz audio (TODO, lower quality demonstrated due to SD performance issues)
- do CAN (TODO)
## v2
Now that I've leraned from my mistakes, here's what I would do if I were to build a v2 of this display (I probably won't):
- Use square, preferably RGB LED "pixels"
- Fit them in a grid of **standard** spacing (i.e. 1, 1.27, 2, or 2.54mm)
- Put pixels edge to edge
- Current design simplfiied connections and JLC assembly, but it's ugly
- Use a black PCB for the LED matrix for increased contrast
- Move driver modules from column/row ends to the backs of LED modules
- The current design is kinda neat, but it doesn't scale, as with each additional row of modules, the perceptual brightness of LEDs goes down
- A 2mm-spacing 32x32 standard module seems reasonable
- Connection between LED module and driver module would be done by placing 1x16 SMD pin headers alongside corners (split in half, say, bottom-left and top-right for rows, and likewise for columns to allow edge-to-edge clearance)
- Possibly some additional pin headers for added stability, maybe some solderable nuts - some mechanical connection between modules would be nice
- Driver modules
- Would consider constant-current drivers, although resistor-based approach seems to work fine and appeared cheaper
- If resistor-based approach is still used, I would consider a buck converter for creating a variable LED power supply
- Would consider not skipping any of the shift register stages as that makes programming more annoying, and in this approach, it shouldn't make layout too difficult
- Fix v1 bugs of course
- Would consider faster shift registers. Perhaps 74HCT (5V supply to '595 gives best performance, and HCT can handle 3v3 input), or LV*
- Would consider a decoder instead of shift registers for selecting rows. The current method is risky in that if programming is not done correctly, multiple LED rows could be lighting up at the same time (with one resistor for multiple LEDs), overstressing them. It's likely that current approach is cheaper and requires fewer signal lines though
- Driving the display
- Each LED/driver module would have a separate connection to the MCU board - sharing row selection, OE, SRCLK, RCLK, lines, but each having its own SER line
- With PIO, this would have minimal impact on driving performance, and RP2040 IO counts would likely allow for 16-20 module to be connected to a single MCU board
- This could in principle be extended further with multiple MCU boards talking to a master
- The display would become /32, so would be brighter and that would not depend on its height
- Each driver module should have a buffer before/after it to improve the signal
Note that while most people do projects like this with an FPGA, in my opinion the RP2040, thanks to PIO, is more than capable enough for this job at a better price, and can do other stuff in addition to just driving the display. The main downside vs an FPGA is limited IO (30 pins), which limits how big of a display can be driven. With the design above, likely ~20kpx (though this could be optimized further)
Noting the above so I don't forget or in case someone else wants to take this project further. To me, this project has fulfilled its educational goal, and making large LED displays this way is impractical given that you can get HUB75 modules for less than just the LED cost off LCSC.

View File

@ -40,49 +40,8 @@ int32_t gfx_decoder_loadNextFrame() {
return false;
}
// Convert framebuffer into raw shift register data for fast PIO pixel pushing
// Data will be held in buffers, one per pixel's depth bit (aka brightness stage),
// with each row split into 32-bit chunks, one per module
// (20 pixels, 24 shift register stages, 8 unused bits)
// Rows are inverted, because that's how they're fed to the shift registers
// TODO: Move this to leds.cpp
// TODO: Use a separate buffer, then copy to ledsBuffer to avoid tearing
for (int bi = 0; bi < 8; bi++) {
uint8_t bitPosition = 1 << bi;
for (int y = 0; y < ROW_COUNT; y++) {
auto yOffset = y * COL_COUNT;
for (int xModule = 0; xModule < COL_MODULES; xModule++) {
auto bufferXOffset = yOffset + xModule * 20;
uint32_t sample = 0;
leds_set_framebuffer(buffer);
for (int x = 0; x < 20; x++) {
// insert placeholders for unused stages
// (before pixels 0, 6, 13)
if (x == 0 || x == 6 || x == 13) {
sample >>= 1;
}
uint8_t px = buffer[bufferXOffset + x];
bool bit = px & bitPosition;
sample = (sample >> 1) | (bit ? 0x80000000 : 0);
}
// insert placeholder for unused last stage (after pixel 19)
sample >>=1;
// shift to LSB position
sample >>=8;
// MSB=1 indicates end of row
if (xModule == COL_MODULES - 1) {
sample |= 0x80000000;
}
ledBuffer[bi][(ROW_COUNT - 1 - y) * COL_MODULES + xModule] = sample;
}
}
}
ledBufferReady = true;
// copy to framebuffer
// TODO: mutex? double buffer? or something...
memcpy(framebuffer, buffer, ROW_COUNT * COL_COUNT);
free(buffer);
return frameSize;
}

View File

@ -7,8 +7,10 @@
#include "leds.h"
#include "leds.pio.h"
PIO pusher_pio = pio0;
PIO leds_pio = pio0;
uint pusher_sm = 255; // invalid
uint delay_sm = 255; // invalid
uint row_sm = 255; // invalid
// NOTE: RCLK, SRCLK capture on *rising* edge
inline void pulsePin(uint8_t pin) {
@ -32,12 +34,98 @@ 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};
// delays in nanoseconds
#define NS_TO_DELAY(ns) ns / NS_PER_CYCLE
uint32_t brightnessPhaseDelays[COLOR_BITS] = {
NS_TO_DELAY(50),
NS_TO_DELAY(100),
NS_TO_DELAY(200),
NS_TO_DELAY(500),
NS_TO_DELAY(1500),
NS_TO_DELAY(6000),
NS_TO_DELAY(20000),
NS_TO_DELAY(60000),
};
// NOTE: Alignment required to allow 4-byte reads
uint8_t framebuffer[ROW_COUNT * COL_COUNT] __attribute__((aligned(32))) = {0};
// Framebuffer encoded for fast PIO pixel pushing
// There's one buffer for each of pixel's bit indices (aka brightness phases),
// Then for each row (laid out bottom to top), we have:
// - one 32-bit word per horizontal (column) module:
// 20 pixels = 24 shift register stages (4 placeholders), 7 unused bits,
// 1 bit to indicate end of row
// - one word for selecting (shifting) a row:
// 1 bit (LSB) to indicate start of frame (1) or not (0)
// remaining bits to indicate a number of shift register pulses
// (again, 24 shift register stages per 20 rows, so there are placeholders)
uint32_t ledBuffer[8][ROW_COUNT * (COL_MODULES + 1)] = {0};
bool ledBufferReady = false;
uint32_t ledBuffer[8][ROW_COUNT * COL_MODULES] = {0};
void leds_set_framebuffer(uint8_t *buffer) {
// TODO: Use a separate buffer, then copy to ledsBuffer to avoid tearing
for (int bi = 0; bi < 8; bi++) {
uint8_t bitPosition = 1 << bi;
for (int yModule = 0; yModule < ROW_MODULES; yModule++) {
for (int moduleY = 0; moduleY < 20; moduleY++) {
auto y = yModule * 20 + moduleY;
auto bufferYOffset = (ROW_COUNT - 1 - y) * COL_COUNT;
auto outputYOffset = y * (COL_MODULES + 1);
// set data for a given row
for (int xModule = 0; xModule < COL_MODULES; xModule++) {
auto bufferXOffset = bufferYOffset + xModule * 20;
uint32_t sample = 0;
for (int x = 0; x < 20; x++) {
// insert placeholders for unused stages
// (before pixels 0, 6, 13)
if (x == 0 || x == 6 || x == 13) {
sample >>= 1;
}
uint8_t px = buffer[bufferXOffset + x];
bool bit = px & bitPosition;
sample = (sample >> 1) | (bit ? 0x80000000 : 0);
}
// insert placeholder for unused last stage (after pixel 19)
sample >>=1;
// shift to LSB position
sample >>=8;
// MSB=1 indicates end of row
if (xModule == COL_MODULES - 1) {
sample |= 0x80000000;
}
ledBuffer[bi][outputYOffset + xModule] = sample;
}
// set row shifting data
bool firstRow = y == (ROW_COUNT - 1);
uint32_t rowPulses = 1;
if (moduleY == 0) {
rowPulses++;
}
if (moduleY == 7 || moduleY == 14 || (moduleY == 0 && yModule != 0)) {
rowPulses++;
}
uint32_t rowData = firstRow | (rowPulses << 1);
ledBuffer[bi][outputYOffset + COL_MODULES] = rowData;
}
}
}
ledBufferReady = true;
// copy to framebuffer
// TODO: mutex? double buffer? or something...
memcpy(framebuffer, buffer, ROW_COUNT * COL_COUNT);
}
void leds_init() {
memset(framebuffer, 0, sizeof(framebuffer));
@ -86,80 +174,42 @@ void main2() {
}
void leds_initPusher();
void leds_initRowSelector();
void leds_initDelay();
void leds_initRenderer() {
leds_initPusher();
leds_initRowSelector();
leds_initDelay();
multicore_reset_core1();
multicore_launch_core1(main2);
}
void leds_render() {
if (!ledBufferReady) {
outputEnable(ROW_OE, false);
return;
}
// brightness phase
bool brightPhase = brightnessPhase >= 3;
auto buffer = ledBuffer[brightnessPhase + 3];
auto buffer = ledBuffer[brightnessPhase];
auto delayData = brightnessPhaseDelays[brightnessPhase];
// hide output
outputEnable(ROW_OE, false);
// clear rows
clearShiftReg(ROW_SRCLK, ROW_SRCLR);
// start selecting rows
gpio_put(ROW_SER, HIGH);
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
gpio_put(ROW_SER, LOW);
// we use 7/8 stages on shift registers + 1 is unused
if (moduleY == 0) {
pulsePin(ROW_SRCLK);
}
if (moduleY == 7 || moduleY == 14 || (moduleY == 0 && yModule != 0)) {
pulsePin(ROW_SRCLK);
}
// set row data using PIO
// latch signal is also sent here
// TODO: Some ideas for future optimization:
// - see if we can disable px pusher delays on improved electric interface
// - improve outer loop which adds 2us of processing on each loop
// - change busy wait into some kind of interrupt-based thing so that processing can continue
// - DMA?
for (int xModule = 0; xModule < COL_MODULES; xModule++) {
uint32_t pxValues = buffer[bufferOffset + xModule];
pio_sm_put_blocking(pusher_pio, pusher_sm, pxValues);
}
// wait until pushing and RCLK latch are done
while (!pio_interrupt_get(pusher_pio, 0)) {
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);
// next row
bufferOffset += COL_MODULES;
// The correct data to push onto PIO has been precomputed by leds_set_framebuffer
// So we only need to move the buffer onto PIO TX FIFOs to keep them full
// TODO: Rewrite this to be interrupt-based. Should be relatively easy to always keep PIO
// full via interrupts and free up most of the core's time to other tasks
for (uint8_t y = 0; y < ROW_COUNT; y++) {
// set row data
for (uint8_t x = 0; x < COL_MODULES; x++) {
auto pxValues = *buffer++;
pio_sm_put_blocking(leds_pio, pusher_sm, pxValues);
}
// set row selection data
auto rowSelData = *buffer++;
pio_sm_put_blocking(leds_pio, row_sm, rowSelData);
// set delay data
pio_sm_put_blocking(leds_pio, delay_sm, delayData);
}
// next brightness phase
@ -167,33 +217,34 @@ void leds_render() {
}
void leds_initPusher() {
PIO pio = pusher_pio;
PIO pio = leds_pio;
uint sm = pio_claim_unused_sm(pio, true);
pusher_sm = sm;
uint offset = pio_add_program(pio, &leds_px_pusher_program);
uint dataPin = COL_SER;
uint latchPin = COL_SRCLK;
pio_sm_config config = leds_px_pusher_program_get_default_config(offset);
sm_config_set_clkdiv_int_frac(&config, 1, 0);
// Shift OSR to the right, autopull
sm_config_set_out_shift(&config, true, true, 32);
// Use FIFO join to create a longer TX FIFO
// NOTE: This is not needed for the other SMs, as the px pusher will always be the bottleneck
sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_TX);
// Set OUT (data) pin, connect to pad, set as output
sm_config_set_out_pins(&config, dataPin, 1);
pio_gpio_init(pio, dataPin);
pio_sm_set_consecutive_pindirs(pio, sm, dataPin, 1, true);
sm_config_set_out_pins(&config, COL_SER, 1);
pio_gpio_init(pio, COL_SER);
pio_sm_set_consecutive_pindirs(pio, sm, COL_SER, 1, true);
// data is inverted
gpio_set_outover(dataPin, GPIO_OVERRIDE_INVERT);
gpio_set_outover(COL_SER, GPIO_OVERRIDE_INVERT);
// Set sideset (SRCLK) pin, connect to pad, set as output
sm_config_set_sideset_pins(&config, latchPin);
pio_gpio_init(pio, latchPin);
pio_sm_set_consecutive_pindirs(pio, sm, latchPin, 1, true);
sm_config_set_sideset_pins(&config, COL_SRCLK);
pio_gpio_init(pio, COL_SRCLK);
pio_sm_set_consecutive_pindirs(pio, sm, COL_SRCLK, 1, true);
// Set SET (RCLK) pin, connect to pad, set as output
sm_config_set_set_pins(&config, RCLK, 1);
@ -204,3 +255,55 @@ void leds_initPusher() {
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
}
void leds_initRowSelector() {
PIO pio = leds_pio;
uint sm = pio_claim_unused_sm(pio, true);
row_sm = sm;
uint offset = pio_add_program(pio, &leds_row_selector_program);
pio_sm_config config = leds_row_selector_program_get_default_config(offset);
sm_config_set_clkdiv_int_frac(&config, 1, 0);
// Shift OSR to the right, autopull
sm_config_set_out_shift(&config, true, true, 32);
// Set OUT and SET (data) pin, connect to pad, set as output
sm_config_set_out_pins(&config, ROW_SER, 1);
sm_config_set_set_pins(&config, ROW_SER, 1);
pio_gpio_init(pio, ROW_SER);
pio_sm_set_consecutive_pindirs(pio, sm, ROW_SER, 1, true);
// Set sideset (SRCLK) pin, connect to pad, set as output
sm_config_set_sideset_pins(&config, ROW_SRCLK);
pio_gpio_init(pio, ROW_SRCLK);
pio_sm_set_consecutive_pindirs(pio, sm, ROW_SRCLK, 1, true);
// Load our configuration, and jump to the start of the program
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
}
void leds_initDelay() {
PIO pio = leds_pio;
uint sm = pio_claim_unused_sm(pio, true);
delay_sm = sm;
uint offset = pio_add_program(pio, &leds_delay_program);
pio_sm_config config = leds_delay_program_get_default_config(offset);
sm_config_set_clkdiv_int_frac(&config, 1, 0);
// Shift OSR to the right, autopull
sm_config_set_out_shift(&config, true, true, 32);
// Set sideset (OE) pin, connect to pad, set as output
sm_config_set_sideset_pins(&config, ROW_OE);
pio_gpio_init(pio, ROW_OE);
pio_sm_set_consecutive_pindirs(pio, sm, ROW_OE, 1, true);
// Load our configuration, and jump to the start of the program
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
}

View File

@ -22,18 +22,19 @@
#define COL_MODULES 2
#define COL_COUNT COL_MODULES * 20
#define COLOR_BITS 5
#define COLOR_BITS 8
#define FPS 30
#define MS_PER_FRAME 1000 / FPS
#define CPU_MHZ 125
#define NS_PER_CYCLE 1000 / CPU_MHZ
void leds_init();
void leds_initRenderer();
void leds_disable();
void leds_loop();
void leds_render();
extern uint8_t framebuffer[ROW_COUNT * COL_COUNT];
extern bool ledBufferReady;
extern uint32_t ledBuffer[8][ROW_COUNT * COL_MODULES];
void leds_set_framebuffer(uint8_t *buffer);
#endif

View File

@ -1,31 +1,85 @@
.define public irq_did_latch 0
.define public irq_delaying 1
.define public irq_row_selected 1
; TODO: check if delays can be lowered with a PCB
.define public srclk_0_delay 1
.define public srclk_1_delay 2
.define public rclk_1_delay 3
.program leds_px_pusher
.side_set 1 opt
public entry_point:
entry_point:
.wrap_target
; get 32 bits from fifo (not required with autopull, useful for debug)
; pull
; push 24 bits to the shift registers
; also, return latch bit to 0
set x, 23 side 0
set x, 23 side 0
loop:
; TODO: check if delays can be lowered with a PCB
; set bit; lower clock edge
out pins, 1 side 0 [1]
out pins, 1 side 0 [srclk_0_delay]
; loop; latch bit (rising edge)
jmp x-- loop side 1 [2]
jmp x-- loop side 1 [srclk_1_delay]
end:
; ignore unused bits
; load MSBs into x
; lower clock edge
out x, 8 side 0
out x, 8 side 0
; MSB=1 indicates end of row
jmp x-- end_of_row
.wrap
jmp !x entry_point
end_of_row:
; indicate to main processor that row was processed
irq set 0
; wait until previous row's delay is complete
; (irq will be 0 on first row by default)
wait 0 irq irq_delaying
; wait until row is selected, and clear flag
wait 1 irq irq_row_selected
; signal that we're about to latch the row onto display
irq set irq_did_latch
; clock RCLK (latch onto register output stage)
set pins, 1 [3]
set pins, 1 [rclk_1_delay]
set pins, 0
; wait for next row
jmp entry_point
.wrap
.program leds_row_selector
.side_set 1 opt
entry_point:
.wrap_target
; LSB=1 indicates first (bottom) row, ergo, high SER for the first pulse
out pins, 1 side 0 [srclk_0_delay]
; The rest of the word indicates number of SRCLK pulses
out x, 31
loop:
; pulse SRCLK x times
; set SER=0 after first pulse
nop side 1 [srclk_1_delay]
set pins, 0 side 0 [srclk_0_delay]
jmp x-- loop
end:
; signal that row is selected and wait until acknowledged
; some delay so that in the worst case scenario we don't start changing rows before RCLK latch
irq wait irq_row_selected [4]
.wrap
.program leds_delay
.side_set 1 opt
.define public output_on 0
.define public output_off 1
entry_point:
.wrap_target
; begin only after data is latched
wait 1 irq irq_did_latch
; signal that delay is ongoing
irq set irq_delaying
; x = number of cycles to delay
out x, 32
loop:
; busy loop for x cycles
; enable output
jmp x-- loop side output_on
end:
; signal that delay is complete
; disable output
irq clear irq_delaying side output_off
.wrap

View File

@ -8,14 +8,19 @@
#include "hardware/pio.h"
#endif
#define irq_did_latch 0
#define irq_delaying 1
#define irq_row_selected 1
#define srclk_0_delay 1
#define srclk_1_delay 2
#define rclk_1_delay 3
// -------------- //
// leds_px_pusher //
// -------------- //
#define leds_px_pusher_wrap_target 0
#define leds_px_pusher_wrap 4
#define leds_px_pusher_offset_entry_point 0u
#define leds_px_pusher_wrap 9
static const uint16_t leds_px_pusher_program_instructions[] = {
// .wrap_target
@ -23,18 +28,19 @@ static const uint16_t leds_px_pusher_program_instructions[] = {
0x7101, // 1: out pins, 1 side 0 [1]
0x1a41, // 2: jmp x--, 1 side 1 [2]
0x7028, // 3: out x, 8 side 0
0x0045, // 4: jmp x--, 5
0x0020, // 4: jmp !x, 0
0x2041, // 5: wait 0 irq, 1
0x20c1, // 6: wait 1 irq, 1
0xc000, // 7: irq nowait 0
0xe301, // 8: set pins, 1 [3]
0xe000, // 9: set pins, 0
// .wrap
0xc000, // 5: irq nowait 0
0xe301, // 6: set pins, 1 [3]
0xe000, // 7: set pins, 0
0x0000, // 8: 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,
};
@ -46,3 +52,71 @@ static inline pio_sm_config leds_px_pusher_program_get_default_config(uint offse
}
#endif
// ----------------- //
// leds_row_selector //
// ----------------- //
#define leds_row_selector_wrap_target 0
#define leds_row_selector_wrap 5
static const uint16_t leds_row_selector_program_instructions[] = {
// .wrap_target
0x7101, // 0: out pins, 1 side 0 [1]
0x603f, // 1: out x, 31
0xba42, // 2: nop side 1 [2]
0xf100, // 3: set pins, 0 side 0 [1]
0x0042, // 4: jmp x--, 2
0xc421, // 5: irq wait 1 [4]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program leds_row_selector_program = {
.instructions = leds_row_selector_program_instructions,
.length = 6,
.origin = -1,
};
static inline pio_sm_config leds_row_selector_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + leds_row_selector_wrap_target, offset + leds_row_selector_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
#endif
// ---------- //
// leds_delay //
// ---------- //
#define leds_delay_wrap_target 0
#define leds_delay_wrap 4
#define leds_delay_output_on 0
#define leds_delay_output_off 1
static const uint16_t leds_delay_program_instructions[] = {
// .wrap_target
0x20c0, // 0: wait 1 irq, 0
0xc001, // 1: irq nowait 1
0x6020, // 2: out x, 32
0x1043, // 3: jmp x--, 3 side 0
0xd841, // 4: irq clear 1 side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program leds_delay_program = {
.instructions = leds_delay_program_instructions,
.length = 5,
.origin = -1,
};
static inline pio_sm_config leds_delay_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + leds_delay_wrap_target, offset + leds_delay_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
#endif