tinygo-org / tinygo

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
https://tinygo.org
Other
14.73k stars 859 forks source link

First write to WS2812 Led 0 sets green to 0xff (seen on RP2040 boards) #4251

Open andrewfstratton opened 2 months ago

andrewfstratton commented 2 months ago

I cannot get the WS2812 leds to be set correctly immediately following a reset - e.g. the code below should switch all the leds off.

The code is a cut down version of the problem, which can be run on Waveshare RP2040 Zero and One - the Zero shows a green led and the one shows a red led (other leds do not appear to be lit).

package main

import (
    "image/color"
    "machine"

    "tinygo.org/x/drivers/ws2812"
)

const GPIO_PIN = machine.GPIO16 // GPIO18 for RP2040 3 Key Keyboard

func main() {
    var led ws2812.Device
    led = ws2812.NewWS2812(GPIO_PIN)
    led.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    var Off = color.RGBA{R: 0x00, G: 0x00, B: 0x00}
    var colours [1]color.RGBA
    for id := range colours {
        colours[id] = Off
    }
    led.WriteColors(colours[:])
}

There is some delay needed before leds can be set. I have similar issues with the Waveshare RP2400 3 Key keyboard - and have put a delay in to fix (hack) the problem - please see https://github.com/andrewfstratton/rp3keys/tree/d834fcf139aed9b9c6121a47644f0b21a8ff2d0e With this delay, the red led does briefly come on, but is then reset - it seems that setting the leds before this delay will set them incorrectly.

andrewfstratton commented 2 months ago

Note: This isn't about resetting the leds (since they are reset on powerup) but about being able to change them immediately after power on.

aykevl commented 2 months ago

How much delay do you need here to fix the problem? I can think of two possible reasons why this happens:

  1. If the power comes up at the same time, it may be that the LEDs need a bit of time to fully start up.
  2. It may be that the pin is not read as "low" before initialization leading to the first bit to be read incorrectly.

I'm not sure what a correct fix would be: this sounds like a problem that depends on the exact system so I'm hesitant to put in a delay without understanding the problem.

Also, usually these LEDs start up black? I've seen exceptions but most of them are black when powering on.

andrewfstratton commented 2 months ago

How much delay do you need here to fix the problem?

The delay was for me to bodge it by resetting the leds after they have been switched on in error

I have a version of the code below that may help - it switches the leds on and off. I then recorded the result on resetting it several times using the Waveshare RP2040 Zero (not the keyboard):

import ( "image/color" "machine" "time"

"tinygo.org/x/drivers/ws2812"

)

const GPIO_PIN = machine.GPIO16

func main() { var led ws2812.Device led = ws2812.NewWS2812(GPIO_PIN) led.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) var Off = color.RGBA{R: 0x00, G: 0x00, B: 0x00} var On = color.RGBA{R: 0x1f, G: 0x1f, B: 0x1f} var colours [1]color.RGBA time.Sleep(time.Millisecond 1000) for { for id := range colours { colours[id] = Off } led.WriteColors(colours[:]) time.Sleep(time.Millisecond 500) for id := range colours { colours[id] = On } led.WriteColors(colours[:]) time.Sleep(time.Millisecond * 500) } }



I then swapped the On and Off round and recorded the resets (i.e. On first then Off):
- 14 Green, then 1 Yellow (?!)

I then removed the first 1 second delay and ran again:
- 4 Green, Yellow
- 4 Green, yellow
- Green, 2 Yellow
- Green yellow
- Green

And then swapped back to Off then On (with no initial delay):
- 2 Green, 1 Black
- 6 Green, Black,
- 3 Green, 2Black
- Green, Black

> 1. If the power comes up at the same time, it may be that the LEDs need a bit of time to fully start up.

I tried a plain 1 second delay at the beginning of main - still incorrect led lit after startup beginning

> 2. It may be that the pin is not read as "low" before initialization leading to the first bit to be read incorrectly.

I also tried a delay after configuring the Pin before write the leds - no joy - maybe the initialisation is incorrectly starting comms with the leds?

> Also, usually these LEDs start up black? I've seen exceptions but most of them are black when powering on.

I know - so strange they are green/yellow/red more often than black.  They are black if I don't access them?!
andrewfstratton commented 2 months ago

Another experiment:

Here's the code this time there's a half second delay before setting the leds Off. The result is that many resets show Green (then go off), less often the leds are off.

package main

import (
    "image/color"
    "machine"
    "time"

    "tinygo.org/x/drivers/ws2812"
)

const GPIO_PIN = machine.GPIO16

func main() {
    var led ws2812.Device
    led = ws2812.NewWS2812(GPIO_PIN)
    led.Pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    time.Sleep(time.Millisecond * 500)
    var Off = color.RGBA{R: 0x00, G: 0x00, B: 0x00}
    var colours [1]color.RGBA
    for {
        for id := range colours {
            colours[id] = Off
        }
        led.WriteColors(colours[:])
        time.Sleep(time.Millisecond * 500)
    }
}

However, if second delay is removed, then the behaviour changes:

led.WriteColors(colours[:])
// time.Sleep(time.Millisecond * 500)

Now, resets get Green - and STAY GREEN, most of the time, sometimes they are black.

This looks to me like the led write can't cope with the traffic...

deadprogram commented 2 months ago

At least from what I can tell, this is something related to your specific hardware @andrewfstratton not TinyGo itself. Or perhaps related to the ws2812 driver implementation in https://github.com/tinygo-org/drivers/tree/release/ws2812

andrewfstratton commented 2 months ago

So I tried this with a Cytron Maker Pi RP2040 which has 2 RGB Leds - this is my last RP2040 with WS2812 Leds - so I don't have any other boards I can try...it would be good to see if other boards get this behaviour since I suspect this is not board or manufacturer specific (though I have only tried two manufacturers) and may therefore be the (WS2812) library...

There appear to be two issues here and therefore this issue may need splitting:

Issue 1 : After a reset, the first write to leds, sets led 0 to green incorrectly Issue 2 : If the leds are set using writeColours without a delay, then requests can fail, possibly due to being continually overwritten/re-requested

Please see the code further down...

I get the following behaviour:

  1. after flash or reset : led 0 is green consistently and led 1 is pink - neither change
  2. after power off then on, both leds are off

If I uncomment the delay (b) sleep code, then:

  1. after flash or reset: at start, led 0 is green and led 1 is pink, then both toggle between off and pink. The first write to led 0 is being ignored
  2. after power on, the same behaviour is seen - i.e. led 0 is green initially
package main

import (
    "image/color"
    "machine"
    "time"

    "tinygo.org/x/drivers/ws2812"
)

const GPIO_PIN = machine.GPIO18

var Off = color.RGBA{R: 0x00, G: 0x00, B: 0x00}
var On = color.RGBA{R: 0x3f, G: 0x00, B: 0x1f}

func main() {
    led_pin := GPIO_PIN
    led_pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    // delay A
    time.Sleep(time.Millisecond * 500)
    var led ws2812.Device = ws2812.NewWS2812(led_pin)
    var colours [2]color.RGBA
    for {
        // (b) delay
        // time.Sleep(time.Millisecond * 500)
        for id := range colours {
            colours[id] = On
        }
        led.WriteColors(colours[:])
        // (b) delay
        // time.Sleep(time.Millisecond * 501)
        for id := range colours {
            colours[id] = Off
        }
        led.WriteColors(colours[:])
    }
}
andrewfstratton commented 2 months ago

I've discovered that the first WS2812 write after power off or reset writes green as 0xff (or a high value - it's hard to be sure) to led 0 instead of the green value given - red and blue values are still set.

e.g. in the code below - both led 0 and led 1 should show blue after reset, but led 0 show turquoise and led 1 shows blue.

Note: I didn't use 0xff to protect my eyes

package main

import (
    "image/color"
    "machine"
    "time"

    "tinygo.org/x/drivers/ws2812"
)

const GPIO_PIN = machine.GPIO18

var Off = color.RGBA{R: 0x00, G: 0x00, B: 0x00}
var Pink = color.RGBA{R: 0x3f, G: 0x00, B: 0x1f}
var Blue = color.RGBA{R: 0x00, G: 0x00, B: 0x3f}

func main() {
    led_pin := GPIO_PIN
    led_pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    // delay A
    time.Sleep(time.Millisecond * 500)
    var led ws2812.Device = ws2812.NewWS2812(led_pin)
    var colours [2]color.RGBA
    colours[0] = Blue
    colours[1] = Blue
    led.WriteColors(colours[:])
    for {
        // (b) delay
        time.Sleep(time.Millisecond * 500)
        for id := range colours {
            colours[id] = Pink
        }
        led.WriteColors(colours[:])
        // (b) delay
        time.Sleep(time.Millisecond * 500)
        for id := range colours {
            colours[id] = Off
        }
        led.WriteColors(colours[:])
    }
}
aykevl commented 2 months ago

This sounds like you have something wrong with your wiring. A few things to check:

See https://learn.adafruit.com/adafruit-neopixel-uberguide/basic-connections for a more extensive guide.

I'm running a bunch of WS2812 LEDs every night in a project of mine, and they're powered using a rp2040. So that combination most certainly works for me.

andrewfstratton commented 2 months ago

These are all onboard rgb leds - 4 different board types with two different manufacturers.

Power might be an issue but seems unlikely with one RGB led and the issue occurring on reset and power on - and the arduino C code works fine...

Please could you try my last listing and see what happens?

Gustavomurta commented 1 month ago

I didn't read all the topics. I'm sending this link, but I don't know if it has anything to do with the problem. https://github.com/tinygo-org/pio/issues/5

aykevl commented 1 month ago

Did a quick test and I can confirm that the first write results in some weird colors. Subsequent writes work fine however.

This will need a bit more investigation to see what's going on. But a workaround would be something like this for the first write:

        led.WriteColors(colours[:])
        time.Sleep(time.Millisecond * 20)
        led.WriteColors(colours[:])
aykevl commented 1 month ago

My best guess would be that the first time the code runs, not all the code has been loaded into the flash cache, slowing down the assembly and messing up the timing of the signal.

I'm not sure what the best way would be to fix that. One solution would be to place the code in RAM, thereby taking up precious RAM (though not so precious on the rp2040). Another solution would be some sort of prefetch, but unfortunately the rp2040 doesn't support that. The easiest solution would probably to send data of a single pixel (or even a single byte), sleep for ~10ms, and then send the real data. Like this:

        led.Write([]byte{0x00})
        time.Sleep(time.Millisecond * 10)
        led.WriteColors(colours[:])

(This might still result in a flash sometimes and I'm not sure we should add this to the ws2812 driver because it's chip-specific).

mishamyrt commented 3 weeks ago

I'm facing the same problem. I use Pico W and WS2812. When trying to record colors I get the first green pixel. And the value itself is also written (if I write red, the first pixel will be yellow).

The hack with a delay after the first writing of the zero bit works, but it has to be inserted before each update. During this pause, the pixel shines green, which turns any animation into a green strobe. Experimentally I found out that a 3ms delay is enough, but it's still not what I want to get.

sago35 commented 1 week ago

I have experienced the same phenomenon. I am using the xiao-rp2040 and an external WS2812 compatible SK6812MINI-E. I captured the waveform of the DIN. The first image shows the result of sending a single black command. The second image shows the result of sending two black commands.

From this, it seems that the timing is not well controlled at the beginning of the first asm() instruction in writeColorsRGB(). As a result, it is likely being recognized as G = 0x80, causing it to emit green light.

{black}

image

{black, black}

image

soypat commented 1 week ago

Heads up, latest commit in PIO repository has a working WS2812B implementation. I've tested it on a strip and it works pretty well, with or without DMA.

https://github.com/tinygo-org/pio/blob/main/rp2-pio/examples/ws2812b/main.go