/
jswrap_neopixel.c
303 lines (264 loc) · 10.3 KB
/
jswrap_neopixel.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
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* Contains JavaScript interface for Neopixel/WS281x/APA10x devices
* ----------------------------------------------------------------------------
*/
#ifdef NRF52_SERIES
#include "i2s_ws2812b_drive.h"
#endif
#ifdef ESP8266
#include <c_types.h>
#include <espmissingincludes.h>
#endif
#ifdef ESP32
#include "esp32_neopixel.h"
#endif
#ifdef WIO_LTE
#include "stm32_ws2812b_driver.h"
#endif
#include <jswrap_neopixel.h>
#include "jsvariterator.h"
#include "jspininfo.h"
#include "jshardware.h"
#include "jsspi.h"
/** Send the given RGB pixel data out to neopixel/WS2811/APA104/etc */
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize);
/*JSON{
"type" : "library",
"class" : "neopixel"
}
This library allows you to write to Neopixel/WS281x/APA10x/SK6812 LED strips
These use a high speed single-wire protocol which needs platform-specific
implementation on some devices - hence this library to simplify things.
*/
/*JSON{
"type" : "staticmethod",
"class" : "neopixel",
"name" : "write",
"generate" : "jswrap_neopixel_write",
"params" : [
["pin", "pin", "The Pin the LEDs are connected to"],
["data","JsVar","The data to write to the LED strip (must be a multiple of 3 bytes long)"]
]
}
Write to a strip of NeoPixel/WS281x/APA104/APA106/SK6812-style LEDs attached to
the given pin.
```
// set just one pixel, red, green, blue
require("neopixel").write(B15, [255,0,0]);
```
```
// Produce an animated rainbow over 25 LEDs
var rgb = new Uint8ClampedArray(25*3);
var pos = 0;
function getPattern() {
pos++;
for (var i=0;i<rgb.length;) {
rgb[i++] = (1 + Math.sin((i+pos)*0.1324)) * 127;
rgb[i++] = (1 + Math.sin((i+pos)*0.1654)) * 127;
rgb[i++] = (1 + Math.sin((i+pos)*0.1)) * 127;
}
return rgb;
}
setInterval(function() {
require("neopixel").write(B15, getPattern());
}, 100);
```
**Note:**
* Different types of LED have the data in different orders - so don't be
surprised by RGB or BGR orderings!
* Some LED strips (SK6812) actually take 4 bytes per LED (red, green, blue and
white). These are still supported but the array of data supplied must still be a
multiple of 3 bytes long. Just round the size up - it won't cause any problems.
* On some platforms like STM32, pins capable of hardware SPI MOSI are required.
* Espruino devices tend to have 3.3v IO, while WS2812/etc run off of 5v. Many
WS2812 will only register a logic '1' at 70% of their input voltage - so if
powering them off 5v you will not be able to send them data reliably. You can
work around this by powering the LEDs off a lower voltage (for example 3.7v from
a LiPo battery), can put the output into the `af_opendrain` state and use a
pullup resistor to 5v on STM32 based boards (nRF52 are not 5v tolerant so you
can't do this), or can use a level shifter to shift the voltage up into the 5v
range.
*/
void jswrap_neopixel_write(Pin pin, JsVar *data) {
JSV_GET_AS_CHAR_ARRAY(rgbData, rgbSize, data);
if (!rgbData) {
jsExceptionHere(JSET_ERROR, "Couldn't convert %t to data to send to LEDs", data);
return;
}
if (rgbSize == 0) {
jsExceptionHere(JSET_ERROR, "Data must be a non empty array.");
return;
}
if (rgbSize % 3 != 0) {
jsExceptionHere(JSET_ERROR, "Data length must be a multiple of 3 (RGB).");
return;
}
neopixelWrite(pin, (unsigned char *)rgbData, rgbSize);
}
// -----------------------------------------------------------------------------------
// -------------------------------------------------------------- Platform specific
// -----------------------------------------------------------------------------------
#if defined(WIO_LTE)
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize) {
return stm32_neopixelWrite(pin, rgbData, rgbSize);
}
#elif defined(STM32) // ----------------------------------------------------------------
// this one could potentially work on other platforms as well...
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize) {
if (!jshIsPinValid(pin)) {
jsExceptionHere(JSET_ERROR, "Pin is not valid.");
return false;
}
JshPinFunction spiDevice = 0;
unsigned int i;
for (i=0;i<JSH_PININFO_FUNCTIONS;i++) {
if (JSH_PINFUNCTION_IS_SPI(pinInfo[pin].functions[i]) &&
((pinInfo[pin].functions[i] & JSH_MASK_INFO)==JSH_SPI_MOSI)) {
spiDevice = pinInfo[pin].functions[i];
}
}
IOEventFlags device = jshGetFromDevicePinFunction(spiDevice);
if (!spiDevice || !device) {
jsExceptionHere(JSET_ERROR, "No suitable SPI device found for this pin\n");
return false;
}
JshSPIInfo inf;
jshSPIInitInfo(&inf);
inf.baudRate = 3200000;
inf.pinMOSI = pin;
jshSPISetup(device, &inf);
jshSPISet16(device, true); // 16 bit output
// we're just sending (no receive)
jshSPISetReceive(device, false);
jshInterruptOff();
for (i=0;i<rgbSize;i++)
jsspiSend4bit(device, rgbData[i], 1, 3);
jshInterruptOn();
jshSPIWait(device); // wait until SPI send finished and clear the RX buffer
jshSPISet16(device, false); // back to 8 bit
return true;
}
#elif defined(NRF52_SERIES) // ----------------------------------------------------------------
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize) {
#ifdef NRF52_SERIES
if (!jshIsPinValid(pin)) {
jsExceptionHere(JSET_ERROR, "Pin is not valid.");
return false;
}
return !i2s_ws2812b_drive_xfer((rgb_led_t *)rgbData, rgbSize/3, pinInfo[pin].pin);
#else
jsExceptionHere(JSET_ERROR, "Neopixel writing not implemented");
return false;
#endif
}
#elif defined(ESP8266) // ----------------------------------------------------------------
static inline uint32_t _getCycleCount(void) {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
return ccount;
}
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize) {
if (!jshIsPinValid(pin)) {
jsExceptionHere(JSET_ERROR, "Pin is not valid.");
return false;
}
if (!jshGetPinStateIsManual(pin))
jshPinSetState(pin, JSHPINSTATE_GPIO_OUT);
// values for 160Mhz clock
uint8_t tOne = 90; // one bit, high typ 800ns
uint8_t tZero = 40; // zero bit, high typ 300ns
uint8_t tLow = 170; // total cycle, typ 1.2us
if (system_get_cpu_freq() < 100) {
tOne = 56; // 56 cyc = 700ns
tZero = 14; // 14 cycl = 175ns
tLow = 100;
}
#define _BV(bit) (1 << (bit))
#if 1
// the loop over the RGB pixel bits below is loaded into the instruction cache from flash
// with the result that dependeing on the cache line alignment the first loop iteration
// takes too long and thereby messes up the first LED.
// The fix is to make it so the first loop iteration does nothing, i.e. just outputs the
// same "low" for the full loop as we had before entering this function. This way no LED
// gets set to the wrong value and we load the cache line so the second iteration, i.e.,
// first real LED bit, runs at full speed.
uint32_t pinMask = _BV(pin); // bit mask for GPIO pin to write to reg
uint8_t *p = (uint8_t *)rgbData; // pointer to walk through pixel array
uint8_t *end = p + rgbSize; // pointer to end of array
uint8_t pix = *p++; // current byte being shifted out
uint8_t mask = 0x80; // mask for current bit
uint32_t start; // start time of bit
// adjust things for the pre-roll
p--; // next byte we fetch will be the first byte again
mask = 0x01; // fetch the next byte at the end of the first loop iteration
pinMask = 0; // zero mask means we set or clear no I/O pin
// iterate through all bits
ets_intr_lock(); // disable most interrupts
while(1) {
uint32_t t;
if (pix & mask) t = tOne;
else t = tZero;
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high
start = _getCycleCount(); // get start time of this bit
while (_getCycleCount()-start < t) ; // busy-wait
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low
if (!(mask >>= 1)) { // Next bit/byte?
if (p >= end) break; // at end, we're done
pix = *p++;
mask = 0x80;
pinMask = _BV(pin);
}
while (_getCycleCount()-start < tLow) ; // busy-wait
}
while (_getCycleCount()-start < tLow) ; // wait for last bit
ets_intr_unlock();
#else
// this is the code without preloading the first bit
uint32_t pinMask = _BV(pin); // bit mask for GPIO pin to write to reg
uint8_t *p = (uint8_t *)rgbData; // pointer to walk through pixel array
uint8_t *end = p + rgbSize; // pointer to end of array
uint8_t pix = *p++; // current byte being shifted out
uint8_t mask = 0x80; // mask for current bit
uint32_t start; // start time of bit
// iterate through all bits
while(1) {
uint32_t t;
if (pix & mask) t = 56; // one bit, high typ 800ns (56 cyc = 700ns)
else t = 14; // zero bit, high typ 300ns (14 cycl = 175ns)
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high
start = _getCycleCount(); // get start time of this bit
while (_getCycleCount()-start < t) ; // busy-wait
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low
if (!(mask >>= 1)) { // Next bit/byte?
if (p >= end) break; // at end, we're done
pix = *p++;
mask = 0x80;
}
while (_getCycleCount()-start < 100) ; // busy-wait, 100 cyc = 1.25us
}
while (_getCycleCount()-start < 100) ; // Wait for last bit
#endif
#undef _BV
return true;
}
#elif defined(ESP32)
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize){
return esp32_neopixelWrite(pin,rgbData, rgbSize);
}
#else
bool neopixelWrite(Pin pin, unsigned char *rgbData, size_t rgbSize) {
jsExceptionHere(JSET_ERROR, "Neopixel writing not implemented");
return false;
}
#endif