WS281x, SK6812 driver for the Omega2? [Solved]

  • @Roland-McIntosh once the DMA/PWM stuff works, I will certainly make a kernel module for WS281x and SK6812 (RGBW). APA102 on the other hand are not nearly as time critical, and should work fine using bitbanged SPI on any two GPIOs of the Omega2 (haven't tried yet though, because I have only a few APA102, but tons of WS281x I'd like to connect...)

    Eventually, my goal is also to have all types unified under a common library/toolset that abstracts the hardware details from the applications using the LEDs. But first the actual bit signal generator needs to be done ;-)

  • @luz forgive a newbie question, where did you get devmem from? Ash say it is nit recognized and opkg list does not seem to have it available. Also, where did you get the memory addresses from as the datasheet I found for the MT7688 has very cryptic information.

  • @Jo-Kritzinger As I'm using my own LEDE images most of the time now, I'm not 100% sure the Omega2 standard image is configured the same way. But I think I remember testing stuff with devmem a while ago with the standard firmware. Unfortunately I can't easily test again because I don't have a free Omega2 with standard firmware available right now.

    There are two parts for devmem - first, the kernel support for /dev/mem (CONFIG_KERNEL_DEVMEM=y in LEDE .config), and second the busybox builtin devmem utility (CONFIG_BUSYBOX_CONFIG_DEVMEM=y).

    This thread suggests that at least in current firmware, /dev/mem should be there (check with ls /dev/mem), but maybe devmem utility might be still missing.

    About the addresses: These are 1:1 the addresses as shown in the MT7688 datasheet, see PWM register block on pages 233ff.

    BTW: with ls /sys/devices/platform you can see that palmbus (apparently the peripheral bus in the MT7688) is at 0x10000000, and going deeper with ls /sys/devices/platform/*.palmbus reveals the addresses of the different peripherals - there you find again 0x10005000 as base address of the PWM.

  • @luz thank you for the great info. Unfortunatly it seems devmem is not part of the standard firmware. Once I get to understand this harware a bit better and become more comfortable with Linux I will try to build a image that includes it.

  • @luz @José-Luis-Cánovas Quick question here, if you don't mind, related to accessing the registers directly. As I do not have access to devmem currently and I've just managed to get a cross compiler working (thanks José) I thought I'd use a C pointer to do the same thing as a way of starting to understand the environment/hardware. As I've mentioned previously, my background is in 8031's where you have program memory and data memory and you could implement memory accessed IO and the pointer casts would simply include a prefix to specify the relevant memory space. Pretty straight forward kind of stuff as there was no operating system in the way.
    The little test prog (see below) is how I'd imagine it would be implemented but the result I get from the program is

    1. the printf output, i.e. "GPIO 0-31 REG" - fine
    2. Segmentation fault - ?
      Is this caused by the same issue people are taking about fast_gpio or is this a lack of understanding how the Omega2 memory works? How would one setup a pointer into memory to access/alter the registers? I would have imagine that, since this is a C pointer, the OS is pretty much left out of the loop so why am I getting a fault?

    The code:

    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>

    //Def a pointer to REGISTER with a int cast to pointer
    //from the data sheet it seems the REG's are 32bits long
    #define GPIO0_31_DATA_REG (*(uint32_t*)0x10000620)
    #define GPIO0_31_DIR_REG (*(uint32_t*)0X10000600)

    int main(int argc, char *argv[]){
    printf("GPIO 0-31 REG\r\n");
    printf("GPIO0-31 Direction %x",GPIO0_31_DIR_REG);
    return EXIT_SUCCESS;

  • @Jo-Kritzinger said in WS281x, SK6812 driver for the Omega2?:

    As I do not have access to devmem currently and I've just managed to get a cross compiler working (thanks José) I thought I'd use a C pointer to do the same thing as a way of starting to understand the environment/hardware.

    Unfortunately, that won't work. A system with a memory management unit that does virtual-to-physical mapping and enforces memory access permissions won't allow a user mode program to directly interact with hardware registers.

    Thus /dev/mem, an effectively giant hole blasted through the containment wall of operating system design, breaking all the rules. Opening and/or mapping /dev/mem is asking the kernel to set up a direct virtual-to-physical mapping to the hardware, complete with permission to actually do what otherwise there would be no way to request, nor any permission to accomplish.

    But without /dev/mem, or some other hole, or some more specifically tailored solution like a kernel driver that does particular narrow operations upon legitimate request - without that, all the usual restrictions on user mode apply, and you can't touch the hardware.

  • @Chris-Stratton Arrr, not what I was hoping to hear but sort of expected. Thank you Chris.

    (removed some text here - discovered what I did wrong)

    Where would one being to look for a kernel driver or info on how to write a kernel driver?

    Once again, thanks.

  • Short update for those waiting - my kernel driver for driving WS2812 using the Omega2 hardware PWM already works with a test LED chain of 24 WS2812 :-)
    It's not yet 100% reliable, and the code is a mess, caused by all the experiments I needed to find out how that sparsely documented PWM engine actually works. I need to clean up before publishing it, but I expect to get that done in the next few weeks.

  • Finally - the driver is ready!

    See p44-ledchain in my feed for LEDE on github.

    People testing it with various LED chains welcome, my current test object is the "pixelboard" (of which you find some parts in the same feed already) with 200 WS2813 LEDs.


  • Very cool! So I'm planning to order some SK6812 for a small project. But it would be first time for me building and installing a kernel driver on Omega2. Could you make a small howto guide?

  • @Anders-Öster
    If you want to build it yourself, you need to install a LEDE buildroot (see onion docs and maybe my post here).
    Once you have setup that, you can:

    • add the plan44 feed to feeds.conf.default by adding a line
      src-git plan44 ssh://;master
    • have LEDE fetch the latest stuff from all feeds with
      ./scripts/feeds update -a
    • "install" (meaning: make ready for being included in compilation) the p44ledchain package with
      ./scripts/feeds install kmod-p44-ledchain
    • enable the package for actually being built in the configuration
      (make menuconfig, then select it under kernel modules -> other modules -> kmod-p44-ledchain)
    • build the package with
      make package/p44-ledchain/compile

    Now you'll have the compiled package in bin/targets/ramips/mt7688/packages/kmod-p44-ledchain_....ipk

    For those that don't want to invest the time (1-2h build time for the initial LEDE make run) and disk space (~15GB), here's a built package for p44-ledchain.

    Sadly, because Onion still hasn't published their complete LEDE build environment (yes, @administrators, my ceterum censeo once again) it is not possible to build a kernel module that actually installs cleanly on the omega firmware. [Update: Luckily it is published by now]

    So, either way (self-built or downloaded from the link above), you need to override kernel version dependency checks when installing

    cd /tmp
    opkg install --force-depends kmod-p44-ledchain*

    And because of the kernel version mismatch, insmod does not find the module automatically, so you need to specify the full path:

    insmod /lib/modules/4.4.61/p44-ledchain.ko ledchain0=0,200,2

    (this is for 200 WS2813 connected to PWM0)

    good luck!

  • @luz

    Thanks for the howto!

  • Hi
    I'm wondering if (and how) it is possible to dim the LED with the p44-ledchain module? (keyword: change of duty cycle)

  • @Laurent-Nittler yes, you can dim (and color) every LED separately!

    In fact, the bytes you write to the /dev/ledchainX device correspond to the brightness (duty cycle) of the red, green and blue part of the LEDs (and a separate white LED for SK6812). With this, you can set any color and any brightness.

    Let's assume you have WS2813 connected to PWM0, then

    echo -en '\xFF\xFF\xFF' >/dev/ledchain0

    switches the first LED to full white brightness (255=0xFF = full on for all three R,G,B channels. Whereas:

    echo -en '\x7F\x7F\x7F' >/dev/ledchain0

    sets all three LEDs to half duty cycle (127=0x7F)

    A somewhat dimmed green (red=off, green=160=0xA0, blue=off) would be:

    echo -en '\x00\xA0\x00' >/dev/ledchain0

    and so forth.

    To control more LEDs, just write a longer string to /dev/ledchain0, 3 bytes for every LED in the chain (4 for RGBW LEDs like SK6812).

    Of course, you wouldn't do that with echo except for the first manual tests, but using some script/program that calculates the string to be sent to /dev/ledchain0.

  • Thanks Luz for this explanation.
    I forgot that HSL values could be transformed into the RGB values.

    I used python to control the 8 LED ring (WS2812b) using code like

    with open('/dev/ledchain0','w') as export:

    where 'col_string' is a a text string containing all the RGB values for the hole ring.
    However sometimes (especially when I have to calculate the RGB values and then compose the string), I get an error 'ValueError: invalid \x escape'

    Maybe this isn't the best way to control the LED with python and you can advise a better one.


  • @LaurentN Although I'm not fluent at all in Python, I'd say there's no reason why Python should not work fine controlling LED chains with p44-ledchain.

    @LaurentN said in WS281x, SK6812 driver for the Omega2? [Solved]:

    However sometimes (especially when I have to calculate the RGB values and then compose the string), I get an error 'ValueError: invalid \x escape'

    That sounds like you are trying to use the literal string syntax (the backslash escapes in \xhh form, with hh being a two-digit hex number) to construct your string, but maybe in a way that sometimes outputs only one digit instead of two, which could be the cause of the ValueError?

    Probably there's a more direct way to get numeric byte values into a string or byte array in Python.

  • Re: [WS281x](SK6812 driver for the Omega2? [Solved])

    In the last days I played a bit with Python to get the WS281x working wirth Luz' module.
    As I want to use HLS color schema (it allows to change the luminosity of the color by only one number), I programmed a function HLS to RGB color string:

    def hls2rgb(h,l,s):
    	r, g, b = colorsys.hls_to_rgb(h,l,s)
    	shr = str(hex(int(r * 255.0)))
    	shg = str(hex(int(g * 255.0)))
    	shb = str(hex(int(b * 255.0)))
    	# print ([int(255.0*r), int(255.0*g), int(255.0*b)])
    	if len(shr) == 3:
    		shr = shr[0:2] + str(0) + shr[2]
    	if len(shg) == 3:
    		shg = shg[0:2] + str(0) + shg[2]
    	if len(shb) == 3:
    		shb = shb[0:2] + str(0) + shb[2]
    	col_led_string = shr[2:4].decode('hex') +shg[2:4].decode('hex') + shb[2:4].decode('hex')

    By using the following additional function, it allows using a 2 dimensional color matrix which is converted in the corresponding hex color string needed for the module. (It might be useful and easier to use a matrix to handle LED chains instead of a string of hexadigitals)

    def hls2rgb_hexstring(hls_matrix):
    	dim1 = len(hls_matrix)
    		dim2t = len(hls_matrix[0])
    		dim2t = 0
    	if dim2t == 0:
    		dim2 = dim1
    		dim1 = 1
    		dim2 = dim2t
    	if dim1 == 1:
    		h = hls_matrix[0]/360.0
    		l = hls_matrix[1]/100.0
    		s = hls_matrix[2]/100.0
    		col_led_string = hls2rgb(h,l,s)
    		return col_led_string
    		for i in range(0,dim1):
    			h = hls_matrix[i][0]/360.0
    			l = hls_matrix[i][1]/100.0
    			s = hls_matrix[i][2]/100.0
    			col_led_string = col_led_string + hls2rgb(h,l,s)
    		return col_led_string

    To display a color gradient among several LED's (here 40) one has to run this code, which is using both function above:

    color_start = 0
    color_end = 359
    nr_led = 40
    lum = 2
    sat = 100
    colorMatrix = []
    for i in range(color_start,color_end, abs(color_end-color_start)/nr_led):
    	#print [i,lum, sat]
    print colorMatrix
    with open('/dev/ledchain0','w') as export:

  • @luz does it work for the omega 2+ version with kernel 4.4.6?

  • @Jakofff-Parker sorry for the 2 month delay, must have missed a notification here!

    The answer is yes.

    The p44-ledchain module does not depend on many kernel internals, the only subsystem it actually uses is hrtimer (high resolution timers), so it should run on any not really ancient kernel version.
    On the other hand, it strictly limited to the MT7688 SoC, as it directly uses the PWM hardware without any abstraction. So it's good for Omega2/2S, LinkItSmart and possibly HLK7688.

  • Hi, just a note regarding WS2811 I had a go with @luz's driver and I found out that even my led strip is branded as WS2811 (BTF-LIGHTING WS2811) it didn't work properly if I set the ledchain to type 0 (ledchain0=0,200,0), but it did work well using type 2 with both 3.3V and 5V signals.

    BTW, many thanks @luz for the driver!

Log in to reply

Looks like your connection to Community was lost, please wait while we try to reconnect.