-
Notifications
You must be signed in to change notification settings - Fork 844
/
ws2812_parallel.c
334 lines (285 loc) · 10.7 KB
/
ws2812_parallel.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/sem.h"
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "ws2812.pio.h"
#define FRAC_BITS 4
#define NUM_PIXELS 64
#define WS2812_PIN_BASE 2
// Check the pin is compatible with the platform
#if WS2812_PIN_BASE >= NUM_BANK0_GPIOS
#error Attempting to use a pin>=32 on a platform that does not support it
#endif
// horrible temporary hack to avoid changing pattern code
static uint8_t *current_strip_out;
static bool current_strip_4color;
static inline void put_pixel(uint32_t pixel_grb) {
*current_strip_out++ = pixel_grb & 0xffu;
*current_strip_out++ = (pixel_grb >> 8u) & 0xffu;
*current_strip_out++ = (pixel_grb >> 16u) & 0xffu;
if (current_strip_4color) {
*current_strip_out++ = 0; // todo adjust?
}
}
static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) {
return
((uint32_t) (r) << 8) |
((uint32_t) (g) << 16) |
(uint32_t) (b);
}
void pattern_snakes(uint len, uint t) {
for (uint i = 0; i < len; ++i) {
uint x = (i + (t >> 1)) % 64;
if (x < 10)
put_pixel(urgb_u32(0xff, 0, 0));
else if (x >= 15 && x < 25)
put_pixel(urgb_u32(0, 0xff, 0));
else if (x >= 30 && x < 40)
put_pixel(urgb_u32(0, 0, 0xff));
else
put_pixel(0);
}
}
void pattern_random(uint len, uint t) {
if (t % 8)
return;
for (uint i = 0; i < len; ++i)
put_pixel(rand());
}
void pattern_sparkle(uint len, uint t) {
if (t % 8)
return;
for (uint i = 0; i < len; ++i)
put_pixel(rand() % 16 ? 0 : 0xffffffff);
}
void pattern_greys(uint len, uint t) {
uint max = 100; // let's not draw too much current!
t %= max;
for (uint i = 0; i < len; ++i) {
put_pixel(t * 0x10101);
if (++t >= max) t = 0;
}
}
void pattern_solid(uint len, uint t) {
t = 1;
for (uint i = 0; i < len; ++i) {
put_pixel(t * 0x10101);
}
}
int level = 8;
void pattern_fade(uint len, uint t) {
uint shift = 4;
uint max = 16; // let's not draw too much current!
max <<= shift;
uint slow_t = t / 32;
slow_t = level;
slow_t %= max;
static int error = 0;
slow_t += error;
error = slow_t & ((1u << shift) - 1);
slow_t >>= shift;
slow_t *= 0x010101;
for (uint i = 0; i < len; ++i) {
put_pixel(slow_t);
}
}
typedef void (*pattern)(uint len, uint t);
const struct {
pattern pat;
const char *name;
} pattern_table[] = {
{pattern_snakes, "Snakes!"},
{pattern_random, "Random data"},
{pattern_sparkle, "Sparkles"},
{pattern_greys, "Greys"},
// {pattern_solid, "Solid!"},
// {pattern_fade, "Fade"},
};
#define VALUE_PLANE_COUNT (8 + FRAC_BITS)
// we store value (8 bits + fractional bits of a single color (R/G/B/W) value) for multiple
// strips of pixels, in bit planes. bit plane N has the Nth bit of each strip of pixels.
typedef struct {
// stored MSB first
uint32_t planes[VALUE_PLANE_COUNT];
} value_bits_t;
// Add FRAC_BITS planes of e to s and store in d
void add_error(value_bits_t *d, const value_bits_t *s, const value_bits_t *e) {
uint32_t carry_plane = 0;
// add the FRAC_BITS low planes
for (int p = VALUE_PLANE_COUNT - 1; p >= 8; p--) {
uint32_t e_plane = e->planes[p];
uint32_t s_plane = s->planes[p];
d->planes[p] = (e_plane ^ s_plane) ^ carry_plane;
carry_plane = (e_plane & s_plane) | (carry_plane & (s_plane ^ e_plane));
}
// then just ripple carry through the non fractional bits
for (int p = 7; p >= 0; p--) {
uint32_t s_plane = s->planes[p];
d->planes[p] = s_plane ^ carry_plane;
carry_plane &= s_plane;
}
}
typedef struct {
uint8_t *data;
uint data_len;
uint frac_brightness; // 256 = *1.0;
} strip_t;
// takes 8 bit color values, multiply by brightness and store in bit planes
void transform_strips(strip_t **strips, uint num_strips, value_bits_t *values, uint value_length,
uint frac_brightness) {
for (uint v = 0; v < value_length; v++) {
memset(&values[v], 0, sizeof(values[v]));
for (uint i = 0; i < num_strips; i++) {
if (v < strips[i]->data_len) {
// todo clamp?
uint32_t value = (strips[i]->data[v] * strips[i]->frac_brightness) >> 8u;
value = (value * frac_brightness) >> 8u;
for (int j = 0; j < VALUE_PLANE_COUNT && value; j++, value >>= 1u) {
if (value & 1u) values[v].planes[VALUE_PLANE_COUNT - 1 - j] |= 1u << i;
}
}
}
}
}
void dither_values(const value_bits_t *colors, value_bits_t *state, const value_bits_t *old_state, uint value_length) {
for (uint i = 0; i < value_length; i++) {
add_error(state + i, colors + i, old_state + i);
}
}
// requested colors * 4 to allow for RGBW
static value_bits_t colors[NUM_PIXELS * 4];
// double buffer the state of the pixel strip, since we update next version in parallel with DMAing out old version
static value_bits_t states[2][NUM_PIXELS * 4];
// example - strip 0 is RGB only
static uint8_t strip0_data[NUM_PIXELS * 3];
// example - strip 1 is RGBW
static uint8_t strip1_data[NUM_PIXELS * 4];
strip_t strip0 = {
.data = strip0_data,
.data_len = sizeof(strip0_data),
.frac_brightness = 0x40,
};
strip_t strip1 = {
.data = strip1_data,
.data_len = sizeof(strip1_data),
.frac_brightness = 0x100,
};
strip_t *strips[] = {
&strip0,
&strip1,
};
// bit plane content dma channel
#define DMA_CHANNEL 0
// chain channel for configuring main dma channel to output from disjoint 8 word fragments of memory
#define DMA_CB_CHANNEL 1
#define DMA_CHANNEL_MASK (1u << DMA_CHANNEL)
#define DMA_CB_CHANNEL_MASK (1u << DMA_CB_CHANNEL)
#define DMA_CHANNELS_MASK (DMA_CHANNEL_MASK | DMA_CB_CHANNEL_MASK)
// start of each value fragment (+1 for NULL terminator)
static uintptr_t fragment_start[NUM_PIXELS * 4 + 1];
// posted when it is safe to output a new set of values
static struct semaphore reset_delay_complete_sem;
// alarm handle for handling delay
alarm_id_t reset_delay_alarm_id;
int64_t reset_delay_complete(__unused alarm_id_t id, __unused void *user_data) {
reset_delay_alarm_id = 0;
sem_release(&reset_delay_complete_sem);
// no repeat
return 0;
}
void __isr dma_complete_handler() {
if (dma_hw->ints0 & DMA_CHANNEL_MASK) {
// clear IRQ
dma_hw->ints0 = DMA_CHANNEL_MASK;
// when the dma is complete we start the reset delay timer
if (reset_delay_alarm_id) cancel_alarm(reset_delay_alarm_id);
reset_delay_alarm_id = add_alarm_in_us(400, reset_delay_complete, NULL, true);
}
}
void dma_init(PIO pio, uint sm) {
dma_claim_mask(DMA_CHANNELS_MASK);
// main DMA channel outputs 8 word fragments, and then chains back to the chain channel
dma_channel_config channel_config = dma_channel_get_default_config(DMA_CHANNEL);
channel_config_set_dreq(&channel_config, pio_get_dreq(pio, sm, true));
channel_config_set_chain_to(&channel_config, DMA_CB_CHANNEL);
channel_config_set_irq_quiet(&channel_config, true);
dma_channel_configure(DMA_CHANNEL,
&channel_config,
&pio->txf[sm],
NULL, // set by chain
8, // 8 words for 8 bit planes
false);
// chain channel sends single word pointer to start of fragment each time
dma_channel_config chain_config = dma_channel_get_default_config(DMA_CB_CHANNEL);
dma_channel_configure(DMA_CB_CHANNEL,
&chain_config,
&dma_channel_hw_addr(
DMA_CHANNEL)->al3_read_addr_trig, // ch DMA config (target "ring" buffer size 4) - this is (read_addr trigger)
NULL, // set later
1,
false);
irq_set_exclusive_handler(DMA_IRQ_0, dma_complete_handler);
dma_channel_set_irq0_enabled(DMA_CHANNEL, true);
irq_set_enabled(DMA_IRQ_0, true);
}
void output_strips_dma(value_bits_t *bits, uint value_length) {
for (uint i = 0; i < value_length; i++) {
fragment_start[i] = (uintptr_t) bits[i].planes; // MSB first
}
fragment_start[value_length] = 0;
dma_channel_hw_addr(DMA_CB_CHANNEL)->al3_read_addr_trig = (uintptr_t) fragment_start;
}
int main() {
//set_sys_clock_48();
stdio_init_all();
printf("WS2812 parallel using pin %d\n", WS2812_PIN_BASE);
PIO pio;
uint sm;
uint offset;
// This will find a free pio and state machine for our program and load it for us
// We use pio_claim_free_sm_and_add_program_for_gpio_range (for_gpio_range variant)
// so we will get a PIO instance suitable for addressing gpios >= 32 if needed and supported by the hardware
bool success = pio_claim_free_sm_and_add_program_for_gpio_range(&ws2812_parallel_program, &pio, &sm, &offset, WS2812_PIN_BASE, count_of(strips), true);
hard_assert(success);
ws2812_parallel_program_init(pio, sm, offset, WS2812_PIN_BASE, count_of(strips), 800000);
sem_init(&reset_delay_complete_sem, 1, 1); // initially posted so we don't block first time
dma_init(pio, sm);
int t = 0;
while (1) {
int pat = rand() % count_of(pattern_table);
int dir = (rand() >> 30) & 1 ? 1 : -1;
if (rand() & 1) dir = 0;
puts(pattern_table[pat].name);
puts(dir == 1 ? "(forward)" : dir ? "(backward)" : "(still)");
int brightness = 0;
uint current = 0;
for (int i = 0; i < 1000; ++i) {
current_strip_out = strip0.data;
current_strip_4color = false;
pattern_table[pat].pat(NUM_PIXELS, t);
current_strip_out = strip1.data;
current_strip_4color = true;
pattern_table[pat].pat(NUM_PIXELS, t);
transform_strips(strips, count_of(strips), colors, NUM_PIXELS * 4, brightness);
dither_values(colors, states[current], states[current ^ 1], NUM_PIXELS * 4);
sem_acquire_blocking(&reset_delay_complete_sem);
output_strips_dma(states[current], NUM_PIXELS * 4);
current ^= 1;
t += dir;
brightness++;
if (brightness == (0x20 << FRAC_BITS)) brightness = 0;
}
memset(&states, 0, sizeof(states)); // clear out errors
}
// This will free resources and unload our program
pio_remove_program_and_unclaim_sm(&ws2812_parallel_program, pio, sm, offset);
}