Skip to content

Commit 13db655

Browse files
committed
ESP NeoPixel fixes
This tweaks the RMT timing to better match the 1/3 and 2/3 of 800khz guideline for timing. It also ensures a delay of 300 microseconds with the line low before reset. Pin reset is now changed to the IDF default which pulls the pin up rather than CircuitPython's old behavior of floating the pin. Fixes #5679
1 parent 8bae6af commit 13db655

File tree

5 files changed

+32
-30
lines changed

5 files changed

+32
-30
lines changed

ports/atmel-samd/common-hal/neopixel_write/__init__.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa
100100
"");
101101
}
102102

103-
uint64_t next_start_raw_ticks = 0;
103+
STATIC uint64_t next_start_raw_ticks = 0;
104104

105105
void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t numBytes) {
106106
// This is adapted directly from the Adafruit NeoPixel library SAMD21G18A code:

ports/espressif/common-hal/microcontroller/Pin.c

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,6 @@
3636
STATIC uint32_t never_reset_pins[2];
3737
STATIC uint32_t in_use[2];
3838

39-
STATIC void floating_gpio_reset(gpio_num_t pin_number) {
40-
// This is the same as gpio_reset_pin(), but without the pullup.
41-
// Note that gpio_config resets the iomatrix to GPIO_FUNC as well.
42-
gpio_config_t cfg = {
43-
.pin_bit_mask = BIT64(pin_number),
44-
.mode = GPIO_MODE_DISABLE,
45-
.pull_up_en = false,
46-
.pull_down_en = false,
47-
.intr_type = GPIO_INTR_DISABLE,
48-
};
49-
gpio_config(&cfg);
50-
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pin_number], 0);
51-
}
52-
5339
void never_reset_pin_number(gpio_num_t pin_number) {
5440
if (pin_number == NO_PIN) {
5541
return;
@@ -72,7 +58,7 @@ void reset_pin_number(gpio_num_t pin_number) {
7258
never_reset_pins[pin_number / 32] &= ~(1 << pin_number % 32);
7359
in_use[pin_number / 32] &= ~(1 << pin_number % 32);
7460

75-
floating_gpio_reset(pin_number);
61+
gpio_reset_pin(pin_number);
7662
}
7763

7864
void common_hal_mcu_pin_reset_number(uint8_t i) {
@@ -93,7 +79,7 @@ void reset_all_pins(void) {
9379
(never_reset_pins[i / 32] & (1 << i % 32)) != 0) {
9480
continue;
9581
}
96-
floating_gpio_reset(i);
82+
gpio_reset_pin(i);
9783
}
9884
in_use[0] = never_reset_pins[0];
9985
in_use[1] = never_reset_pins[1];

ports/espressif/common-hal/neopixel_write/__init__.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,23 @@
4343
#include "py/mphal.h"
4444
#include "py/runtime.h"
4545
#include "shared-bindings/neopixel_write/__init__.h"
46+
#include "supervisor/port.h"
4647
#include "components/driver/include/driver/rmt.h"
4748
#include "peripherals/rmt.h"
4849

49-
#define WS2812_T0H_NS (350)
50-
#define WS2812_T0L_NS (1000)
51-
#define WS2812_T1H_NS (1000)
52-
#define WS2812_T1L_NS (350)
53-
#define WS2812_RESET_US (280)
50+
// 416 ns is 1/3 of the 1250ns period of a 800khz signal.
51+
#define WS2812_T0H_NS (416)
52+
#define WS2812_T0L_NS (416 * 2)
53+
#define WS2812_T1H_NS (416 * 2)
54+
#define WS2812_T1L_NS (416)
5455

5556
static uint32_t ws2812_t0h_ticks = 0;
5657
static uint32_t ws2812_t1h_ticks = 0;
5758
static uint32_t ws2812_t0l_ticks = 0;
5859
static uint32_t ws2812_t1l_ticks = 0;
5960

61+
static uint64_t next_start_raw_ticks = 0;
62+
6063
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
6164
size_t wanted_num, size_t *translated_size, size_t *item_num) {
6265
if (src == NULL || dest == NULL) {
@@ -107,21 +110,29 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout,
107110
if (rmt_get_counter_clock(config.channel, &counter_clk_hz) != ESP_OK) {
108111
mp_raise_RuntimeError(translate("Could not retrieve clock"));
109112
}
110-
float ratio = (float)counter_clk_hz / 1e9;
111-
ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
112-
ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
113-
ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
114-
ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
113+
size_t ns_per_tick = 1e9 / counter_clk_hz;
114+
ws2812_t0h_ticks = WS2812_T0H_NS / ns_per_tick;
115+
ws2812_t0l_ticks = WS2812_T0L_NS / ns_per_tick;
116+
ws2812_t1h_ticks = WS2812_T1H_NS / ns_per_tick;
117+
ws2812_t1l_ticks = WS2812_T1L_NS / ns_per_tick;
115118

116119
// Initialize automatic timing translator
117120
rmt_translator_init(config.channel, ws2812_rmt_adapter);
118121

122+
// Wait to make sure we don't append onto the last transmission. This should only be a tick or
123+
// two.
124+
while (port_get_raw_ticks(NULL) < next_start_raw_ticks) {
125+
}
126+
119127
// Write and wait to finish
120128
if (rmt_write_sample(config.channel, pixels, (size_t)numBytes, true) != ESP_OK) {
121129
mp_raise_RuntimeError(translate("Input/output error"));
122130
}
123131
rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
124132

133+
// Update the next start to +2 ticks. It ensures that we've gone 300+ us.
134+
next_start_raw_ticks = port_get_raw_ticks(NULL) + 2;
135+
125136
// Free channel again
126137
peripherals_free_rmt(config.channel);
127138
// Swap pin back to GPIO mode

ports/raspberrypi/common-hal/neopixel_write/__init__.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,6 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout,
9898
gpio_init(digitalinout->pin->number);
9999
common_hal_digitalio_digitalinout_switch_to_output((digitalio_digitalinout_obj_t *)digitalinout, false, DRIVE_MODE_PUSH_PULL);
100100

101-
// Update the next start.
102-
next_start_raw_ticks = port_get_raw_ticks(NULL) + 1;
101+
// Update the next start to +2 ticks. This ensures we give it at least 300us.
102+
next_start_raw_ticks = port_get_raw_ticks(NULL) + 2;
103103
}

supervisor/shared/status_leds.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ uint8_t rgb_status_brightness = 63;
4848
#define MICROPY_HW_NEOPIXEL_COUNT (1)
4949
#endif
5050

51+
static uint64_t next_start_raw_ticks;
5152
static uint8_t status_neopixel_color[3 * MICROPY_HW_NEOPIXEL_COUNT];
5253
static digitalio_digitalinout_obj_t status_neopixel;
5354

@@ -218,6 +219,10 @@ void status_led_init() {
218219

219220
void status_led_deinit() {
220221
#ifdef MICROPY_HW_NEOPIXEL
222+
// Make sure the pin stays low for the reset period. The pin reset may pull
223+
// it up and stop the reset period.
224+
while (port_get_raw_ticks(NULL) < next_start_raw_ticks) {
225+
}
221226
common_hal_reset_pin(MICROPY_HW_NEOPIXEL);
222227

223228
#elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
@@ -265,7 +270,7 @@ void new_status_color(uint32_t rgb) {
265270
status_neopixel_color[3 * i + 2] = rgb_adjusted & 0xff;
266271
}
267272
common_hal_neopixel_write(&status_neopixel, status_neopixel_color, 3 * MICROPY_HW_NEOPIXEL_COUNT);
268-
273+
next_start_raw_ticks = port_get_raw_ticks(NULL) + 2;
269274
#elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
270275
for (size_t i = 0; i < MICROPY_HW_APA102_COUNT; i++) {
271276
// Skip 4 + offset to skip the header bytes too.

0 commit comments

Comments
 (0)