-
Notifications
You must be signed in to change notification settings - Fork 72
/
Copy pathAdafruit_WS2801.cpp
319 lines (286 loc) · 9.26 KB
/
Adafruit_WS2801.cpp
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
/*!
* @file Adafruit_WS2801.cpp
*
* @mainpage Adafruit WS2801 Library
*
* @section intro_sec Introduction
*
* Example to control WS2801-based RGB LED Modules in a strand or strip
*
* @section author Author
*
* Written by Adafruit
* @section license License
* MIT license
*/
#include "Adafruit_WS2801.h"
#ifdef __AVR_ATtiny85__
// Teensy/Gemma-specific stuff for hardware-assisted SPI @ 1 MHz
#if (F_CPU > 8000000L)
#define SPI_DELAY asm volatile("rjmp .+0\nrjmp .+0"); // Burn 4 cycles
#elif (F_CPU > 4000000L)
#define SPI_DELAY asm volatile("rjmp .+0"); // Burn 2 cycles
#else
#define SPI_DELAY // Run max speed
#endif
#define SPIBIT \
USICR = ((1 << USIWM0) | (1 << USITC)); \
SPI_DELAY \
USICR = ((1 << USIWM0) | (1 << USITC) | (1 << USICLK)); \
SPI_DELAY
static void spi_out(uint8_t n) {
USIDR = n;
SPIBIT
SPIBIT
SPIBIT
SPIBIT
SPIBIT
SPIBIT
SPIBIT
SPIBIT
}
#else
// All other boards support Full and Proper Hardware SPI
#include <SPI.h>
#define spi_out(n) (void)SPI.transfer(n) //!< Sets up SPI
#endif
// Constructor for use with hardware SPI (specific clock/data pins):
Adafruit_WS2801::Adafruit_WS2801(uint16_t n, uint8_t order) {
rgb_order = order;
alloc(n);
updatePins();
}
// Constructor for use with arbitrary clock/data pins:
Adafruit_WS2801::Adafruit_WS2801(uint16_t n, uint8_t dpin, uint8_t cpin,
uint8_t order) {
rgb_order = order;
alloc(n);
updatePins(dpin, cpin);
}
// Constructor for use with a matrix configuration, specify w, h for size
// of matrix. Assumes configuration where string starts at coordinate 0,0
// and continues to w-1,0, w-1,1 and on to 0,1, 0,2 and on to w-1,2 and
// so on. Snaking back and forth till the end. Other function calls
// provide access to pixels via an x,y coordinate system
Adafruit_WS2801::Adafruit_WS2801(uint16_t w, uint16_t h, uint8_t dpin,
uint8_t cpin, uint8_t order) {
rgb_order = order;
alloc(w * h);
width = w;
height = h;
updatePins(dpin, cpin);
}
// Allocate 3 bytes per pixel, init to RGB 'off' state:
void Adafruit_WS2801::alloc(uint16_t n) {
begun = false;
numLEDs = ((pixels = (uint8_t *)calloc(n, 3)) != NULL) ? n : 0;
}
// via Michael Vogt/neophob: empty constructor is used when strand length
// isn't known at compile-time; situations where program config might be
// read from internal flash memory or an SD card, or arrive via serial
// command. If using this constructor, MUST follow up with updateLength()
// and updatePins() to establish the strand length and output pins!
// Also, updateOrder() to change RGB vs GRB order (RGB is default).
Adafruit_WS2801::Adafruit_WS2801(void) {
begun = false;
numLEDs = 0;
pixels = NULL;
rgb_order = WS2801_RGB;
updatePins(); // Must assume hardware SPI until pins are set
}
// Release memory (as needed):
Adafruit_WS2801::~Adafruit_WS2801(void) {
if (pixels)
free(pixels);
}
// Activate hard/soft SPI as appropriate:
void Adafruit_WS2801::begin(void) {
if (hardwareSPI == true) {
startSPI();
} else {
pinMode(datapin, OUTPUT);
pinMode(clkpin, OUTPUT);
}
begun = true;
}
// Change pin assignments post-constructor, switching to hardware SPI:
void Adafruit_WS2801::updatePins(void) {
pinMode(datapin, INPUT); // Restore data and clock pins to inputs
pinMode(clkpin, INPUT);
datapin = clkpin = 0;
hardwareSPI = true;
// If begin() was previously invoked, init the SPI hardware now:
if (begun == true)
startSPI();
// Otherwise, SPI is NOT initted until begin() is explicitly called.
}
// Change pin assignments post-constructor, using arbitrary pins:
void Adafruit_WS2801::updatePins(uint8_t dpin, uint8_t cpin) {
if (begun == true) { // If begin() was previously invoked...
// If previously using hardware SPI, turn that off:
if (hardwareSPI) {
#ifdef __AVR_ATtiny85__
DDRB &= ~(_BV(PORTB1) | _BV(PORTB2));
#else
SPI.end();
#endif
} else {
pinMode(datapin, INPUT); // Restore prior data and clock pins to inputs
pinMode(clkpin, INPUT);
}
pinMode(dpin, OUTPUT); // Enable output on 'soft' SPI pins:
pinMode(cpin, OUTPUT);
}
datapin = dpin;
clkpin = cpin;
#ifdef __AVR__
clkport = portOutputRegister(digitalPinToPort(cpin));
clkpinmask = digitalPinToBitMask(cpin);
dataport = portOutputRegister(digitalPinToPort(dpin));
datapinmask = digitalPinToBitMask(dpin);
#endif
hardwareSPI = false;
}
// Enable SPI hardware and set up protocol details:
void Adafruit_WS2801::startSPI(void) {
#ifdef __AVR_ATtiny85__
PORTB &= ~(_BV(PORTB1) | _BV(PORTB2)); // Outputs
DDRB |= _BV(PORTB1) | _BV(PORTB2); // DO (NOT MOSI) + SCK
#else
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
#if defined(__AVR__) || defined(CORE_TEENSY)
SPI.setClockDivider(SPI_CLOCK_DIV16); // 1MHz max, else flicker
#else
SPI.setClockDivider((F_CPU + 500000L) / 1000000L);
#endif
#endif
}
uint16_t Adafruit_WS2801::numPixels(void) { return numLEDs; }
// Change strand length (see notes with empty constructor, above):
void Adafruit_WS2801::updateLength(uint16_t n) {
if (pixels != NULL)
free(pixels); // Free existing data (if any)
// Allocate new data -- note: ALL PIXELS ARE CLEARED
numLEDs = ((pixels = (uint8_t *)calloc(n, 3)) != NULL) ? n : 0;
// 'begun' state does not change -- pins retain prior modes
}
// Change RGB data order (see notes with empty constructor, above):
void Adafruit_WS2801::updateOrder(uint8_t order) {
rgb_order = order;
// Existing LED data, if any, is NOT reformatted to new data order.
// Calling function should clear or fill pixel data anew.
}
void Adafruit_WS2801::show(void) {
uint16_t i, nl3 = numLEDs * 3; // 3 bytes per LED
uint8_t bit;
// Write 24 bits per pixel:
if (hardwareSPI) {
for (i = 0; i < nl3; i++)
spi_out(pixels[i]);
} else {
for (i = 0; i < nl3; i++) {
for (bit = 0x80; bit; bit >>= 1) {
#ifdef __AVR__
if (pixels[i] & bit)
*dataport |= datapinmask;
else
*dataport &= ~datapinmask;
*clkport |= clkpinmask;
*clkport &= ~clkpinmask;
#else
if (pixels[i] & bit)
digitalWrite(datapin, HIGH);
else
digitalWrite(datapin, LOW);
digitalWrite(clkpin, HIGH);
digitalWrite(clkpin, LOW);
#endif
}
}
}
delay(1); // Data is latched by holding clock pin low for 1 millisecond
}
// Set pixel color from separate 8-bit R, G, B components:
void Adafruit_WS2801::setPixelColor(uint16_t n, uint8_t r, uint8_t g,
uint8_t b) {
if (n < numLEDs) { // Arrays are 0-indexed, thus NOT '<='
uint8_t *p = &pixels[n * 3];
// See notes later regarding color order
if (rgb_order == WS2801_RGB) {
*p++ = r;
*p++ = g;
} else {
*p++ = g;
*p++ = r;
}
*p++ = b;
}
}
// Set pixel color from separate 8-bit R, G, B components using x,y coordinate
// system:
void Adafruit_WS2801::setPixelColor(uint16_t x, uint16_t y, uint8_t r,
uint8_t g, uint8_t b) {
boolean evenRow = ((y % 2) == 0);
// calculate x offset first
uint16_t offset = x % width;
if (!evenRow) {
offset = (width - 1) - offset;
}
// add y offset
offset += y * width;
setPixelColor(offset, r, g, b);
}
// Set pixel color from 'packed' 32-bit RGB value:
void Adafruit_WS2801::setPixelColor(uint16_t n, uint32_t c) {
if (n < numLEDs) { // Arrays are 0-indexed, thus NOT '<='
uint8_t *p = &pixels[n * 3];
// To keep the show() loop as simple & fast as possible, the
// internal color representation is native to different pixel
// types. For compatibility with existing code, 'packed' RGB
// values passed in or out are always 0xRRGGBB order.
if (rgb_order == WS2801_RGB) {
*p++ = c >> 16; // Red
*p++ = c >> 8; // Green
} else {
*p++ = c >> 8; // Green
*p++ = c >> 16; // Red
}
*p++ = c; // Blue
}
}
// Set pixel color from 'packed' 32-bit RGB value using x,y coordinate system:
void Adafruit_WS2801::setPixelColor(uint16_t x, uint16_t y, uint32_t c) {
boolean evenRow = ((y % 2) == 0);
// calculate x offset first
uint16_t offset = x % width;
if (!evenRow) {
offset = (width - 1) - offset;
}
// add y offset
offset += y * width;
setPixelColor(offset, c);
}
// Clear all pixels
void Adafruit_WS2801::clear() {
if (pixels != NULL) {
memset(pixels, 0, numLEDs * 3);
}
}
// Query color from previously-set pixel (returns packed 32-bit RGB value)
uint32_t Adafruit_WS2801::getPixelColor(uint16_t n) {
if (n < numLEDs) {
uint16_t ofs = n * 3;
// To keep the show() loop as simple & fast as possible, the
// internal color representation is native to different pixel
// types. For compatibility with existing code, 'packed' RGB
// values passed in or out are always 0xRRGGBB order.
return (rgb_order == WS2801_RGB)
? ((uint32_t)pixels[ofs] << 16) |
((uint16_t)pixels[ofs + 1] << 8) | pixels[ofs + 2]
: (pixels[ofs] << 8) | ((uint32_t)pixels[ofs + 1] << 16) |
pixels[ofs + 2];
}
return 0; // Pixel # is out of bounds
}