LuckfoxTECH / luckfox-pico

luckfox-pico sdk
317 stars 134 forks source link

control WS2812B LEDs #135

Open KaliAssistant opened 4 months ago

KaliAssistant commented 4 months ago

is possible use rpi_ws281x and adafruit-circuitpython-neopixel to control ws2812b rgb led just like raspberry pi?

plan44 commented 4 months ago

I hope this is possible, maybe by leveraging pne of the PWMs of which 2 units apparently can send IR data? Is there any detail docs about the RV1106 PWM? I have written a PWM based WS28xx kernel driver for the Mediatek MT7688, and would very much like to port it to RV1106, once I can obtain docs.

KaliAssistant commented 4 months ago

I will try it. I don’t have hardware now

nopnop2002 commented 4 months ago

adafruit-circuitpython-neopixel library is based on Adafruit Blinka.

https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel/blob/main/requirements.txt

Adafruit Blinka supports pico board.

adafruit-circuitpython-neopixel library may work with pico board.

KaliAssistant commented 4 months ago

@nopnop2002 I installed rpi_ws281x and adafruit-circuitpython-neopixel , i run the code ,it says gpio not support this board

KaliAssistant commented 4 months ago

it still using RPI.gpio

KaliAssistant commented 4 months ago

I use 0.5 days to install pip pkg

nopnop2002 commented 4 months ago

i run the code ,it says gpio not support this board

https://github.com/adafruit/Adafruit_Blinka/blob/main/src/adafruit_blinka/board/luckfox/luckfoxpico_mini.py

I use 0.5 days to install pip pkg

python3 -m pip install Adafruit-Blinka==8.43.0

Installation took about 10 minutes on the pico mini board using RNDIS.

from 8.44.0, installation will take longer.

SSD1306 also works well.

Pico-Min-SSD1306-1

KaliAssistant commented 4 months ago

@nopnop2002 thanks i will try it.

nopnop2002 commented 4 months ago

Performance will be slightly faster if you use a 16GB or larger SD card and expand SWAP.

$ sudo mkswap /dev/mmcblk1p8
Setting up swapspace version 1, size = 54726455296 bytes

$ sudo swapon /dev/mmcblk1p8
[  105.830584] Adding 53443804k swap on /dev/mmcblk1p8.  Priority:-2 extents:1 across:53443804k SS

$ free -h
               total        used        free      shared  buff/cache   available
Mem:            33Mi        13Mi       2.0Mi       0.0Ki        17Mi        16Mi
Swap:          8.7Gi        15Mi       8.7Gi
KaliAssistant commented 4 months ago

This is very harmful to the sd card...

nopnop2002 commented 4 months ago

This is very harmful to the sd card...

Why?

KaliAssistant commented 4 months ago

This is very harmful to the sd card...

Why?

Just like you make a very slow ram on sd card...

KaliAssistant commented 4 months ago

@nopnop2002 same...

pico@foxjack:~/python3$ sudo ./.venv/bin/python3
[sudo] password for pico:
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import gpiod
>>> import board
>>> import neopixel
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pico/python3/.venv/lib/python3.10/site-packages/neopixel.py", line 18, in <module>
    from neopixel_write import neopixel_write
  File "/home/pico/python3/.venv/lib/python3.10/site-packages/neopixel_write.py", line 41, in <module>
    raise NotImplementedError("Board not supported")
NotImplementedError: Board not supported
>>>
luckfox-eng33 commented 4 months ago

If you don't use SPI, you can theoretically use SPI's MOSI pins to control WS2812B

rpi_ws281x has two options

  1. Based on the PWM register and DMA of Raspberry Pi, it is difficult to implement on RV1103/6
  2. Using SPI for simulation is not difficult to implement.
KaliAssistant commented 4 months ago

I run my code

pico@foxjack:~/python3$ sudo ./.venv/bin/python3 ./neotest 
[sudo] password for pico: 
Traceback (most recent call last):
  File "/home/pico/python3/./neotest", line 97, in <module>
    strip.begin()
  File "/home/pico/python3/.venv/lib/python3.10/site-packages/rpi_ws281x/rpi_ws281x.py", line 143, in begin
    raise RuntimeError('ws2811_init failed with code {0} ({1})'.format(resp, str_resp))
RuntimeError: ws2811_init failed with code -3 (Hardware revision is not supported)
Segmentation fault

code:

#!/usr/bin/env python3
# NeoPixel library strandtest example
# Author: Tony DiCola (tony@tonydicola.com)
#
# Direct port of the Arduino NeoPixel library strandtest example.  Showcases
# various animations on a strip of NeoPixels.

import time
from rpi_ws281x import PixelStrip, Color
import argparse

# LED strip configuration:
LED_COUNT = 1        # Number of LED pixels.
LED_PIN = 50          # SPI ? idk 
LED_FREQ_HZ = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10          # ?
LED_BRIGHTNESS = 255  # Set to 0 for darkest and 255 for brightest
LED_INVERT = False    # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0       # set to '1' for GPIOs 13, 19, 41, 45 or 53

# Define functions which animate LEDs in various ways.
def colorWipe(strip, color, wait_ms=50):
    """Wipe color across display a pixel at a time."""
    for i in range(strip.numPixels()):
        strip.setPixelColor(i, color)
        strip.show()
        time.sleep(wait_ms / 1000.0)

def theaterChase(strip, color, wait_ms=50, iterations=10):
    """Movie theater light style chaser animation."""
    for j in range(iterations):
        for q in range(3):
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i + q, color)
            strip.show()
            time.sleep(wait_ms / 1000.0)
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i + q, 0)

def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return Color(pos * 3, 255 - pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return Color(255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return Color(0, pos * 3, 255 - pos * 3)

def rainbow(strip, wait_ms=20, iterations=1):
    """Draw rainbow that fades across all pixels at once."""
    for j in range(256 * iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel((i + j) & 255))
        strip.show()
        time.sleep(wait_ms / 1000.0)

def rainbowCycle(strip, wait_ms=20, iterations=5):
    """Draw rainbow that uniformly distributes itself across all pixels."""
    for j in range(256 * iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel(
                (int(i * 256 / strip.numPixels()) + j) & 255))
        strip.show()
        time.sleep(wait_ms / 1000.0)

def theaterChaseRainbow(strip, wait_ms=50):
    """Rainbow movie theater light style chaser animation."""
    for j in range(256):
        for q in range(3):
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i + q, wheel((i + j) % 255))
            strip.show()
            time.sleep(wait_ms / 1000.0)
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i + q, 0)

# Main program logic follows:
if __name__ == '__main__':
    # Process arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit')
    args = parser.parse_args()

    # Create NeoPixel object with appropriate configuration.
    strip = PixelStrip(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
    # Intialize the library (must be called once before other functions).
    strip.begin()

    print('Press Ctrl-C to quit.')
    if not args.clear:
        print('Use "-c" argument to clear LEDs on exit')

    try:

        while True:
            print('Color wipe animations.')
            colorWipe(strip, Color(255, 0, 0))  # Red wipe
            colorWipe(strip, Color(0, 255, 0))  # Green wipe
            colorWipe(strip, Color(0, 0, 255))  # Blue wipe
            print('Theater chase animations.')
            theaterChase(strip, Color(127, 127, 127))  # White theater chase
            theaterChase(strip, Color(127, 0, 0))  # Red theater chase
            theaterChase(strip, Color(0, 0, 127))  # Blue theater chase
            print('Rainbow animations.')
            rainbow(strip)
            rainbowCycle(strip)
            theaterChaseRainbow(strip)

    except KeyboardInterrupt:
        if args.clear:
            colorWipe(strip, Color(0, 0, 0), 10)

idk what gpio num should i use in my code 2024-07-05_12-57-46

nopnop2002 commented 4 months ago

Adafruit CircuitPython NeoPixel only supports these boards.

if detector.board.any_raspberry_pi:
    from adafruit_blinka.microcontroller.bcm283x import neopixel as _neopixel
elif detector.board.pico_u2if:
    from adafruit_blinka.microcontroller.rp2040_u2if import neopixel as _neopixel
elif (
    detector.board.feather_u2if
    or detector.board.feather_can_u2if
    or detector.board.feather_epd_u2if
    or detector.board.feather_rfm_u2if
    or detector.board.qtpy_u2if
    or detector.board.itsybitsy_u2if
    or detector.board.macropad_u2if
    or detector.board.qt2040_trinkey_u2if
    or detector.board.kb2040_u2if
):
    from adafruit_blinka.microcontroller.rp2040_u2if import neopixel as _neopixel
elif "sphinx" in sys.modules:
    pass
else:
    raise NotImplementedError("Board not supported")
plan44 commented 4 months ago

If you don't use SPI, you can theoretically use SPI's MOSI pins to control WS2812B The RV1106 has two SPI units - only SPI0 is exposed on the LuckFox pico's pins. I guess the internal NAND-Flash is also connected via SPI, is that on SPI1? If so, SPI0 would be free 😄

  1. Using SPI for simulation is not difficult to implement.

Yes, if you can set the SPI to ~2.8Mhz bit clock, then sending 0xC and 0x8 nibbles for H and L bits, respectively, works fine (implemented this on another platforms some years ago). The wiki has good information about SPI programming: https://wiki.luckfox.com/Luckfox-Pico/Luckfox-Pico-SPI#4-spi-communication-c-program

I will try this as soon as I get my OpenWrt flashed onto the pico max, but I am stuck with that right now...

luckfox-eng33 commented 4 months ago

I will try this as soon as I get my OpenWrt flashed onto the pico max, but I am stuck with that right now...

It seems that someone has already ported openwrt in #115. You can refer to it.

plan44 commented 4 months ago

@luckfox-eng33 It seems that someone has already ported openwrt in #115. You can refer to it.

Yes, thanks - I'm aware of that and am trying to work with and maybe help top improve it, because at this time it is SD-card only and uses the SDK kernel, not the one from openwrt. I'm currently trying to get the openwrt built kernel find a openwrt-style squashfs/jffs2 rootfs on the pico max, but with little success so far (see my comments in #115).

DhSufi commented 1 month ago

Just in case someone finds it useful. As luckfox-eng33 said, we should be able to control them with SPI.

  1. You should play with spi.max_speed_hz (between 2500000 and 3500000 depending on your LEDs).

  2. Then connect the LEDs data pin to the MOSI pin of the Luckfox Pico Pro.

  3. Finally you have to translate each bit of information to a 3 bits message in the SPI bus: (This is because of WS2812 bit timings VS SPI bit timings, and is related to the frequency we set above in point 1)

    • if data bit is 1 you have to send 3 bits: 110 on the MOSI pin
    • if data bit is 0 you have to send 3 bits: 100 on the MOSI pin

A simple python script to set 8 leds strip to color red will be like this:

import spidev

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 3300000  # Adjust between 2500000 and 3500000

# Create a list with the GRB color for each led
num_leds = 8
colors_list = []
for _ in range(num_leds):
    colors_list.extend([0, 255, 0])  # set to color RED -> format GRB

mosi_data = []
for color_byte in colors_list:

    # convert each color byte to list of bits
    byte_str = format(color_byte, '08b')
    bit_list = [int(bit) for bit in byte_str]

    # Translate each bit into the equivalent 3 bits of SPI MOSI
    for original_bit in bit_list :
        if original_bit == 1:
            mosi_data.extend([1, 1, 0])
        elif original_bit == 0:
            mosi_data.extend([1, 0, 0])

# spidev needs bytes in order to send data
byte_array = bytearray()
for i in range(0, len(mosi_data), 8):
    byte = mosi_data[i:i + 8]
    byte_value = sum(bit << (7 - j) for j, bit in enumerate(byte))
    byte_array.append(byte_value)

spi.writebytes(byte_array)

Hope it helps