/
led.pde
639 lines (545 loc) · 16.4 KB
/
led.pde
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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
/*----------------------------------------------------------------------------
** led.pde
** Driver for running GE Color Effects lights on an Amtel328p board.
**
** Features:
** - Can drive up to six strings by attaching them to port b (pins 8-13)
** - Gets commands via serial port
**
** License:
** GPL v 3 - See LICENSE
**
**--------------------------------------------------------------------------*/
#include "led.h"
#include "message.h"
/*----------------------------------------------------------------------------
** Notes on Memory:
** The 328 has 2K of RAM, 512 bytes of EEPROM, and 32 K of Flash RAM.
** That makes the size of this ring buffer crucial, as it uses up the
** bulk of available ram.
**--------------------------------------------------------------------------*/
uint8_t ring[SLICES_TO_SHOW_BULB * 10];
const static uint8_t *ring_fence = ring + sizeof(ring);
#define INCREMENT_RING_PTR(p) { if ((++(p)) == ring_fence) (p) = ring; }
uint8_t *readp = ring;
uint8_t *writep = ring;
/*----------------------------------------------------------------------------
** Ring timer code. This interrupt vector just pushes bytes from the ring
** buffer out to Port B, thus driving the lights. The complex bit
** is staging the ring buffer just right.
**--------------------------------------------------------------------------*/
ISR(TIMER1_COMPA_vect)
{
if (readp != writep)
{
PORTB = *readp;
*readp = 0;
INCREMENT_RING_PTR(readp);
}
}
void start_timer1(void)
{
uint8_t sreg;
/* Save global interrupt flag */
sreg = SREG;
/* Disable interrupts */
cli();
/* Set timer mode */
TCCR1A = 0;
/* What count to interrupt the timer upon */
OCR1A = CYCLES_PER_SLICE;
TCCR1B = _BV(WGM12) | _BV(CS10);
/* Turn on CTC1 mode, with no divider */
TIMSK1 = _BV(OCIE1A); /* Enable the interrupt */
/* Restore global interrupt flag */
SREG = sreg;
}
void stop_timer1(void)
{
uint8_t sreg;
/* Save global interrupt flag */
sreg = SREG;
/* Disable interrupts */
cli();
TCCR1A = 0;
TCCR1B = 0;
TIMSK1 = 0;
/* Restore global interrupt flag */
SREG = sreg;
}
void set_writep(uint8_t *p)
{
uint8_t sreg;
/* Save global interrupt flag */
sreg = SREG;
/* Disable interrupts */
cli();
writep = p;
/* Restore global interrupt flag */
SREG = sreg;
}
int available(void)
{
uint8_t sreg;
int a;
/* Save global interrupt flag */
sreg = SREG;
/* Disable interrupts */
cli();
if (writep == readp)
a = sizeof(ring);
else if (writep > readp)
a = (readp - ring) + (ring_fence - writep);
else
a = readp - writep;
/* Restore global interrupt flag */
SREG = sreg;
return a;
}
/*----------------------------------------------------------------------------
** Bulb management code
** This code will take a 4 byte expression of a bulb and
** get it into the ring buffer in the right order.
** There is one slight optimization: we can write orders
** for up to 6 strings simultaneously. So we will cache a bulb
** (on BULB_FLAG_COMBINE) and send them all at once if so requested.
**--------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
** Be careful - this struct is not for general purpose use.
** It's sole use is to provide preshifted bits to write out to the port.
**--------------------------------------------------------------------------*/
typedef struct bulb_struct
{
uint8_t stringmask;
uint8_t addrshift;
uint8_t bright;
uint8_t redshift;
uint8_t greenshift;
uint8_t blueshift;
} bulb;
void write_bits(uint8_t **p, uint8_t one_strings, uint8_t zero_strings)
{
/* Always starts with low */
**p &= ~(one_strings | zero_strings);
INCREMENT_RING_PTR(*p);
/* 3 slices of low for one, 3 of high for zero */
**p &= ~one_strings;
**p |= zero_strings;
INCREMENT_RING_PTR(*p);
**p &= ~one_strings;
**p |= zero_strings;
INCREMENT_RING_PTR(*p);
/* Always ends with high */
**p |= (one_strings | zero_strings);
INCREMENT_RING_PTR(*p);
}
/* Note - the Sketch stuff generates function headers at the top of the file,
after all the #includes, so don't use typedefs in prototypes or people
won't be able to use Sketches :-( */
void write_raw_bulbs(int count, struct bulb_struct *bulbs)
{
uint8_t *p;
int i, j;
uint8_t all_bulbs;
uint8_t low_bulbs;
uint8_t high_bulbs;
while (available() < SLICES_TO_SHOW_BULB)
;
p = (uint8_t *) writep;
all_bulbs = 0;
for (i = 0; i < count; i++)
all_bulbs |= bulbs[i].stringmask;
/* start indicator high */
for (i = 0; i < START_SLICES; i++)
{
*p = all_bulbs;
INCREMENT_RING_PTR(p);
}
/* Address bits */
for (i = 6; i; i--)
{
high_bulbs = low_bulbs = 0;
for (j = 0; j < count; j++)
{
if (bulbs[j].addrshift & 0x80)
high_bulbs |= bulbs[j].stringmask;
else
low_bulbs |= bulbs[j].stringmask;
bulbs[j].addrshift <<= 1;
}
write_bits(&p, high_bulbs, low_bulbs);
}
/* Bright bits */
for (i = 8; i; i--)
{
high_bulbs = low_bulbs = 0;
for (j = 0; j < count; j++)
{
if (bulbs[j].bright & 0x80)
high_bulbs |= bulbs[j].stringmask;
else
low_bulbs |= bulbs[j].stringmask;
bulbs[j].bright <<= 1;
}
write_bits(&p, high_bulbs, low_bulbs);
}
/* Blue bits */
for (i = 4; i; i--)
{
high_bulbs = low_bulbs = 0;
for (j = 0; j < count; j++)
{
if (bulbs[j].blueshift & 0x80)
high_bulbs |= bulbs[j].stringmask;
else
low_bulbs |= bulbs[j].stringmask;
bulbs[j].blueshift <<= 1;
}
write_bits(&p, high_bulbs, low_bulbs);
}
/* Green bits */
for (i = 4; i; i--)
{
high_bulbs = low_bulbs = 0;
for (j = 0; j < count; j++)
{
if (bulbs[j].greenshift & 0x80)
high_bulbs |= bulbs[j].stringmask;
else
low_bulbs |= bulbs[j].stringmask;
bulbs[j].greenshift <<= 1;
}
write_bits(&p, high_bulbs, low_bulbs);
}
/* Red */
for (i = 4; i; i--)
{
high_bulbs = low_bulbs = 0;
for (j = 0; j < count; j++)
{
if (bulbs[j].redshift & 0x80)
high_bulbs |= bulbs[j].stringmask;
else
low_bulbs |= bulbs[j].stringmask;
bulbs[j].redshift <<= 1;
}
write_bits(&p, high_bulbs, low_bulbs);
}
/* stop indicator low */
for (i = 0; i < STOP_SLICES; i++)
{
*p &= ~all_bulbs;
INCREMENT_RING_PTR(p);
}
set_writep(p);
}
void process_bulb(const uint8_t *data)
{
static bulb bulbs[STRING_COUNT];
static int bulb_count = 0;
bulb *p;
p = &bulbs[bulb_count];
p->addrshift = BULB_ADDRESS(data) << 2;
p->stringmask = _BV(BULB_STRING(data));
p->blueshift = BULB_BLUESHIFT(data);
p->greenshift = BULB_GREENSHIFT(data);
p->redshift = BULB_REDSHIFT(data);
p->bright = BULB_BRIGHT(data);
if (p->bright > MAX_BRIGHT)
p->bright = MAX_BRIGHT;
if (++bulb_count >= STRING_COUNT || (! IS_COMBINED(data)))
{
write_raw_bulbs(bulb_count, bulbs);
bulb_count = 0;
}
}
/*----------------------------------------------------------------------------
** Message scrolling code
**--------------------------------------------------------------------------*/
#define SCROLL_INTERVAL 200
uint8_t *g_scroll_pos = g_message_bits;
int g_scroll_width = 20;
unsigned long g_next_scroll = 0;
uint8_t g_scroll_blueshift = 0;
uint8_t g_scroll_redshift = 13 << 4;
uint8_t g_scroll_greenshift = 0;
uint8_t g_scroll_bright = MAX_BRIGHT;
uint8_t g_scrolling = 0;
uint8_t g_init_at_start = 1;
void map_xy_to_string_addr(int x, int y, int *string, int *addr)
{
*string = x / 5;
*addr = y * 5;
if (y % 2 == 0)
*addr += (x % 5);
else
*addr += (5 - (x % 5));
}
scroll_color_map_t * current_map(int n)
{
static scroll_color_map_t def;
int i;
scroll_color_map_t *ret;
if (sizeof(g_scroll_color_map) == 0)
{
def.bright = g_scroll_bright;
def.r = g_scroll_redshift;
def.g = g_scroll_greenshift;
def.b = g_scroll_blueshift;
ret = &def;
}
for (i = 0; i < sizeof(g_scroll_color_map) / sizeof(g_scroll_color_map[0]); i++)
if (n >= g_scroll_color_map[i].start)
ret = &g_scroll_color_map[i];
else
break;
return ret;
}
void scroll_display(void)
{
int i;
uint8_t *p;
bulb pixel;
int x, y;
int shift;
int string;
int addr;
scroll_color_map_t *map;
p = g_scroll_pos;
for (x = 0; x < g_scroll_width; x++)
{
for (y = 0; y < MESSAGE_ROWS; y++)
{
shift = 1 << y;
map_xy_to_string_addr(x, y, &string, &addr);
map = current_map(p - g_message_bits);
if (*p & shift)
pixel.bright = map->bright;
else
pixel.bright = 0;
pixel.stringmask = _BV(string);
pixel.addrshift = addr << 2;
pixel.redshift = map->r;
pixel.greenshift = map->g;
pixel.blueshift = map->b;
write_raw_bulbs(1, &pixel);
}
if (((++p) - g_message_bits) >= sizeof(g_message_bits))
p = g_message_bits;
}
}
void scroll_advance(int howmuch)
{
while (howmuch-- > 0)
if ((++g_scroll_pos) - g_message_bits >= sizeof(g_message_bits))
g_scroll_pos = g_message_bits;
}
void check_scroll()
{
unsigned long now = millis();
if (now >= g_next_scroll || now <= SCROLL_INTERVAL)
{
g_next_scroll = now + SCROLL_INTERVAL;
scroll_advance(1);
scroll_display();
}
}
/*----------------------------------------------------------------------------
** init
** Send the sequence to initialize the strings.
**--------------------------------------------------------------------------*/
void init(unsigned int pause)
{
int string;
int addr;
uint8_t out[4];
for (addr = 0; addr < ADDR_COUNT; addr++)
{
for (string = 0; string < STRING_COUNT; string++)
{
BULB_FLAG_ADDRESS(out) = addr;
if (string < STRING_COUNT - 1)
BULB_FLAG_ADDRESS(out) |= BULB_FLAG_COMBINE;
BULB_BLUE_STRING(out) = string;
BULB_GREEN_RED(out) = 0;
BULB_BRIGHT(out) = 0;
process_bulb(out);
}
if (pause > 0)
delay(pause);
}
}
/*----------------------------------------------------------------------------
** chase
** Send a pattern along the strings by way of testing
**--------------------------------------------------------------------------*/
void chase(void)
{
int string;
int addr;
int red, green, blue;
int bright;
uint8_t out[4];
for (addr = 0; addr < ADDR_COUNT; addr++)
{
for (bright = MAX_BRIGHT; bright >= 0; bright--)
for (string = 0; string < STRING_COUNT; string++)
{
red = green = blue = 0;
switch(string)
{
case 0: red = 13; break;
case 1: green = 13; break;
case 2: blue = 13; break;
case 3: blue = 13; red = 13; break;
case 4: green = 13; red = 13; break;
case 5: blue = 13; green = 13; red = 13; break;
}
BULB_FLAG_ADDRESS(out) = addr;
if (string < STRING_COUNT - 1)
BULB_FLAG_ADDRESS(out) |= BULB_FLAG_COMBINE;
BULB_BLUE_STRING(out) = string | (blue << 4);
BULB_GREEN_RED(out) = (green << 4) | red;
BULB_BRIGHT(out) = bright;
process_bulb(out);
}
}
}
/*----------------------------------------------------------------------------
** flood
** Flood all the strings with a single color
**--------------------------------------------------------------------------*/
void flood(void)
{
int string;
int addr;
int red, green, blue;
uint8_t out[4];
for (addr = 0; addr < ADDR_COUNT; addr++)
{
for (string = 0; string < STRING_COUNT; string++)
{
red = green = blue = 0;
switch(string)
{
case 0: red = 13; break;
case 1: green = 13; break;
case 2: blue = 13; break;
case 3: blue = 13; red = 13; break;
case 4: green = 13; red = 13; break;
case 5: blue = 13; green = 13; red = 13; break;
}
BULB_FLAG_ADDRESS(out) = addr;
if (string < STRING_COUNT - 1)
BULB_FLAG_ADDRESS(out) |= BULB_FLAG_COMBINE;
BULB_BLUE_STRING(out) = string | (blue << 4);
BULB_GREEN_RED(out) = (green << 4) | red;
BULB_BRIGHT(out) = MAX_BRIGHT;
process_bulb(out);
}
}
}
void flush_sync(void)
{
uint8_t c;
do
{
c = Serial.read();
}
while (c == COMMAND_SYNC);
}
void process_command(const uint8_t *data)
{
switch(BULB_FLAG_ADDRESS(data))
{
case COMMAND_ACK:
/*----------------------------------------------------------------
** Command 1: Echo. We just return the next 3 bytes
**--------------------------------------------------------------*/
Serial.write(data + 1, 3);
break;
case COMMAND_SYNC:
flush_sync();
break;
case COMMAND_INIT:
init(10);
break;
case COMMAND_STATUS:
Serial.println("AVR LED alive.");
if (g_scrolling)
Serial.println(" Displaying builtin message:");
else
Serial.println(" NOT Displaying builtin message:");
Serial.println(MESSAGE_TEXT);
Serial.print('\0');
break;
case COMMAND_CLEAR:
init(0);
break;
case COMMAND_CHASE:
chase();
break;
case COMMAND_FLOOD:
flood();
break;
case COMMAND_SCROLL_DISPLAY_OFF:
g_scrolling = 0;
break;
case COMMAND_SCROLL_DISPLAY:
g_scrolling = 1;
if (g_scrolling)
{
g_scroll_blueshift = BULB_BLUESHIFT(data);
g_scroll_greenshift = BULB_GREENSHIFT(data);
g_scroll_redshift = BULB_REDSHIFT(data);
g_scroll_bright = BULB_BRIGHT(data);
scroll_display();
g_next_scroll = millis() + SCROLL_INTERVAL;
}
else
init(0);
break;
default:
Serial.print("Unknown command: ");
Serial.println((int) BULB_FLAG_ADDRESS(data));
}
}
/*----------------------------------------------------------------------------
** Initial setup
**--------------------------------------------------------------------------*/
void setup()
{
memset(ring, 0, sizeof(ring));
Serial.begin(115200);
/*------------------------------------------------------------------------
** Start our out bound processing loop. DDRB sets the direction of the
** PORTB register; we want it all set to output
**----------------------------------------------------------------------*/
start_timer1();
DDRB = ALL_STRINGS_MASK;
if (g_init_at_start)
{
delay(2000);
init(10);
}
}
void loop()
{
uint8_t data[4];
if (g_scrolling)
check_scroll();
/*------------------------------------------------------------------------
** Pull a command from the serial port, and execute it
**----------------------------------------------------------------------*/
if (Serial.available() >= 4)
{
data[0] = Serial.read();
data[1] = Serial.read();
data[2] = Serial.read();
data[3] = Serial.read();
if (IS_COMMAND(data))
process_command(data);
else
process_bulb(data);
}
}