We have upgraded the community system as part of the upgrade a password reset is required for all users before login in.

WS281x, SK6812 driver for the Omega2? [Solved]

  • In this thread the same question was asked for the Omega1, and also answered: There's a kernel module called ws2812-draiveris.

    Only, ws2812-draiveris does not work on the Omega2 because it is specific to the Omega1's AR9331 chip.

    So the question arises again for the Omega2: how to drive WS281x and SK6812 LEDs?

    Maybe somebody is already working to make a driver?

    If not - one could try to adapt ws2812-draiveris for the Omega2's MT7688 chip. But it is a bit-banging driver which blocks IRQs for serveral milliseconds to keep the timing. Blocking IRQs that long is bad, and should be avoided whenever possible. It's not avoidable on the AR9331 which has no PWM hardware, but there's probably a better way on the Omega2's MT7688.

    The MT7688 does have PWM hardware which is probably able to generate the WS281x timing when feeding it via DMA.
    The problem is that the documentation for the PWM and DMA units in the MT7688 datasheet is very thin. It's a mere list of registers, without any conceptual overview or block diagrams. Too little information (at least for me at this point) to really get an idea if, and if yes how complicated making a PWM/DMA driver would be. I'd by willing to try making such a driver, but I don't want to reverse engineer the PWM unit first.

    So - anybody out there who has experience with the MT7688 PWM and DMA units? Any sample code that could help? Maybe there's datasheet of a similar chip that uses the same PWM and DMA units?

  • @luz Why not use SPI? A lot of people drive their ws2812's over SPI-bus.

  • @WereCatf said in WS281x, SK6812 driver for the Omega2?:

    @luz Why not use SPI? A lot of people drive their ws2812's over SPI-bus.

    Unless you are running from a ramdisk or sdcard and only reading from the on-board flash on boot, that SPI is providing the filesystems - sharing it isn't impossible, but could require some coordination with the MTD driver and if not exactly right could introduce filesystem corruption. Could be interesting to look into, just might not be trivial.

    Perhaps the I2S peripheral engine could do it. Unclear if the SD channel can be made to fallback to a simple enough mode to; perhaps with registers poked by the people who designed them, but unclear if that would be possible with limited public information.

  • @Chris-Stratton I know the flash is on that same SPI-bus, but that's not a showstopper in and of itself.

  • I was thinking about PWM foremost just because that's the most generic mechanism to potentially generate any form of output signal.

    Yes, SPI, i2s, PCM, PWM, or maybe even an UART - are all candidates for being (mis)used to produce the WS281x timing.

    However, to find out, better docs for these functional blocks than what the MT7688 datasheet delivers would be needed. And for all of these, DMA would be needed to feed them, so knowing how that block works in detail is also required.

    For starters: looking at pages 180/181 of the MT7688 datasheet, there is a table of peripheral channel connections, which does list i2s, PCM and SPI, but not PWM and SPI slave, and not even the UARTs. Does that mean DMA cannot be used to feed data into these units? Still, from the description of the DMA unit's registers it looks generic enough…
    It's really hard to get anywhere with such a meagre datasheet 😞

  • This weekend, I played a bit with the PWM unit in the MT7688 (using devmem from the command line), trying to make sense of the datasheet, which documents the registers and bits, but not the functionality behind.

    The good news is: the PWM as such is very much suited to generate WS281x timing accurately! It's not just a PWM where you can set frequencey/duty cycle, but it can also serialize 64bits into a succession of adjacent "high" and "low" times. That's exactly what is needed for non-standard serial data protocols like the WS281x!

    What I haven't found out yet is how to automatically feed more than 64bits of data into the PWM unit.

    Hours of googling revealed interesting bits: In Android, there is a "mediatek" kernel branch, which has full Linux PWM support for a "MediaTek PWM" with identical register layout as what we have in the MT7688. However, it seems the MT7688 PWM is a stripped down version of that. There's a gap of 4 longwords in the register block in the MT7688 (between PWMx_GDUR and PWMx_SEND_DATA0), where the full "MediatTek PWM" has registers for a so called memory mode. In memory mode, the PWM unit fetches data to be sent from memory. There are also bits 4-6 in PWMx_CON to select modes in the full PWM, which are read-only in the MT7688 version. So the memory mode that looked promising for generating a long stream of bits for WS281x is not available in the MT7688.

    However I find it very unlikely that the MT7688 cannot feed the PWM from memory at all. My current guess is that the memory mode is missing only because there's another mechanism in the MT7688 to feed data into the PWM, probably the generic DMA engine. The info I'm missing there is how the DMA engine would synchronize with the PWMs data demand. The MT7688 DMA has explicit channels for I2S, PCM and SPI, but not for PWM. I haven't actually tried connecting the DMA to the PWM to just look at what would happen, because that's beyond devmem from command line. But it's my plan to code something to test this soon.

    For those who want to play with the PWM in general:

    # make GPIO19 be PWM1 output
    omega2-ctrl gpiomux set pwm1 pwm
    # set PWM1 to output 64bit pattern (STOP_BITPOS=63), 100kHz base clock
    devmem 0x10005000 32 0x7E00
    # set duration for the 1-bits in the pattern (100 base clock cycles = 1mS)
    devmem 0x10005054 32 100
    # set duration for the 0-bits in the pattern (100 base clock cycles = 1mS)
    devmem 0x10005058 32 300
    # set duration for the pause (guard time) after sending all bits before starting again (10mS)
    devmem 0x1000505C 32 1000
    # now set the actual pattern
    devmem 0x10005070 32 0xFFFF00FF
    devmem 0x10005074 32 0x0F0F0000
    # set to repeat pattern forever (PWM1_WAVE_NUM=0)
    devmem 0x10005078 32 0
    # enable PWM 1
    devmem 0x10005000 32 0x2

    If you have a scope connected to GPIO19, you'll see a waveform like:
    (without a scope, connecting a speaker will at least allow making funny noise ;-))

    Note: for experimenting I used the slow 100kHz clock option. But the PWM can be run at 40Mhz, giving a resolution of 25nS, which is well precise enough to generate the 350nS and 700nS pulses needed for WS281x.

  • I too would be interested in a native Omega2 LED strip driver and tools. It might be possible to port the FastLED library and write some userland tools that link against it, but for my purposes I think I'm going to stick with using a Particle Photon to drive my LED strips, or get a FadeCandy board to handle the low level LED strip protocols and just control them by speaking Open Pixel Control protocol over USB.

    I have both APA102 and WS2812 lights, so I'm keen on solutions that have implementations for both.

  • @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 https://github.com/plan44/plan44-feed.git;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
    wget http://plan44.ch/downloads/experimental/kmod-p44-ledchain_4.4.61%2B0.9-2_mipsel_24kc.ipk
    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)

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