Forum: Mikrocontroller und Digitale Elektronik WS2812 per STM32F0 Timer und DMA


von Raphael L. (rleh)


Lesenswert?

Hallo allerseits,

ich habe hier ein STM32F072 Discovery Board (STM32F072RBT6) und versuche 
damit ein paar WS2812B LEDs anzusteuern (konkret ein Ring mit 12 LEDs).

Mein Ansatz ist es einen Timer zu verwenden, disen mittels Prescaler und 
Overflow auf 800kHz [3] zu konfigurieren und dann im PWM Modus nach 
350ns oder 900ns einen Pin togglen lassen. Die passenden Werte für die 
350/900ns sollen per DMA in das entsprechende Capture-Compare Register 
geschrieben werden.
Die berechneten Werte liegen als Array von uint16_t in RAM (ein Element 
für jedes Bit am WS2812B).

Das Problem scheint zu sein, dass der DMA Transfer niemals anläuft, das 
DMA_CNDTR Register zählt niemals herunter.

Die Reihenfolge der Konfiguration von Timer und DMA habe ich aus STs 
Appnote AN4104 [1] und dem Reference Manual (RM0091) [2].

Auch die Kombination von Timer 2, Capture-Compare 2 mit DMA 1 Channel 3 
und GPIO A Pin 1 sollte laut Datenblatt möglich sein.

Ich poste hier mal Codeausschnitte zum Initialisieren der Peripherie und 
aktivieren des DMA-Transfers, den kompletten Code gibt es hier 
https://jufo.mytfg.de/rlleh/stm32f072-dma-pwm-ws2812-demo in einem Git 
Repo.
1
SystemInit();
2
3
// Set flash Latency (1 wait state for 48MHz)
4
FLASH_SetLatency(FLASH_Latency_1);
5
6
// Set up 48 MHz Core Clock using HSI (8Mhz) with PLL x 6
7
RCC_PLLConfig(RCC_PLLSource_HSI, RCC_PLLMul_6);
8
RCC_PLLCmd(ENABLE);
9
10
// Wait for PLLRDY after enabling PLL.
11
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) != SET);
12
13
// Select the PLL as clock source and update SystemCoreClock
14
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
15
SystemCoreClockUpdate();
16
17
// Config SysTickTimer
18
SysTick_Config(SystemCoreClock/100);
19
20
// Read clock frequency
21
RCC_ClocksTypeDef clocks;
22
RCC_GetClocksFreq(&clocks);
23
24
// Enable GPIOC, GPIOA, TIM2 and DMA1
25
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
26
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
27
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
28
RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM2, ENABLE);
29
30
// Enable PA1 as TIM2 PWM output (AF mode)
31
GPIO_InitTypeDef pwmOut;
32
GPIO_StructInit(&pwmOut);
33
pwmOut.GPIO_Pin = GPIO_Pin_1;
34
pwmOut.GPIO_Speed = GPIO_Speed_50MHz;
35
pwmOut.GPIO_Mode = GPIO_Mode_AF;
36
GPIO_Init(GPIOA, &pwmOut);
37
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_2);
38
39
40
uint32_t cycles = 1.25 * (clocks.SYSCLK_Frequency / 1000000UL);
41
uint16_t prescaler = (cycles + 65535) / 65536;  // always round up
42
uint16_t period = (cycles / prescaler) - 1;  // e.g. 100 cycles are from 0 to 99
43
44
// reset DMA and TIM2
45
DMA_DeInit(DMA1_Channel3);
46
TIM_DeInit(TIM2);
47
TIM2->CCR2 = 0;
48
49
// init DMA
50
DMA_InitTypeDef dma;
51
DMA_StructInit(&dma);
52
dma.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR2;
53
dma.DMA_MemoryBaseAddr = (uint32_t)(&timerValues[0]);
54
dma.DMA_DIR = DMA_DIR_PeripheralDST;
55
dma.DMA_BufferSize = numDmaTransactions;
56
dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
57
dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
58
dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
59
dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
60
dma.DMA_Mode = DMA_Mode_Circular;
61
dma.DMA_Priority = DMA_Priority_High;
62
dma.DMA_M2M = DMA_M2M_Disable;
63
DMA_Init(DMA1_Channel3, &dma);
64
65
DMA_Cmd(DMA1_Channel3, ENABLE);
66
67
// Init timer and timer output: pwm
68
TIM_TimeBaseInitTypeDef tim2;
69
TIM_TimeBaseStructInit(&tim2);
70
tim2.TIM_Prescaler = prescaler;
71
tim2.TIM_Period = period;
72
tim2.TIM_ClockDivision = TIM_CKD_DIV1;
73
tim2.TIM_CounterMode = TIM_CounterMode_Up;
74
tim2.TIM_RepetitionCounter = 1;
75
TIM_TimeBaseInit(TIM2, &tim2);
76
77
TIM_OCInitTypeDef pwm;
78
TIM_OCStructInit(&pwm);
79
pwm.TIM_OCMode = TIM_OCMode_PWM1;
80
pwm.TIM_OutputState = TIM_OutputState_Enable;
81
pwm.TIM_Pulse = 2;
82
pwm.TIM_OCPolarity = TIM_OCPolarity_High;
83
TIM_OC2Init(TIM2, &pwm);
84
85
TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);
86
87
TIM_Cmd(TIM2, ENABLE);

Hat jemand eine Idee, was ich falsch mache?

Vielen Danke schon mal
Raphael

[1]: 
http://www.st.com/content/ccc/resource/technical/document/application_note/ac/cb/d7/31/a9/ae/4a/43/DM00053400.pdf/files/DM00053400.pdf/jcr:content/translations/en.DM00053400.pdf
[2]: 
http://www.st.com/content/ccc/resource/technical/document/reference_manual/c2/f8/8a/f2/18/e6/43/96/DM00031936.pdf/files/DM00031936.pdf/jcr:content/translations/en.DM00031936.pdf
[3]: https://www.mikrocontroller.net/articles/WS2812_Ansteuerung

von Markus M. (adrock)


Lesenswert?

Vom Prinzip her würde das funktionieren, aber Du benötigst relativ viel 
Speicher für den Buffer mit den Timerwerten.

Ich würde lieber SPI mit 3 MHz Bitrate nehmen. Dann kannst Du ein WS2812 
Bit aus drei SPI-Bits (100 und 110 für jeweils 0/1) zusammensetzen und 
benötigst weniger Bufferspeicher.

EDIT: Bei 12 LEDs ist das natürlich egal mit dem Speicher.

Muss man mal sehen, ich glaube ich hatte das mit dem DMA schonmal 
programmiert. Falls ich den Code noch finde...

: Bearbeitet durch User
von Raphael L. (rleh)


Lesenswert?

Die ineffiziente Speichernutzung ist mit bewusst.

Ich hatte Anfangs meinen Lösungsansatz als den einfachsten eingeschätzt, 
aber dem ist scheinbar nicht so.
Mit SPI ist das natürlich eine effizientere Lösung sofern man noch eine 
SPI Schnittstelle frei hat (Ist bei mir der Fall).

Trotzdem würde mich interessieren wieso meine Idee/mein Code kein 
einziges Signal am Ausgang erzeugt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.