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
This commit is contained in:
Scott Shawcroft 2022-01-19 16:19:13 -08:00
parent 8bae6af12a
commit 13db65566d
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
5 changed files with 32 additions and 30 deletions

View File

@ -100,7 +100,7 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa
""); "");
} }
uint64_t next_start_raw_ticks = 0; STATIC uint64_t next_start_raw_ticks = 0;
void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t numBytes) { void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t numBytes) {
// This is adapted directly from the Adafruit NeoPixel library SAMD21G18A code: // This is adapted directly from the Adafruit NeoPixel library SAMD21G18A code:

View File

@ -36,20 +36,6 @@
STATIC uint32_t never_reset_pins[2]; STATIC uint32_t never_reset_pins[2];
STATIC uint32_t in_use[2]; STATIC uint32_t in_use[2];
STATIC void floating_gpio_reset(gpio_num_t pin_number) {
// This is the same as gpio_reset_pin(), but without the pullup.
// Note that gpio_config resets the iomatrix to GPIO_FUNC as well.
gpio_config_t cfg = {
.pin_bit_mask = BIT64(pin_number),
.mode = GPIO_MODE_DISABLE,
.pull_up_en = false,
.pull_down_en = false,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&cfg);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pin_number], 0);
}
void never_reset_pin_number(gpio_num_t pin_number) { void never_reset_pin_number(gpio_num_t pin_number) {
if (pin_number == NO_PIN) { if (pin_number == NO_PIN) {
return; return;
@ -72,7 +58,7 @@ void reset_pin_number(gpio_num_t pin_number) {
never_reset_pins[pin_number / 32] &= ~(1 << pin_number % 32); never_reset_pins[pin_number / 32] &= ~(1 << pin_number % 32);
in_use[pin_number / 32] &= ~(1 << pin_number % 32); in_use[pin_number / 32] &= ~(1 << pin_number % 32);
floating_gpio_reset(pin_number); gpio_reset_pin(pin_number);
} }
void common_hal_mcu_pin_reset_number(uint8_t i) { void common_hal_mcu_pin_reset_number(uint8_t i) {
@ -93,7 +79,7 @@ void reset_all_pins(void) {
(never_reset_pins[i / 32] & (1 << i % 32)) != 0) { (never_reset_pins[i / 32] & (1 << i % 32)) != 0) {
continue; continue;
} }
floating_gpio_reset(i); gpio_reset_pin(i);
} }
in_use[0] = never_reset_pins[0]; in_use[0] = never_reset_pins[0];
in_use[1] = never_reset_pins[1]; in_use[1] = never_reset_pins[1];

View File

@ -43,20 +43,23 @@
#include "py/mphal.h" #include "py/mphal.h"
#include "py/runtime.h" #include "py/runtime.h"
#include "shared-bindings/neopixel_write/__init__.h" #include "shared-bindings/neopixel_write/__init__.h"
#include "supervisor/port.h"
#include "components/driver/include/driver/rmt.h" #include "components/driver/include/driver/rmt.h"
#include "peripherals/rmt.h" #include "peripherals/rmt.h"
#define WS2812_T0H_NS (350) // 416 ns is 1/3 of the 1250ns period of a 800khz signal.
#define WS2812_T0L_NS (1000) #define WS2812_T0H_NS (416)
#define WS2812_T1H_NS (1000) #define WS2812_T0L_NS (416 * 2)
#define WS2812_T1L_NS (350) #define WS2812_T1H_NS (416 * 2)
#define WS2812_RESET_US (280) #define WS2812_T1L_NS (416)
static uint32_t ws2812_t0h_ticks = 0; static uint32_t ws2812_t0h_ticks = 0;
static uint32_t ws2812_t1h_ticks = 0; static uint32_t ws2812_t1h_ticks = 0;
static uint32_t ws2812_t0l_ticks = 0; static uint32_t ws2812_t0l_ticks = 0;
static uint32_t ws2812_t1l_ticks = 0; static uint32_t ws2812_t1l_ticks = 0;
static uint64_t next_start_raw_ticks = 0;
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
size_t wanted_num, size_t *translated_size, size_t *item_num) { size_t wanted_num, size_t *translated_size, size_t *item_num) {
if (src == NULL || dest == NULL) { if (src == NULL || dest == NULL) {
@ -107,21 +110,29 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout,
if (rmt_get_counter_clock(config.channel, &counter_clk_hz) != ESP_OK) { if (rmt_get_counter_clock(config.channel, &counter_clk_hz) != ESP_OK) {
mp_raise_RuntimeError(translate("Could not retrieve clock")); mp_raise_RuntimeError(translate("Could not retrieve clock"));
} }
float ratio = (float)counter_clk_hz / 1e9; size_t ns_per_tick = 1e9 / counter_clk_hz;
ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); ws2812_t0h_ticks = WS2812_T0H_NS / ns_per_tick;
ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); ws2812_t0l_ticks = WS2812_T0L_NS / ns_per_tick;
ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); ws2812_t1h_ticks = WS2812_T1H_NS / ns_per_tick;
ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); ws2812_t1l_ticks = WS2812_T1L_NS / ns_per_tick;
// Initialize automatic timing translator // Initialize automatic timing translator
rmt_translator_init(config.channel, ws2812_rmt_adapter); rmt_translator_init(config.channel, ws2812_rmt_adapter);
// Wait to make sure we don't append onto the last transmission. This should only be a tick or
// two.
while (port_get_raw_ticks(NULL) < next_start_raw_ticks) {
}
// Write and wait to finish // Write and wait to finish
if (rmt_write_sample(config.channel, pixels, (size_t)numBytes, true) != ESP_OK) { if (rmt_write_sample(config.channel, pixels, (size_t)numBytes, true) != ESP_OK) {
mp_raise_RuntimeError(translate("Input/output error")); mp_raise_RuntimeError(translate("Input/output error"));
} }
rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100)); rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
// Update the next start to +2 ticks. It ensures that we've gone 300+ us.
next_start_raw_ticks = port_get_raw_ticks(NULL) + 2;
// Free channel again // Free channel again
peripherals_free_rmt(config.channel); peripherals_free_rmt(config.channel);
// Swap pin back to GPIO mode // Swap pin back to GPIO mode

View File

@ -98,6 +98,6 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout,
gpio_init(digitalinout->pin->number); gpio_init(digitalinout->pin->number);
common_hal_digitalio_digitalinout_switch_to_output((digitalio_digitalinout_obj_t *)digitalinout, false, DRIVE_MODE_PUSH_PULL); common_hal_digitalio_digitalinout_switch_to_output((digitalio_digitalinout_obj_t *)digitalinout, false, DRIVE_MODE_PUSH_PULL);
// Update the next start. // Update the next start to +2 ticks. This ensures we give it at least 300us.
next_start_raw_ticks = port_get_raw_ticks(NULL) + 1; next_start_raw_ticks = port_get_raw_ticks(NULL) + 2;
} }

View File

@ -48,6 +48,7 @@ uint8_t rgb_status_brightness = 63;
#define MICROPY_HW_NEOPIXEL_COUNT (1) #define MICROPY_HW_NEOPIXEL_COUNT (1)
#endif #endif
static uint64_t next_start_raw_ticks;
static uint8_t status_neopixel_color[3 * MICROPY_HW_NEOPIXEL_COUNT]; static uint8_t status_neopixel_color[3 * MICROPY_HW_NEOPIXEL_COUNT];
static digitalio_digitalinout_obj_t status_neopixel; static digitalio_digitalinout_obj_t status_neopixel;
@ -218,6 +219,10 @@ void status_led_init() {
void status_led_deinit() { void status_led_deinit() {
#ifdef MICROPY_HW_NEOPIXEL #ifdef MICROPY_HW_NEOPIXEL
// Make sure the pin stays low for the reset period. The pin reset may pull
// it up and stop the reset period.
while (port_get_raw_ticks(NULL) < next_start_raw_ticks) {
}
common_hal_reset_pin(MICROPY_HW_NEOPIXEL); common_hal_reset_pin(MICROPY_HW_NEOPIXEL);
#elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) #elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
@ -265,7 +270,7 @@ void new_status_color(uint32_t rgb) {
status_neopixel_color[3 * i + 2] = rgb_adjusted & 0xff; status_neopixel_color[3 * i + 2] = rgb_adjusted & 0xff;
} }
common_hal_neopixel_write(&status_neopixel, status_neopixel_color, 3 * MICROPY_HW_NEOPIXEL_COUNT); common_hal_neopixel_write(&status_neopixel, status_neopixel_color, 3 * MICROPY_HW_NEOPIXEL_COUNT);
next_start_raw_ticks = port_get_raw_ticks(NULL) + 2;
#elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK) #elif defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)
for (size_t i = 0; i < MICROPY_HW_APA102_COUNT; i++) { for (size_t i = 0; i < MICROPY_HW_APA102_COUNT; i++) {
// Skip 4 + offset to skip the header bytes too. // Skip 4 + offset to skip the header bytes too.