Ws2812 DMA library [RESOLVED]

in example size of led string set to 10. If i’ll set this to 20, than colors with 6 appears in 12 or 14 number…

Is there a global for PWM_Buffer somewhere? I don’t see it in the code you presented.

1 Like

I’ve just maked this part for example. In real code there is ws2811.h file with difinitions of class:

	typedef struct led {
		unsigned int red : 8;
		unsigned int green : 8;
		unsigned int blue : 8;
	} led;



class ws2812
{
	public:
	int framebuffer_size;
	int pwm_buffer_size;
	led * framebuffer;
    void init(int size);
	void show(void);
	uint16_t * PWM_Buffer;
	void Update_Buffer();
	//uint32_t pwm_pos = 0;
	int incomplete_return = 0; 
	void start_dma(void);
	void init_buffers(int size);
	//inline void color2pwm(uint16_t ** const dest, const uint8_t color);
	void color2pwm(int pos, const uint8_t color1,  const uint8_t color2,  const uint8_t color3);
};

Hooray! I’ve found a problem, with help of my friend!
It was in wrong malloc usage.
correct is

PWM_Buffer = (uint16_t*) calloc (pwm_buffer_size,sizeof(uint16_t));
framebuffer = (led*) calloc (framebuffer_size,sizeof(led));

and after this “calloc” i can remove cycles of filling zeros. It’s working now!

1 Like

Congrats!, I hope you’re able to share the app :wink:

So, i’m still working on this code to make it beauty and fast. But it working for now( i have a little flicks,i think it’s because i’m driving ws2812 strip from 5v and data has no level shifter)

https://github.com/ekbduffy/Spark-Neopixel-DMA - here is a link for library on GitHub. But i’m not using webide, so i’ve write this only basing on BDub’s neopixel library on github.

So everyone welcome to test:)

1 Like

already tried, but got 500 error on posting :smile:

Can anybody try to use it thru WebIDE?:slight_smile: I’ve didn’t tried to do this:)

Did you publish it as a library or should I just paste it from github?

Anyways, looking forward to try it shortly as I need to drive 10m of neopixels soon.

It’s not pretty for publishing now… I’ll publish, when it will work fine:)
Now i’m in debug :slight_smile:

I’m trying to use this library on the Photon. However I get a bunch of errors.

I’ve replaced

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_dma.h"

with

#include "stm32f2xx.h"
#include "stm32f2xx_rcc.h"
#include "stm32f2xx_gpio.h"
#include "stm32f2xx_tim.h"
#include "stm32f2xx_dma.h"

However I get errors like:

error: 'RCC_APB2Periph_GPIOB' was not declared in this scope
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
error: 'GPIO_Mode_AF_PP' was not declared in this scope
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
error: 'RCC_AHBPeriph_DMA1' was not declared in this scope
error: 'RCC_AHBPeriphClockCmd' was not declared in this scope

Maybe someone already ported this?

I’m studying this library to make a DMX library that uses DMA.

In that library I also have some issues to get the GPIO right (next to the fact that it compiles, but it doesn’t seem to do the right thing).

Hi @kasper

The WS2812 library has an DMA implementation that only works on the earlier Core.
You could change the code to use the f2xx (as you have tried by changing the include files, however there were a lot more changes in the low level implementation of the DMA drivers). That's why you are getting all of the other errors.

The Photon already has DMA supported for SPI transfers. It may be better to start with a neopixel library that uses SPI writes.

If you wanted to see the implementation for the (f2x) series (aka Photon) you can look at /hal/src/core/spi_hal.c in the particle repo as a starting point.

3 Likes

Hi @mtnscott thanks for your reply. Indeed my next bet is to use the implemented SPI with DMA. I have that working successfully for the APA102 strips. However I thought it would have been nice to be able to select more pins for DMX. SPI directly blocks a few other pins as well.

But I now learned that working low level is a completely other way of programming. It’s difficult to find good resources on how to read microprocessor datasheets and implement it in the right way (if there are people with good tips/books/sites, please let me know).

Beginning C for Arduino - 2nd edition” helped me a lot to learn understand and deal with things like pointers and .c and .h files etc.

Sorry, to kick this up. But I had some findings.

@mtnscott SPI won’t work, because I can’t set the timing precise (see: Set spi speed to 2Mhz precise)

I found an WS2812 DMA implementation for the Photon from @jvanier named autopixel. It’s a work in progress but it’s actually the first working code I’ve found that seems to output something with DMA. It works on pin A7 (there is also an earlier commit that I’ve successfully tested on pin D3). D3 works perfectly on the internet button. Of course this library needs some work for more pixels, but it’s a starter that shows at least working activity on a pin that turns my neopixels on.

I also tried the Speaker library that uses the DAC (https://github.com/monkbroc/particle-speaker), which outputs also in a good way. I can flash a LED with it in a pattern (changed the frequence to 1 Hz) although the speed of flashing seems still to be dictated by the buffer length (while I want each byte/halfword to have the same time span).

The combination of Timers, DMA channels, Streams, peripherals is still black magic to me. I’ve check the manual, but I think without a proper foundation in embedded programming I don’t come far.

For DMX I just need to set a pin high or low. So they have both the same length (4us). In all this WS2811 examples the pulse width seems to be modified by a byte array with “compare” values. They probably influence the HIGH and LOW time. But what do the values mean, how do I get to the values I need. Or is there a way to completely bypass the PWM (how) and just send a bytestream directly (just like on SPI, but then with more control on the timing)?

Congrats on finding that code! I haven’t pushed it over the finish line because like you say it’s a bit of a black magic to make this stuff work.

Have you read the Paul Stoffregen’s page on WS28xx LEDs, section OctoWS2811 Technical Details? It describes the algorithm of using several timers in PWM mode and several DMA channels to do DMA control of the WS28xx LEDs. It’s not possible to do something like SPI but with more control on the timing. I’m afraid the method that Paul describe is the way to go for a non-blocking hardware peripheral driver for Neopixel.

@jvanier: Yes, I came across the code because I’m looking for DMA implementation examples. There are a lot for the STM Discovery board (STM32 F4xx), but not for the STM32 F2xx used by the Photon.

I’m not really looking to drive WS28xx leds, but I’d like to output DMX in the background. I’ve found a nice implementation that stores the whole “pattern” into a byte array. The only thing I need is to transfer that byte array with a solid clockspeed on a pin. Actually just like SPI, but then with a clockrate that I configure precisely.

I thought I had found to solution in your Speaker implementation, which is really clean and clear. However does the DAC do other things as well? Or could I make a fixed frequency square wave pattern in the buffer?

You should be able to change the target register of the speaker library to write to the port set register instead of writing to the DAC value port. Hints on how to do this would be to look at which registers are used in the GPIO implementation for the STM32F2.

Depending on what the waveform you’re trying to generate looks like you may need 2 DMA channels: one copying to the port set register (BSRRL) an array where the 1’s mean turn on the pin and one copying to the port reset register (BSRRH) an array where the 1’s mean turn off the pin. Does that make sense?

I start understanding things more and more gradually.

I noticed that in the application I try to implement that the whole port is written (GPIOE) by modifying the ODR register. So 8 DMX channels are written at once. Just with a bytearray (so each bit refers to one pin I suppose). On the Photon that won’t be possible, because I don’t have a complete port available. However by settings the bytes in the array in a smart way I might be able to use this method.

Your suggestion makes also sense. I tried it without DMA and it works (this article explains GPIO registers really well).

//A3        PA5
//A4        PA6
//A5        PA7
//A6 (DAC)  PA4

  GPIOA->BSRRL = 0b0000000011110000; // HIGH
  digitalWrite(D7, HIGH);
  delay(500);
  GPIOA->BSRRH = 0b0000000011110000; // LOW
  digitalWrite(D7, LOW);

Would doing it with DMA be something like this?
I suppose I have to invert the array then.

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &GPIOA->BSRRL;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &GPIOA->BSRRH;

I’m constantly struggling to find the right DMA/Timer/Stream match.
I found this table in the RM0033 manual, is this the right one to use?

Based on this scheme I come to the conclusion I also need two timers. I select timer6 en 7 because they aren’t used by the Particle firmware.
Channel 7 Stream1 connects to TIM6_UP.
Channel 1 Stream2 connect to TIM7_UP.

Is that right? Are the timers always in sync when I configure them with the same settings?

This stuff is confusing for sure. You have the right table. You can look at the speaker and autopixel libraries to see how they use the various registers and what’s the relationship between timer channels and DMA streams. Good luck!

1 Like

I could get it to work. Thanks for the hints. I wrote a small proof of concept (just some blinking led) as a starting point for others that like to work with DMA to control pins on the Photon.