adafruit / Adafruit_NeoPixel

Arduino library for controlling single-wire LED pixels (NeoPixel, WS2812, etc.)
GNU Lesser General Public License v3.0
3.1k stars 1.27k forks source link

Neopixel Library is not processing the NEO_KHZ400 option for ESP8266 (Wemos D1 mini) #186

Open mctrp opened 5 years ago

mctrp commented 5 years ago

Hi,

For the holidays I was working on a WS2811 LED strip (5 meter, 3 LEDs per WS2811 chip) together with a Wemos D1 mini (ESP8266) as a controller.

The Neopixel library offers the NEO_KHZ400 option to drive WS2811 type strips, since these don't use a 800 KHz pulse freq like the WS2812x do.

For some reason however the NEO_KHZ400 option is ignored when I use a board based on the ESP8266 mcu. I've tried to run a simple script (just turn on 1 led based on serial input) with an Arduino UNO and the LED strip performs as expected. Using the same exact code on the Wemos got no response. (I'm aware the Wemos has a 3.3v output voltage so I used a logic level converter to turn it into 5v)

Minimal test script:

// Based on NeoPixel Ring simple sketch (c) 2013 Shae Erisson
// released under the GPLv3 license to match the rest of the AdaFruit NeoPixel library

#include <Adafruit_NeoPixel.h>

#define PIN D2 //For the Wemos D1 mini
//#define PIN 5 //For the Arduino UNO

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS      50
#define brightness     100

// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
// example for more information on possible values.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ400);

void setup() {
  pinMode(PIN, OUTPUT);
  Serial.begin(115200);
  pixels.begin(); // This initializes the NeoPixel library.
  pixels.setPixelColor(0, pixels.Color(0,0,0));
  pixels.show();
}
void loop() {
    // Tigger on serial input to synchronize UNO and Wemos sending the same Neopixel command.
    if (Serial.available() > 0) {
        Serial.println(1, BIN);
        pixels.setPixelColor(0, pixels.Color(brightness,brightness,brightness));
        pixels.show();
        Serial.read(); // Read value to reset Serial.available
        delay(1000); // Debounce delay         
    }
}

I started debugging and after (quite) some time found the following difference in signal output:

normal_400khz

After zooming in a little bit you can clearly see a difference in the length of 1 bit between the UNO and the Wemos:

Arduino UNO:

normal_400khz_closeup_uno

Wemos:

normal_400khz_closeup_wemos

The UNO uses the expected frequency of 400KHz but the Wemos is using the 800KHz frequency.

After some digging around I found the following section in esp8266.c that is related to the timing of these signals. It should change the frequencies based on a boolean named "is800KHz".

#define CYCLES_800_T0H  (F_CPU / 2500000) // 0.4us
#define CYCLES_800_T1H  (F_CPU / 1250000) // 0.8us
#define CYCLES_800      (F_CPU /  800000) // 1.25us per bit
#define CYCLES_400_T0H  (F_CPU / 2000000) // 0.5uS
#define CYCLES_400_T1H  (F_CPU /  833333) // 1.2us
#define CYCLES_400      (F_CPU /  400000) // 2.5us per bit

  uint8_t *p, *end, pix, mask;
  uint32_t t, time0, time1, period, c, startTime, pinMask;

  pinMask   = _BV(pin);
  p         =  pixels;
  end       =  p + numBytes;
  pix       = *p++;
  mask      = 0x80;
  startTime = 0;

#ifdef NEO_KHZ400
  if(is800KHz) {
#endif
    time0  = CYCLES_800_T0H;
    time1  = CYCLES_800_T1H;
    period = CYCLES_800;
#ifdef NEO_KHZ400
  } else { // 400 KHz bitstream
    time0  = CYCLES_400_T0H;
    time1  = CYCLES_400_T1H;
    period = CYCLES_400;
  }
#endif

However it does not. If I force the use of the 400KHz timing scheme by changing the 800KHz to match the 400KHz ones like this:

#define CYCLES_800_T0H  (F_CPU / 2000000) 
#define CYCLES_800_T1H  (F_CPU /  833333) 
#define CYCLES_800      (F_CPU /  400000) 
#define CYCLES_400_T0H  (F_CPU / 2000000) 
#define CYCLES_400_T1H  (F_CPU /  833333) 
#define CYCLES_400      (F_CPU /  400000)

It does work and I get the following output on my logic analyser:

Overview:

forced_400khz

Arduio UNO:

forced_400khz_closeup_uno

Wemos:

forced_400khz_closeup_wemos

Now the signals seem to match in terms of frequency. Both on 400KHz.

I have the feeling it has to do with this "is800KHz" boolean that is not being set correctly somewhere. But I cannot seem to find if the problem lies in the esp8266.c itself or in Adafruit_NeoPixel.cpp that calls it.

Does anyone know where to look for the cause? Or am I missing something?

Arduino board: Wemos D1 Mini (ESP8266), Arduino UNO (genuine) Arduino IDE version: 1.8.5 Neopixel library version: 2.4.2

slootsky commented 5 years ago

Unrelated to your specific problem, but if you can separate the first LED, you don’t need a level shifter, you can fudge things as shown in this hackaday: https://hackaday.com/2017/01/20/cheating-at-5v-ws2812-control-to-use-a-3-3v-data-line/

From: Marcel notifications@github.com Sent: Monday, December 24, 2018 8:28 AM To: adafruit/Adafruit_NeoPixel Adafruit_NeoPixel@noreply.github.com Cc: Subscribed subscribed@noreply.github.com Subject: [adafruit/Adafruit_NeoPixel] Neopixel Library is not processing the NEO_KHZ400 option for ESP8266 (Wemos D1 mini) (#186)

Hi,

For the holidays I was working on a WS2811 LED strip (5 meter, 3 LEDs per WS2811 chip) together with a Wemos D1 mini (ESP8266) as a controller.

The Neopixel library offers the NEO_KHZ400 option to drive WS2811 type strips, since these don't use a 800 KHz pulse freq like the WS2812x do.

For some reason however the NEO_KHZ400 option is ignored when I use a board based on the ESP8266 mcu. I've tried to run a simple script (just turn on 1 led based on serial input) with an Arduino UNO and the LED strip performs as expected. Using the same exact code on the Wemos got no response. (I'm aware the Wemos has a 3.3v output voltage so I used a logic level converter to turn it into 5v)

Minimal test script:

// Based on NeoPixel Ring simple sketch (c) 2013 Shae Erisson

// released under the GPLv3 license to match the rest of the AdaFruit NeoPixel library

include

define PIN D2 //For the Wemos D1 mini

//#define PIN 5 //For the Arduino UNO

// How many NeoPixels are attached to the Arduino?

define NUMPIXELS 50

define brightness 100

// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.

// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest

// example for more information on possible values.

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ400);

void setup() {

pinMode(PIN, OUTPUT);

Serial.begin(115200);

pixels.begin(); // This initializes the NeoPixel library.

pixels.setPixelColor(0, pixels.Color(0,0,0));

pixels.show();

}

void loop() {

// Tigger on serial input to synchronize UNO and Wemos sending the same Neopixel command.

if (Serial.available() > 0) {

    Serial.println(1, BIN);

    pixels.setPixelColor(0, pixels.Color(brightness,brightness,brightness));

    pixels.show();

    Serial.read(); // Read value to reset Serial.available

    delay(1000); // Debounce delay

}

}

I started debugging and after (quite) some time found the following difference in signal output:

[normal_400khz]https://user-images.githubusercontent.com/19150076/50399955-68b03780-0783-11e9-99f8-75f0f6a65762.PNG

After zooming in a little bit you can clearly see a difference in the length of 1 bit between the UNO and the Wemos:

Arduino UNO: [normal_400khz_closeup_uno]https://user-images.githubusercontent.com/19150076/50399977-82ea1580-0783-11e9-898d-bb59e211fc54.PNG

Wemos: [normal_400khz_closeup_wemos]https://user-images.githubusercontent.com/19150076/50399981-8b425080-0783-11e9-8a62-bb644e4f4ca9.PNG

The UNO uses the expected frequency of 400KHz but the Wemos is using the 800KHz frequency.

After some digging around I found the following section in esp8266.c that is related to the timing of these signals. It should change the frequencies based on a boolean named "is800KHz".

define CYCLES_800_T0H (F_CPU / 2500000) // 0.4us

define CYCLES_800_T1H (F_CPU / 1250000) // 0.8us

define CYCLES_800 (F_CPU / 800000) // 1.25us per bit

define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS

define CYCLES_400_T1H (F_CPU / 833333) // 1.2us

define CYCLES_400 (F_CPU / 400000) // 2.5us per bit

uint8_t p, end, pix, mask;

uint32_t t, time0, time1, period, c, startTime, pinMask;

pinMask = _BV(pin);

p = pixels;

end = p + numBytes;

pix = *p++;

mask = 0x80;

startTime = 0;

ifdef NEO_KHZ400

if(is800KHz) {

endif

time0  = CYCLES_800_T0H;

time1  = CYCLES_800_T1H;

period = CYCLES_800;

ifdef NEO_KHZ400

} else { // 400 KHz bitstream

time0  = CYCLES_400_T0H;

time1  = CYCLES_400_T1H;

period = CYCLES_400;

}

endif

However it does not. If I force the use of the 400KHz timing scheme by changing the 800KHz to match the 400KHz ones like this:

define CYCLES_800_T0H (F_CPU / 2000000)

define CYCLES_800_T1H (F_CPU / 833333)

define CYCLES_800 (F_CPU / 400000)

define CYCLES_400_T0H (F_CPU / 2000000)

define CYCLES_400_T1H (F_CPU / 833333)

define CYCLES_400 (F_CPU / 400000)

It does work and I get the following output on my logic analyser:

Overview: [forced_400khz]https://user-images.githubusercontent.com/19150076/50399994-ab720f80-0783-11e9-8989-7217ffbd60e9.PNG

Arduio UNO: [forced_400khz_closeup_uno]https://user-images.githubusercontent.com/19150076/50399997-af059680-0783-11e9-830b-89ab9405bd2e.PNG

Wemos: [forced_400khz_closeup_wemos]https://user-images.githubusercontent.com/19150076/50399999-b167f080-0783-11e9-955e-bbe70a79d64c.PNG

Now the signals seem to match in terms of frequency. Both on 400KHz.

I have the feeling it has to do with this "is800KHz" boolean that is not being set correctly somewhere. But I cannot seem to find if the problem lies in the esp8266.c itself or in Adafruit_NeoPixel.cpp that calls it.

Does anyone know where to look for the cause? Or am I missing something?

Arduino board: Wemos D1 Mini (ESP8266), Arduino UNO (genuine) Arduino IDE version: 1.8.5 Neopixel library version: 2.4.2

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://github.com/adafruit/Adafruit_NeoPixel/issues/186, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AFI2Vf3gsBuoSuixERcHru2hytiQNra3ks5u8NZWgaJpZM4Zgcuz.

ladyada commented 5 years ago

yeah we dont really support 400khz - if you can submit a PR we'll take a look but like, we dont even know where to get 400khz neopixels anymore :D

PaintYourDragon commented 5 years ago

I don't see anything seriously out of whack. Additionally, I don't have any 400 KHz pixels handy to test with, so I'm flying on instruments here...but...

I notice the function declaration for espShow() in Adafruit_NeoPixel.cpp (line 115) doesn't exactly match what's in esp8266.c (line 20). Last argument is uint8_t in former, boolean in latter. IN PRINCIPLE they should compile to the same thing, but maybe the ESP8266 is a strange case.

Try changing the code so it's the same in both places...either both uint8_t, or both boolean...see if the behavior is any different.

mctrp commented 5 years ago

Thank you for your responses! I seem to have found the last one in existence apparently :P

With your feedback I think I have found the culprit:

If I change this part of esp8266.c

#ifdef NEO_KHZ400
  if(is800KHz) {
#endif
    time0  = CYCLES_800_T0H;
    time1  = CYCLES_800_T1H;
    period = CYCLES_800;
#ifdef NEO_KHZ400
  } else { // 400 KHz bitstream
    time0  = CYCLES_400_T0H;
    time1  = CYCLES_400_T1H;
    period = CYCLES_400;
  }
#endif

to

  if(is800KHz) {
    time0  = CYCLES_800_T0H;
    time1  = CYCLES_800_T1H;
    period = CYCLES_800;
  } else { // 400 KHz bitstream
    time0  = CYCLES_400_T0H;
    time1  = CYCLES_400_T1H;
    period = CYCLES_400;
  }

The signal is correct. So it seems that the definition of NEO_KHZ400 does not carry over to the secondary c file. Is there a more elegant way of solving this? Otherwise I will submit a PR for the code change described above.

PaintYourDragon commented 5 years ago

Oh snap! Good eye.

NEO_KHZ400 is ONLY not-defined on ATtiny devices. It's present on everything else, so I'm OK with the change as shown...feel free to PR, or I can make the change next time I'm in there.

Thanks for spotting it!

Jmarinis commented 4 years ago

I'd like to get this change implemented. Should I reference this thread in the PR?

AstroZombieSG commented 4 years ago

I just ran in to this today. I was working on an ATTiny85 and I grabbed a strip of neoPixels I had laying around. After some odd behavior of the LEDs (when plugged in to the ATTiny85 they flicker uncontrollably) I was able to identify a marking on the strip that said 2811, so I changed the setting to NEO_KHZ400 and received the compilation error. 'NEO_KHZ400' was not declared in this scope.

Seems like a trivial thing to fix, but also a very rare occurrence of anyone using these two combinations.

SputnikSeven commented 4 years ago

I also just ran into this issue today on an attiny85 and an older pololu addressable led.