|
9 | 9 | #include "py/mphal.h"
|
10 | 10 | #include "modesp.h"
|
11 | 11 |
|
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 | + } |
30 | 57 | } 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 | + } |
35 | 69 | }
|
| 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 |
36 | 115 |
|
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 | + } |
49 | 127 | }
|
50 | 128 | }
|
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; |
53 | 166 | }
|
0 commit comments