About how to interface ws2812s rgb leds with nRF based board

I want to run the driver for ws2812 rgb leds with nRF52840 board. The problem is the implementation is only limited to Atmegas, ESP, etc but not for the nRF boards.

https://doc.riot-os.org/group__drivers__ws281x.html

As I am in the initial research of options to get it to work onto the nRF boards, can anyone tell me what can I do? Can PWM/Timer driver provide a solution? The timings on the ws2812 chip are somewhat strict.

image

There are two drivers that are not yet upstream that would work in your case. The older is found in my neopixel branch and needs timing parameters to be entered for working on the nRF52. @chrysn has probably a rebased branch of this with the suitable timing parameters for the nRF52 already provided.

The more recent attempt is found in my ws281x-systick branch that already works on nRF52 MCUs. I am planning to get this upstream. I have hoped that no manual tweaking of timings will be needed when the SysTick timer is used for the delay, but this didn’t turn out to be true so far. Likely the overhead is higher than the timing tolerance, which even is a bit stricter in the driver - we are trying to be compatible with clones that have slightly different but overlapping timing requirements by using the average times of original and clones. According to my experiments, we succeeded in this and users don’t need to know which flavor of NeoPixels they got.

There also is the option to abuse the MOSI pin of an SPI device to generate the correct timings, but it would require knowing the exact SPI frequency. @hugueslarrive has a PR open that would add just that. Once this is upstream, a generic driver could be written that would work with any sufficiently fast SPI peripheral.

I am sorry that the upstream situation with the backends is not particularly pleasing right now. We are working on it. In the meantime, I suggest using the ws281x-systick branch. (I hope I did indeed commit the manual tweaking of the timings… Give me a holler if it doesn’t work.)

Is there a saparate driver in the neopixel and ws281x-systick branch? I looked over but found the similar implementation to what I saw in upstream branch in /drivers/ws281x etc. There is some __asm code indeed but that would be different for nRF boards if there is an implementation.

The SPI Mosi pin abuse is also something interesting, will try it if I can do something.

This is the new backend in the ws281x-systack branch: https://github.com/maribu/RIOT/blob/ws281x-systick/drivers/ws281x/busywait.c

You can select it by using USEMODULE += ws281x_busywait, but actually, the dependency logic here should select that backend automatically when you just use USEMODULE += ws281x.

The SPI Mosi pin abuse is also something interesting, will try it if I can do something.

It should be possible to write a backend for that even now, but the user would have to specify an spi_clk_t value to use and the actual frequency in Hz this constant would select. Once the SPI API change is in, this would no longer be needed, as the driver could just use the API to obtain the actually used frequency.

Thanks, for now this should work for me, if I try SPI protocol, I will creat a merge request or whatever riot prefers.

Hi, Maybe this answer is not that relavant to RIOT but I tried the PWM with esp32s2 using esp-idf and ledc(PWM library) and it works as expected. I will try RIOT PWM soon and let you know.

Currently, I have only tested the 0 and 1 bit, when I flush 1 bit I can see the white illuminated light. for 0 there is no light.

Just to make sure we’re on the same page: You are using PWM to control a dump RGB LED (no controller such as the ws281x in between), right? In other words, you are not using the PWM to generate the ws281x serial protocol, right?

I am using ws2812s based rgb leds… I did the math for the waveforms I have posted above with the duty cycle, frequence and PWM resolutions. beow is some math for the 0 and 1 code respectively and a code for esp with some parameters.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "driver/ledc.h"

#include "esp_log.h"

#define DUTY (5.6)

static const char TAG[] = "main";

void app_main(void){

  esp_err_t ret;
  ESP_LOGI(TAG, "Initializing the example");

  ledc_timer_config_t config = {
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .timer_num = LEDC_TIMER_0,
    .duty_resolution = LEDC_TIMER_3_BIT,
    .freq_hz = 800000,
    .clk_cfg = LEDC_AUTO_CLK,
  };
  ESP_ERROR_CHECK(ledc_timer_config(&config));

  ledc_channel_config_t cfconfig = {
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .channel = LEDC_CHANNEL_0,
    .timer_sel = LEDC_TIMER_0,
    .intr_type = LEDC_INTR_DISABLE,
    .gpio_num = 5,
    .duty = 0,
    .hpoint = 0
  };

    ESP_ERROR_CHECK(ledc_channel_config(&cfconfig));
    ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, DUTY));
    ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
  //spi_device_release_bus(spi);
}

Note that while it is easy with PWM to create the timing of a 1 or a 0 bit in a PWM cycle, the difficult part is to set the duty cycle in time after every period. With a DMA that can be configured to stream a new duty cycle into the PWM for every period, that would be totally feasible and very elegant. But RIOT’s PWM API doesn’t expose “DMA feeding”, so extension of that API would be needed first.

1 Like

Thanks, I will see and look for better aproch to do that.

Just to make sure you didn’t get me wrong: The use of DMA and PWM for encoding is very elegant and a quick search in the internet shows that this approach has been used successfully. I was merely pointing out that a bit of effort would be involved.

Note that adding a DMA functionality to the PWM API is likely easier than the change of the SPI API, as the DMA functionality has to be an optional feature anyway (since not all MCUs can feed the duty cycle settings via DMA or even have a DMA controller in the first place). And if it is optional, it is easy to add one implementation at a time.