Skip to content

Commit 674367b

Browse files
committed
esp32/neopixel: Improvements to neopixel and docs
1 parent 73c94bb commit 674367b

File tree

8 files changed

+246
-54
lines changed

8 files changed

+246
-54
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,7 @@ user.props
4343
# Generated rst files
4444
######################
4545
genrst/
46+
47+
# IDE Config
48+
.idea/
49+
.vscode/

docs/esp32/quickref.rst

+27-5
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,35 @@ Use the ``neopixel`` module::
412412
For low-level driving of a NeoPixel::
413413

414414
import esp
415-
esp.neopixel_write(pin, grb_buf, is800khz)
415+
esp.neopixel_write(pin, grb_buf, timing=NeoPixel.WS2812B_TIMING)
416416

417417
.. Warning::
418-
By default ``NeoPixel`` is configured to control the more popular *800kHz*
419-
units. It is possible to use alternative timing to control other (typically
420-
400kHz) devices by passing ``timing=0`` when constructing the
421-
``NeoPixel`` object.
418+
By default ``NeoPixel`` is configured to control the more popular 800kHz
419+
WS2812B units. It is possible to use alternative timing to control other
420+
(typically 400kHz WS2811) devices by passing ``timing=NeoPixel.WS2811_TIMING``
421+
when constructing the ``NeoPixel`` object.
422+
423+
For extremely low-level driving of NeoPixel-like strands, such as the SK6812,
424+
or future types that may not yet be supported, or the "totally" "neopixels" you
425+
got off of eBay, an even lower-level function is available, that allows
426+
you to specify custom clock cycle timings. This may be helpful even if your
427+
NeoPixel strand is supported by the higher level functions, if you're looking
428+
to drive your strand to its limits, as fast as it will go, particularly for
429+
persistence of vision effects. You could also flip the timing for the 0 and
430+
the 1 and then stream the same bits, but send the opposite colors.::
431+
432+
from esp import neopixel_write_timings
433+
pin23 = Pin(23)
434+
pixel_grb_data = bytearray(b'\x10\x00\x00\x00\x10\x00\x00\x00\x10\x10\x10\x10')
435+
# Clock cycles to drive timing (be sure to consider your CPU frequency)
436+
# Consult the datasheet for your particular strand timings
437+
t0high_ticks = 1
438+
# This works! One clock cycle!
439+
t1high_ticks = 216
440+
t0low_ticks = 216
441+
t1low_ticks = 50
442+
latch_ticks = 35000
443+
neopixel_write_timings(pin23, pixel_grb_data, t0high_ticks, t1high_ticks, t0low_ticks, t1low_ticks, latch_ticks)
422444

423445

424446
Capacitive Touch

ports/esp32/Makefile

+19
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,25 @@ endif
103103
# pretty format of ESP IDF version, used internally by the IDF
104104
IDF_VER := $(shell git -C $(ESPIDF) describe)
105105

106+
CROSS_COMPILE_VER = $(shell xtensa-esp32-elf-c++ --version | head -n1)
107+
108+
# NOTE: If you change this, also change the README.md,
109+
# where it mentions the version of the toolchain
110+
# As of this writing, that's around these strings:
111+
# - "Currently this project uses the v3.3 toolchain"
112+
# - "xtensa-esp32-elf-c++ --version"
113+
CROSS_COMPILE_SUPVER = xtensa-esp32-elf-c++ (crosstool-NG crosstool-ng-1.22.0-80-g6c4433a) 5.2.0
114+
ifneq ($(CROSS_COMPILE_VER), $(CROSS_COMPILE_SUPVER))
115+
$(info ** WARNING **)
116+
$(info The version of xtensa cross-compiler does not match the supported version)
117+
$(info The build may complete and the firmware may work but it is not guaranteed)
118+
$(info The current toolchain supported by this project can be found here:)
119+
$(info - https://docs.espressif.com/projects/esp-idf/en/v3.3/get-started/index.html#setup-toolchain)
120+
$(info Toolchain path: $(shell which xtensa-esp32-elf-c++))
121+
$(info Current version: $(CROSS_COMPILE_VER))
122+
$(info Supported version: $(CROSS_COMPILE_SUPVER))
123+
endif
124+
106125
ifeq ($(shell which $(CC) 2> /dev/null),)
107126
$(info ** ERROR **)
108127
$(info Cannot find C compiler $(CC))

ports/esp32/README.md

+15-8
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ using the following command:
3838
Note: The ESP IDF v4.x support is currently experimental.
3939

4040
The binary toolchain (binutils, gcc, etc.) can be installed using the following
41-
guides:
41+
guides. Currently this project uses the v3.3 toolchain:
4242

43-
* [Linux installation](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/linux-setup.html)
44-
* [MacOS installation](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/macos-setup.html)
45-
* [Windows installation](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/windows-setup.html)
43+
* [Linux installation](https://docs.espressif.com/projects/esp-idf/en/v3.3/get-started/linux-setup.html)
44+
* [MacOS installation](https://docs.espressif.com/projects/esp-idf/en/v3.3/get-started/macos-setup.html)
45+
* [Windows installation](https://docs.espressif.com/projects/esp-idf/en/v3.3/get-started/windows-setup.html)
4646

4747
If you are on a Windows machine then the
4848
[Windows Subsystem for Linux](https://msdn.microsoft.com/en-au/commandline/wsl/install_guide)
@@ -69,9 +69,16 @@ as well as a copy of the ESP-IDF repository. You will need to update your `PATH`
6969
environment variable to include the ESP32 toolchain. For example, you can issue
7070
the following commands on (at least) Linux:
7171

72-
$ export PATH=$PATH:$HOME/esp/crosstool-NG/builds/xtensa-esp32-elf/bin
72+
$ export PATH="$HOME/esp/xtensa-esp32-elf/bin:$PATH"
7373

74-
You can put this command in your `.profile` or `.bash_login`.
74+
You can put this command in your `.bash_profile` (OSX) or `.bashrc` (Linux).
75+
76+
Once you have that properly set up, you should try running the following command to check the version:
77+
78+
```bash
79+
$ xtensa-esp32-elf-c++ --version
80+
# Expect crosstool-ng-1.22.0-80-g6c4433a) 5.2.0
81+
```
7582

7683
You then need to set the `ESPIDF` environment/makefile variable to point to
7784
the root of the ESP-IDF repository. You can set the variable in your PATH,
@@ -185,8 +192,8 @@ point when booting up. But for the most part the documentation and tutorials
185192
for the ESP8266 should apply to the ESP32 (at least for the components that
186193
are implemented).
187194

188-
See http://docs.micropython.org/en/latest/esp8266/esp8266/quickref.html for
189-
a quick reference, and http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html
195+
See http://docs.micropython.org/en/latest/esp32/quickref.html for
196+
a quick reference, and http://docs.micropython.org/en/latest/esp32/tutorial/intro.html
190197
for a tutorial.
191198

192199
The following function can be used to connect to a WiFi access point (you can

ports/esp32/espneopixel.c

+149-36
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,158 @@
99
#include "py/mphal.h"
1010
#include "modesp.h"
1111

12-
void IRAM_ATTR esp_neopixel_write(uint8_t pin, uint8_t *pixels, uint32_t numBytes, uint8_t timing) {
13-
uint8_t *p, *end, pix, mask;
14-
uint32_t t, time0, time1, period, c, startTime, pinMask;
15-
16-
pinMask = 1 << pin;
17-
p = pixels;
18-
end = p + numBytes;
19-
pix = *p++;
20-
mask = 0x80;
21-
startTime = 0;
22-
23-
uint32_t fcpu = ets_get_cpu_frequency() * 1000000;
24-
25-
if (timing == 1) {
26-
// 800 KHz
27-
time0 = (fcpu * 0.35) / 1000000; // 0.35us
28-
time1 = (fcpu * 0.8) / 1000000; // 0.8us
29-
period = (fcpu * 1.25) / 1000000; // 1.25us per bit
12+
/**
13+
* WS2812 timing
14+
* T0H: 0 code, high voltage time
15+
* T1H: 1 code, high voltage time
16+
* T0L: 0 code, low voltage time
17+
* T1L: 1 code, low voltage time
18+
*/
19+
20+
// 400KHz ancient v1 neopixels
21+
#define WS2811_TIMING 0
22+
#define WS2811_T0H_US (0.5)
23+
#define WS2811_T1H_US (1.2)
24+
#define WS2811_T0L_US (2.0)
25+
#define WS2811_T1L_US (1.3)
26+
27+
#define WS2812S_TIMING 1
28+
#define WS2812S_T0H_US (0.35)
29+
#define WS2812S_T1H_US (0.7)
30+
#define WS2812S_T0L_US (0.8)
31+
#define WS2812S_T1L_US (0.6)
32+
33+
#define WS2812B_TIMING 2
34+
#define WS2812B_T0H_US (0.35)
35+
#define WS2812B_T1H_US (0.9)
36+
#define WS2812B_T0L_US (0.9)
37+
#define WS2812B_T1L_US (0.35)
38+
39+
#define CPU_TICKS_HALFWAY (2147483648) // 2**31
40+
41+
__attribute__((always_inline)) static inline void wait_until_tick(uint32_t wait_until_tick) {
42+
// This function waits for the CPU clock cycle counting register to read the value specified
43+
// in wait_until_tick, it is always inlined for speed.
44+
uint32_t current_ticks = mp_hal_ticks_cpu();
45+
// By dropping 6 clock cycles, we give enough time for loops to complete
46+
uint32_t before_tick = wait_until_tick - 6;
47+
if (before_tick < CPU_TICKS_HALFWAY) {
48+
// The tick we are waiting for is closer to the beginning
49+
while (current_ticks > CPU_TICKS_HALFWAY) {
50+
// Wait for the current tick to loop around (if necessary)
51+
current_ticks = mp_hal_ticks_cpu();
52+
}
53+
while (current_ticks < before_tick) {
54+
// Wait for the current tick to be after the tick we are waiting for
55+
current_ticks = mp_hal_ticks_cpu();
56+
}
3057
} else {
31-
// 400 KHz
32-
time0 = (fcpu * 0.5) / 1000000; // 0.35us
33-
time1 = (fcpu * 1.2) / 1000000; // 0.8us
34-
period = (fcpu * 2.5) / 1000000; // 1.25us per bit
58+
// before_tick >= CPU_TICKS_HALFWAY
59+
// The tick we are waiting for is closer to the end
60+
while (current_ticks < CPU_TICKS_HALFWAY) {
61+
// Wait for the current tick to be in the final half
62+
current_ticks = mp_hal_ticks_cpu();
63+
}
64+
while ((current_ticks < before_tick) || (current_ticks < CPU_TICKS_HALFWAY)) {
65+
// Wait for the current tick to be after the tick we are waiting for
66+
// including the loop around, if we miss it
67+
current_ticks = mp_hal_ticks_cpu();
68+
}
3569
}
70+
}
71+
72+
uint32_t IRAM_ATTR esp_neopixel_write_timings(uint8_t pin, uint8_t *pixel_bytes, uint32_t numBytes,
73+
uint32_t t0high, uint32_t t1high, uint32_t t0low, uint32_t t1low, uint32_t t_latch) {
74+
// This function will actually write to the neopixel with custom timings
75+
// while it is normally called from the esp_neopixel_write function,
76+
// you can call it directly from python code, particularly if you have
77+
// the off-brand WS2812 style neopixels instead of the name brand
78+
// Neopixels from Adafruit.
79+
// The timing params are in clock cycles
80+
uint8_t *pixel_ptr, *end, pixel_color_byte, color_bit_mask;
81+
uint32_t start_time, pin_mask;
82+
83+
pin_mask = 1 << pin;
84+
pixel_ptr = pixel_bytes;
85+
end = pixel_ptr + numBytes;
86+
pixel_color_byte = *pixel_ptr++;
87+
color_bit_mask = 0x80;
88+
89+
uint32_t irq_state = mp_hal_quiet_timing_enter(); // Disable interrupts
90+
start_time = mp_hal_ticks_cpu();
91+
while (true) {
92+
wait_until_tick(start_time);
93+
if (pixel_color_byte & color_bit_mask) {
94+
// Send a 1
95+
// Set high
96+
GPIO_REG_WRITE(GPIO_OUT_W1TS_REG, pin_mask);
97+
start_time += t1high;
98+
wait_until_tick(start_time);
99+
// Set low
100+
GPIO_REG_WRITE(GPIO_OUT_W1TC_REG, pin_mask);
101+
start_time += t1low;
102+
} else {
103+
// Send a 0
104+
// Set high
105+
GPIO_REG_WRITE(GPIO_OUT_W1TS_REG, pin_mask);
106+
start_time += t0high;
107+
wait_until_tick(start_time);
108+
// Set low
109+
GPIO_REG_WRITE(GPIO_OUT_W1TC_REG, pin_mask);
110+
start_time += t0low;
111+
}
112+
// Time is most flexible when the pin is low. For way too much info, see:
113+
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
114+
// So we will do our loading logic here
36115

37-
uint32_t irq_state = mp_hal_quiet_timing_enter();
38-
for (t = time0;; t = time0) {
39-
if (pix & mask) t = time1; // Bit high duration
40-
while (((c = mp_hal_ticks_cpu()) - startTime) < period); // Wait for bit start
41-
GPIO_REG_WRITE(GPIO_OUT_W1TS_REG, pinMask); // Set high
42-
startTime = c; // Save start time
43-
while (((c = mp_hal_ticks_cpu()) - startTime) < t); // Wait high duration
44-
GPIO_REG_WRITE(GPIO_OUT_W1TC_REG, pinMask); // Set low
45-
if (!(mask >>= 1)) { // Next bit/byte
46-
if(p >= end) break;
47-
pix = *p++;
48-
mask = 0x80;
116+
// Shift the mask to the next bit
117+
color_bit_mask >>= 1;
118+
if (color_bit_mask == 0) {
119+
// Pixel color byte is totally streamed out
120+
if(pixel_ptr >= end) {
121+
// All pixel bytes are sent
122+
break;
123+
} else {
124+
pixel_color_byte = *pixel_ptr++;
125+
color_bit_mask = 0x80;
126+
}
49127
}
50128
}
51-
while ((mp_hal_ticks_cpu() - startTime) < period); // Wait for last bit
52-
mp_hal_quiet_timing_exit(irq_state);
129+
mp_hal_quiet_timing_exit(irq_state); // Enable interrupts again
130+
wait_until_tick(start_time + t_latch); // Done streaming data
131+
return t0high;
132+
}
133+
134+
uint32_t esp_neopixel_write(uint8_t pin, uint8_t *pixel_bytes, uint32_t numBytes, uint8_t timing) {
135+
uint32_t t1high, t1low, t0high, t0low, t_latch;
136+
uint32_t ticks_per_us = ets_get_cpu_frequency();
137+
138+
if (timing == WS2811_TIMING) {
139+
// WS2811 400KHz design
140+
t0high = ticks_per_us * WS2811_T0H_US;
141+
t1high = ticks_per_us * WS2811_T1H_US;
142+
t0low = ticks_per_us * WS2811_T0L_US;
143+
t1low = ticks_per_us * WS2811_T1L_US;
144+
} else if (timing == WS2812S_TIMING) {
145+
// WS2812S
146+
t0high = ticks_per_us * WS2812S_T0H_US;
147+
t1high = ticks_per_us * WS2812S_T1H_US;
148+
t0low = ticks_per_us * WS2812S_T0L_US;
149+
t1low = ticks_per_us * WS2812S_T1L_US;
150+
} else { //if (timing == WS2812B_TIMING) {
151+
// WS2812B
152+
t0high = ticks_per_us * WS2812B_T0H_US;
153+
t1high = ticks_per_us * WS2812B_T1H_US;
154+
t0low = ticks_per_us * WS2812B_T0L_US;
155+
t1low = ticks_per_us * WS2812B_T1L_US;
156+
}
157+
// NOTE: If you add another timing version here
158+
// (perhaps the APA106, WS2812D, WS2813, WS2815, PD9823, or the SK6812)
159+
// then be sure to mention it in the getting started documentation:
160+
// docs/esp32/quickref.rst
161+
// Also, you should add a new constant to the neopixel.py file
162+
// You can uncomment the commented if statement above.
163+
t_latch = ticks_per_us * 50;
164+
esp_neopixel_write_timings(pin, pixel_bytes, numBytes, t0high, t1high, t0low, t1low, t_latch);
165+
return t0high;
53166
}

ports/esp32/modesp.c

+23-3
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,31 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_gpio_matrix_out_obj, 4, 4, esp_gp
118118
STATIC mp_obj_t esp_neopixel_write_(mp_obj_t pin, mp_obj_t buf, mp_obj_t timing) {
119119
mp_buffer_info_t bufinfo;
120120
mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ);
121-
esp_neopixel_write(mp_hal_get_pin_obj(pin),
122-
(uint8_t*)bufinfo.buf, bufinfo.len, mp_obj_get_int(timing));
123-
return mp_const_none;
121+
return mp_obj_new_int_from_uint(esp_neopixel_write(mp_hal_get_pin_obj(pin),
122+
(uint8_t*)bufinfo.buf, bufinfo.len, mp_obj_get_int(timing)));
124123
}
125124
STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp_neopixel_write_obj, esp_neopixel_write_);
126125

126+
STATIC mp_obj_t esp_neopixel_write_timings_(size_t n_args, const mp_obj_t *args) {
127+
(void)n_args;
128+
mp_obj_t pin = args[0], buf = args[1], t0high = args[2], t1high = args[3], t0low = args[4], t1low = args[5], t_latch = args[6];
129+
mp_buffer_info_t bufinfo;
130+
mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ);
131+
return mp_obj_new_int_from_uint(
132+
esp_neopixel_write_timings(
133+
mp_hal_get_pin_obj(pin),
134+
(uint8_t*)bufinfo.buf,
135+
bufinfo.len,
136+
mp_obj_get_int(t0high),
137+
mp_obj_get_int(t1high),
138+
mp_obj_get_int(t0low),
139+
mp_obj_get_int(t1low),
140+
mp_obj_get_int(t_latch)
141+
)
142+
);
143+
}
144+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_neopixel_write_timings_obj, 7, 7, esp_neopixel_write_timings_);
145+
127146
STATIC const mp_rom_map_elem_t esp_module_globals_table[] = {
128147
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp) },
129148

@@ -139,6 +158,7 @@ STATIC const mp_rom_map_elem_t esp_module_globals_table[] = {
139158
{ MP_ROM_QSTR(MP_QSTR_gpio_matrix_out), MP_ROM_PTR(&esp_gpio_matrix_out_obj) },
140159

141160
{ MP_ROM_QSTR(MP_QSTR_neopixel_write), MP_ROM_PTR(&esp_neopixel_write_obj) },
161+
{ MP_ROM_QSTR(MP_QSTR_neopixel_write_timings), MP_ROM_PTR(&esp_neopixel_write_timings_obj) },
142162
{ MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) },
143163

144164
// Constants for second arg of osdebug()

ports/esp32/modesp.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
void esp_neopixel_write(uint8_t pin, uint8_t *pixels, uint32_t numBytes, uint8_t timing);
1+
2+
uint32_t esp_neopixel_write(uint8_t pin, uint8_t *pixels, uint32_t numBytes, uint8_t timing);
3+
4+
uint32_t esp_neopixel_write_timings(uint8_t pin, uint8_t *pixel_bytes, uint32_t numBytes,
5+
uint32_t t0high, uint32_t t1high, uint32_t t0low, uint32_t t1low, uint32_t t_latch);

ports/esp32/modules/neopixel.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
class NeoPixel:
88
ORDER = (1, 0, 2, 3)
9+
WS2811_TIMING = 0
10+
WS2812S_TIMING = 1
11+
WS2812B_TIMING = 2
912

10-
def __init__(self, pin, n, bpp=3, timing=1):
13+
def __init__(self, pin, n, bpp=3, timing=WS2812S_TIMING):
1114
self.pin = pin
1215
self.n = n
1316
self.bpp = bpp

0 commit comments

Comments
 (0)