Forum: Mikrocontroller und Digitale Elektronik WS2812B mit Lib von Martin Hubáček auf F103


von Bernd (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Forum,

ich möchte die WS2812B Library von Martin Hubáček von STM32F302 auf 
STM32F103C8 adaptieren. Leider bereiten mir die DMA Einstellungen 
Probleme.
Meine Adaption ist angehängt. Das Original gibt es hier:
https://github.com/hubmartin/ws2812b_stm32F3
Mir schien die F3 Version näher am F1 als die F4 Version.

Mein Problem konkret ist, dass am PWM Pin (TIM1_CH1, GPIOA PIN8) nur ein 
high Signal (5V) raus kommt (durch einen externen Pullup nach 5V und 
open drain Ausgang). Die PWM tut gar nicht, was vermutlich mit der DMA 
Konfiguration zu tun hat. Ich glaube ich habe die DMA channel für Timer 
update und CC1 richtig adaptiert. RM0008 Seite 282.  Aber was mache ich 
mit CC2? Es gibt keinen DMA channel für TIM1_CH2. Ich habe es mit DMA 
channel4 probiert (TIM1_CH4, TIM1_TRIG, TIM1_COM). So ganz klar ist mir 
die Aufgabe von CC2 aber auch nicht. Ich denke es ist für das DMA 
Doublebuffering gedacht.

Kann mir jemand auf die Sprünge helfen?
Danke.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Eine fertige und funktionierende Lib für STM32F103C8 und für einige 
STM32F4xx findest Du im WordClock-Projekt:

  https://www.mikrocontroller.net/articles/WordClock_mit_WS2812

Repo-Browser, d.h. direkter Link zu den WS2812-Sourcen:

  https://www.mikrocontroller.net/svnbrowser/wordclock24h/src/ws2812/

Benutzt wird DMA und Double-Buffering bei minimalem 
DMA-Buffer-Verbrauch. Der DMA-Buffer ist lediglich 48 Bytes lang statt 
viele KB (je nach Länge der verwendeten Stripes).

: Bearbeitet durch Moderator
von Bernd (Gast)


Lesenswert?

Frank M. schrieb:
> Eine fertige und funktionierende Lib für STM32F103C8 und für einige
> STM32F4xx findest Du im WordClock-Projekt

Hallo Frank,

danke für deine Antwort. Das WordClock Projekt kenne ich. Ich hatte 
etwas für die HAL Libs gesucht, da mein Projekt schon darauf basiert. 
Aber evtl. ist es einfacher deine Version auf HAL umzustricken.

Gruß
Bernd

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Bernd schrieb:
>  Ich hatte etwas für die HAL Libs gesucht, da mein Projekt schon darauf
> basiert

Interessant. Ich dachte bisher, die HAL-Libs würden die Hardware derart 
abstrahieren, dass eine Portierung wesentlich einfacher zu machen wäre. 
Hm.

Drücke Dir die Daumen.

von Bernd (Gast)


Lesenswert?

Frank M. schrieb:
> Ich dachte bisher, die HAL-Libs würden die Hardware derart
> abstrahieren, dass eine Portierung wesentlich einfacher zu machen wäre.

Ich finde es dadurch zum Teil wesentlich schwieriger, weil nicht immer 
klar ist, was eigentlich genau passiert. Das sich einige Strukturen zur 
Initialisierung der Peripherie deutlich von den Standard Peripheral Libs 
unterscheiden, macht es nicht unbedingt einfacher.
Irgendwie komme ich auch mit der HAL Doku nicht zurecht. Ohne Beispiele 
ist es sehr schwer für mich. Was dagegen gut funktioniert, ist eine 
Grundkonfiguration mit CubeMX. Auch wenn die Aufteilung der 
Projektdateien nicht nach meinem Geschmack ist.
Dein Projekt ist sehr schön strukturiert finde ich.

von dxs (Gast)


Lesenswert?

Ich habe mal für den F4 eine Ansteuerung basierend auf der HAL 
geschrieben.
Ausgabe der Daten direkt über DMA/PWM; hier hatte ich das m.E. beste aus 
den vorhandenen Lösungen zusammengesucht. Es ist lediglich ein 
Zwischenspeicher für die Werte notwendig.
(Ich hatte den Zwischenspeicher sogar verdoppelt, um Flackern bei der 
Ausgabe zu vermeiden - wenn gleichzeitig auf den Puffer gelesen und 
geschrieben wird).
Im DMA-Speicher sind die Daten für zwei LEDs enthalten, somit kann immer 
einer aus dem Display-Zwischenspeicher geladen werden.

Die Streifen waren dabei zu einer Matrix angeordnet - dies lässt sich 
aber parametrisieren. Die Matrix ist prinzipiell wie folgt aufgebaut:

---------------|
               |
|--------------|
|
|--------------|
               |
---------------|

etc.

Defines:
1
// WS2812b Defines
2
#define PWM_ZERO 32
3
#define PWM_ONE 64
4
5
#define RED 1
6
#define GREEN 0
7
#define BLUE 2
8
9
#define DISPX 90
10
#define DISPY 6
11
#define COLORS 3
12
13
#define DMABUFSIZE 48 // 2 LED Dataframes á 24 "Bits"

Benötigte Variablen:
1
uint8_t display_buffer[DISPY][DISPX][COLORS] = {0};  //max Display size: 255x255!
2
uint8_t display_buffer_show[DISPY][DISPX][COLORS] = {0};  //double buffer for anti-flicker
3
4
uint8_t x_index = 0;
5
uint8_t y_index = 0;
6
uint8_t res_index = 0;
7
8
// reset_threshold: single dataset length: 1.25us*24 = 30us, min reset pulse 50 us -> res_th = 3
9
uint8_t res_th = 3;
10
11
12
// DMA_Buffer -> 2x LED Daten; Wechsel bei Half complete IRQ
13
uint16_t dma_buffer[DMABUFSIZE] = {0};
14
//uint16_t resetpattern[24] = {0};
15
16
// Buffer state index -> which dataset is next in Buffer
17
uint32_t bufcount = 0;
18
// frame size
19
uint32_t displaydots = DISPY * DISPX;


Folgende Routinen müssen implementiert werden:
(fill_area ist nicht unbedingt notwendig, zwigt aber den Zugriff auf den 
Zwischenspeicher
1
void dma_callback_halfcomplete(DMA_HandleTypeDef *_hdma)
2
{
3
  if(x_index < DISPX && y_index < DISPY) // 0-based index!
4
  {
5
    // load frame into lower dma_buffer
6
    // for each bit, the correspondig pwm value is set
7
    for (uint8_t icol = 0; icol<COLORS; icol++)
8
    {
9
      // test each bit / send high bit first
10
      dma_buffer[icol*8 + 0] = ((display_buffer_show[y_index][x_index][icol] & 0b10000000)>0 ? PWM_ONE : PWM_ZERO );
11
      dma_buffer[icol*8 + 1] = ((display_buffer_show[y_index][x_index][icol] & 0b01000000)>0 ? PWM_ONE : PWM_ZERO );
12
      dma_buffer[icol*8 + 2] = ((display_buffer_show[y_index][x_index][icol] & 0b00100000)>0 ? PWM_ONE : PWM_ZERO );
13
      dma_buffer[icol*8 + 3] = ((display_buffer_show[y_index][x_index][icol] & 0b00010000)>0 ? PWM_ONE : PWM_ZERO );
14
      dma_buffer[icol*8 + 4] = ((display_buffer_show[y_index][x_index][icol] & 0b00001000)>0 ? PWM_ONE : PWM_ZERO );
15
      dma_buffer[icol*8 + 5] = ((display_buffer_show[y_index][x_index][icol] & 0b00000100)>0 ? PWM_ONE : PWM_ZERO );
16
      dma_buffer[icol*8 + 6] = ((display_buffer_show[y_index][x_index][icol] & 0b00000010)>0 ? PWM_ONE : PWM_ZERO );
17
      dma_buffer[icol*8 + 7] = ((display_buffer_show[y_index][x_index][icol] & 0b00000001)>0 ? PWM_ONE : PWM_ZERO );
18
    }
19
    // adjust x index according to strip arrangement
20
    // HERE: 0/0 lower left, alternating x direction with each line
21
    if(y_index%2 == 0)
22
    {
23
      // check for display sizes
24
      if(x_index == DISPX-1) // Border reached
25
      {
26
        // next y line
27
        y_index++;
28
        // No need to change x_index, as it is decreasing in next line
29
      }
30
      else
31
      {
32
        x_index++;
33
      }
34
35
    }
36
    else
37
    {
38
      // Odd line
39
      // check for display sizes
40
      if(x_index == 0) // Border reached
41
      {
42
        // next y line
43
        y_index++;
44
        // No need to change x_index, as it is decreasing in next line
45
      }
46
      else
47
      {
48
        x_index--;
49
      }
50
    }
51
    // End of frame update
52
  }
53
  else if(res_index < res_th ) // 0-based index!
54
  {
55
    if(res_index <2)
56
    {
57
      // for the first two reset frame calls, fill lower buffer with 0
58
      for(int i = 0; i<DMABUFSIZE/2; i++)
59
      {
60
        dma_buffer[i] = 0;
61
      }
62
    }
63
    res_index++;
64
  }
65
  else if(res_index >= res_th)
66
  {
67
    // new frame starts
68
    x_index = 0;
69
    y_index = 0;
70
    res_index = 0;
71
    // load first frame into lower dma_buffer
72
    // for each bit, the correspondig pwm value is set
73
    for (uint8_t icol = 0; icol<COLORS; icol++)
74
    {
75
      // test each bit
76
      dma_buffer[icol*8 + 0] = ((display_buffer_show[y_index][x_index][icol] & 0b10000000)>0 ? PWM_ONE : PWM_ZERO );
77
      dma_buffer[icol*8 + 1] = ((display_buffer_show[y_index][x_index][icol] & 0b01000000)>0 ? PWM_ONE : PWM_ZERO );
78
      dma_buffer[icol*8 + 2] = ((display_buffer_show[y_index][x_index][icol] & 0b00100000)>0 ? PWM_ONE : PWM_ZERO );
79
      dma_buffer[icol*8 + 3] = ((display_buffer_show[y_index][x_index][icol] & 0b00010000)>0 ? PWM_ONE : PWM_ZERO );
80
      dma_buffer[icol*8 + 4] = ((display_buffer_show[y_index][x_index][icol] & 0b00001000)>0 ? PWM_ONE : PWM_ZERO );
81
      dma_buffer[icol*8 + 5] = ((display_buffer_show[y_index][x_index][icol] & 0b00000100)>0 ? PWM_ONE : PWM_ZERO );
82
      dma_buffer[icol*8 + 6] = ((display_buffer_show[y_index][x_index][icol] & 0b00000010)>0 ? PWM_ONE : PWM_ZERO );
83
      dma_buffer[icol*8 + 7] = ((display_buffer_show[y_index][x_index][icol] & 0b00000001)>0 ? PWM_ONE : PWM_ZERO );
84
    }
85
    // increment x index, as first dataset is in dma buffer
86
    x_index++;
87
  }
88
}
89
90
//void dma_callback_complete(DMA_HandleTypeDef *_hdma)
91
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
92
{
93
  if(x_index < DISPX && y_index < DISPY) // 0-based index!
94
  {
95
    // load frame into upper dma_buffer
96
    // for each bit, the correspondig pwm value is set
97
    for (uint8_t icol = 0; icol<COLORS; icol++)
98
    {
99
      // test each bit
100
      dma_buffer[DMABUFSIZE/2 + icol*8 + 0] = ((display_buffer_show[y_index][x_index][icol] & 0b10000000)>0 ? PWM_ONE : PWM_ZERO );
101
      dma_buffer[DMABUFSIZE/2 + icol*8 + 1] = ((display_buffer_show[y_index][x_index][icol] & 0b01000000)>0 ? PWM_ONE : PWM_ZERO );
102
      dma_buffer[DMABUFSIZE/2 + icol*8 + 2] = ((display_buffer_show[y_index][x_index][icol] & 0b00100000)>0 ? PWM_ONE : PWM_ZERO );
103
      dma_buffer[DMABUFSIZE/2 + icol*8 + 3] = ((display_buffer_show[y_index][x_index][icol] & 0b00010000)>0 ? PWM_ONE : PWM_ZERO );
104
      dma_buffer[DMABUFSIZE/2 + icol*8 + 4] = ((display_buffer_show[y_index][x_index][icol] & 0b00001000)>0 ? PWM_ONE : PWM_ZERO );
105
      dma_buffer[DMABUFSIZE/2 + icol*8 + 5] = ((display_buffer_show[y_index][x_index][icol] & 0b00000100)>0 ? PWM_ONE : PWM_ZERO );
106
      dma_buffer[DMABUFSIZE/2 + icol*8 + 6] = ((display_buffer_show[y_index][x_index][icol] & 0b00000010)>0 ? PWM_ONE : PWM_ZERO );
107
      dma_buffer[DMABUFSIZE/2 + icol*8 + 7] = ((display_buffer_show[y_index][x_index][icol] & 0b00000001)>0 ? PWM_ONE : PWM_ZERO );
108
    }
109
    // adjust x index according to strip arrangement
110
    // HERE: 0/0 lower left, alternating x direction with each line
111
    if(y_index%2 == 0)
112
    {
113
      // check for display sizes
114
      if(x_index == DISPX-1) // Border reached
115
      {
116
        // next y line
117
        y_index++;
118
        // No need to change x_index, as it is decreasing in next line
119
      }
120
      else
121
      {
122
        x_index++;
123
      }
124
125
    }
126
    else
127
    {
128
      // Odd line
129
      // check for display sizes
130
      if(x_index == 0) // Border reached
131
      {
132
        // next y line
133
        y_index++;
134
        // No need to change x_index, as it is decreasing in next line
135
      }
136
      else
137
      {
138
        x_index--;
139
      }
140
    }
141
    // End of frame update
142
  }
143
  else if(res_index < res_th ) // 0-based index!
144
  {
145
    if(res_index <2)
146
    {
147
      // for the first two reset frame calls, fill upper buffer with 0
148
      for(int i = 0; i<DMABUFSIZE/2; i++)
149
      {
150
        dma_buffer[DMABUFSIZE/2 + i] = 0;
151
      }
152
    }
153
    res_index++;
154
  }
155
  else if(res_index >= res_th)
156
  {
157
    // new frame starts
158
    x_index = 0;
159
    y_index = 0;
160
    res_index = 0;
161
    // load first frame into upper dma_buffer
162
    // for each bit, the correspondig pwm value is set
163
    for (uint8_t icol = 0; icol<COLORS; icol++)
164
    {
165
      // test each bit
166
      dma_buffer[DMABUFSIZE/2 + icol*8 + 0] = ((display_buffer_show[y_index][x_index][icol] & 0b10000000)>0 ? PWM_ONE : PWM_ZERO );
167
      dma_buffer[DMABUFSIZE/2 + icol*8 + 1] = ((display_buffer_show[y_index][x_index][icol] & 0b01000000)>0 ? PWM_ONE : PWM_ZERO );
168
      dma_buffer[DMABUFSIZE/2 + icol*8 + 2] = ((display_buffer_show[y_index][x_index][icol] & 0b00100000)>0 ? PWM_ONE : PWM_ZERO );
169
      dma_buffer[DMABUFSIZE/2 + icol*8 + 3] = ((display_buffer_show[y_index][x_index][icol] & 0b00010000)>0 ? PWM_ONE : PWM_ZERO );
170
      dma_buffer[DMABUFSIZE/2 + icol*8 + 4] = ((display_buffer_show[y_index][x_index][icol] & 0b00001000)>0 ? PWM_ONE : PWM_ZERO );
171
      dma_buffer[DMABUFSIZE/2 + icol*8 + 5] = ((display_buffer_show[y_index][x_index][icol] & 0b00000100)>0 ? PWM_ONE : PWM_ZERO );
172
      dma_buffer[DMABUFSIZE/2 + icol*8 + 6] = ((display_buffer_show[y_index][x_index][icol] & 0b00000010)>0 ? PWM_ONE : PWM_ZERO );
173
      dma_buffer[DMABUFSIZE/2 + icol*8 + 7] = ((display_buffer_show[y_index][x_index][icol] & 0b00000001)>0 ? PWM_ONE : PWM_ZERO );
174
    }
175
    // increment x index, as first dataset is in dma buffer
176
    x_index++;
177
  }
178
}
179
void fill_area(int16_t xorigin, int16_t yorigin, uint8_t xend, uint8_t yend, uint8_t red, uint8_t green, uint8_t blue, uint8_t dpbuf[DISPY][DISPX][COLORS])
180
{
181
  uint8_t xtemp = xorigin;
182
  while(xtemp <= xend && xtemp < DISPX)
183
  {
184
    uint8_t ytemp = yorigin;
185
    while(ytemp <= yend && ytemp < DISPY)
186
    {
187
      if(xtemp >= 0 && ytemp >= 0)
188
      {
189
        dpbuf[ytemp][xtemp][RED] = red;
190
        dpbuf[ytemp][xtemp][GREEN] = green;
191
        dpbuf[ytemp][xtemp][BLUE] = blue;
192
      }
193
194
      ytemp++;
195
    }
196
    xtemp++;
197
  }
198
}

Diese Routinen müssen beim Programmstart als Interrupt-Callbacks 
registriert werden:
1
// Register DMA Callbacks
2
  // Half Complete
3
  HAL_DMA_RegisterCallback(&hdma_tim3_ch1_trig, HAL_DMA_XFER_HALFCPLT_CB_ID, dma_callback_halfcomplete);
4
  // Complete is handled by void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) !!!
5
  // DMA Start
6
   HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, dma_buffer, DMABUFSIZE);
7
  // Frame updates will be handled automatically by DMA IRQ
8
  // 2 Dataframes delay, as buffer has to be filled by dma handler

Zusätzlich muss - bei doppeltem Puffer - dieser natürlich regelmäßig 
upgedatet werden:
1
memcpy(display_buffer_show, display_buffer_dim, sizeof(display_buffer));

Die Konfiguration ist dabei wie folgt (Insbesondere die für den Timer 
und Timer-Clock ist wichtig):
MCU STM32F407VGTx
HCLK = 160 MHz
APB1 Timer Clock = 80 MHz
APB2 Timer clock = 160 MHz

PWM Generation CH1 / TIM3
Prescaler (PSC - 16 bits value) 0
Counter Mode Up
Counter Period (AutoReload Register - 16 bits value ) 100 *
Internal Clock Division (CKD) No Division
Master/Slave Mode Disable (no sync between this TIM (Master) and its 
Slaves
Trigger Event Selection Reset (UG bit from TIMx_EGR)
PWM Generation Channel 1:
Mode PWM mode 1
Pulse (16 bits value) 0
Fast Mode Enable *
CH Polarity High

DMA request   Stream       Direction            Priority
TIM3_CH1/TRIG DMA1_Stream4 Memory To Peripheral Very High *

TIM3_CH1/TRIG: DMA1_Stream4 DMA request Settings:
Mode: Circular *
Use fifo: Disable
Peripheral Increment: Disable
Memory Increment: Enable *
Peripheral Data Width: Half Word
Memory Data Width: Half Word



Ich hoffe, du (und weitere :) kannst damit was anfangen.

Gruß Daniel

von Bernd (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe nun versucht die Dateien von Frank für den STM32F103 auf HAL 
umzustellen (siehe Anhang).
Die Erkennung des externen PullUps funktioniert und es wird der Pin auf 
OpenDrain gestellt. Die Timer und DMA Strukturen sind bei HAL anders 
aufgebaut und werden anders verknüpft und gestartet. Ich habe mich dabei 
an der schon genannten Bibliothek von Markus orientiert. Er arbeitet 
zwar nach einem ähnlichen Prinzip, benutzt aber 3 DMA Kanäle. Ich 
vermute mal für die parallele Ansteuerung mehrerer Stripes, die er dann 
im Wechsel befüllt.
Frank schreibt glaube ich direkt per DMA in das Timer Compare Register 
und nutzt nur den DMA Interrupt. Hier wird dann per HalbTransfer und 
VollTransfer Callback die Umschaltung im Buffer gemacht.
Bei mir kommt leider gar nichts am PWM Pin raus und das Programm hängt 
in ws2812_refresh() in der while(ws2812_dma_status != 0).
Da der DutyCyle nur per DMA geändert wird, passt also irgendwas an 
meiner Timer und/oder DMA Konfiguration nicht. Bin gerade etwas ratlos, 
wie ich da weiter vorgehen soll. Vielleicht sieht auch jemand einen 
Fehler in der Konfiguration?

von Bernd (Gast)


Lesenswert?

Ich muss wohl nach der PullUp Erkennung den Pin auf "GPIO_MODE_AF_OD" 
stellen, dann kommt mit einem festen Testwert für TIM1->CCR1 und ohne 
Aufruf von ws2812_dma_init() und weiterer WS2812 Funktionen immerhin 
eine feste PWM raus. Also scheint die DMA Konfiguration noch fehlerhaft 
zu sein. Vermutlich das Verlinken von DMA und Timer und/oder das Starten 
von DMA.
Kann hier bitte mal jemand drauf schauen.

von Bernd (Gast)


Lesenswert?

Kann mir jemand erklären, wie ich mit 
HAL_TIM_PWM_Start_DMA(&WS2812_TimHandle, TIM_CHANNEL_1, 
(uint32_t*)dma_buf, DMA_BUF_LEN); den DMA Halbtransfer und 
TransferComplete Interrupt realisieren kann?
Es scheint kein separater DMA_HandleTypeDef nötig zu sein.
Es werden automatisch ein paar Callbacks registriert.
Aber kann ich dann auch das Doublebuffering mit Circular Mode und Half 
und Complete Interrupts nutzen? Oder kann man mit HAL auch noch einen 
Timer und einen DMA_HandleTypeDef verknüpfen?

von Bernd (Gast)


Lesenswert?

@Frank:
Wie wird denn bei deiner Variante nach jeder Timerperiode (sprich jedem 
Bit) ein neuer Wert an den Timer übergeben? Das soll ja der DMA machen, 
aber wie bekommt er mit, dass die Timerperiode um ist? Einen Interrupt 
dafür sehe ich nicht, das muss also irgendwie implizit passieren. Ich 
glaube diese Stelle klappt bei mir mit HAL nicht. Der Timer startet und 
gibt mittlerweile fortlaufend "Nullen" aus (das Timing stimmt, 1,25us 
Periode mit 35% DC) das Pogramm hängt aber in ws2812_refresh() in der 
while Schleife und wartet auf das Ende des DMA Transfers.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dafür wird der "Half-Transfer-Interrupt" verwendet:
1
#  define WS2812_DMA_CHANNEL_IRQ_TC     DMA1_IT_TC2                    // transfer complete interrupt
2
#  define WS2812_DMA_CHANNEL_IRQ_HT     DMA1_IT_HT2                    // half-transfer interrupt
1
void
2
WS2812_DMA_CHANNEL_ISR (void)
3
{
4
#if defined (STM32F4XX)
5
    if (DMA_GetITStatus(WS2812_DMA_STREAM, WS2812_DMA_CHANNEL_IRQ_HT))              // check half-transfer interrupt flag
6
    {
7
        DMA_ClearITPendingBit (WS2812_DMA_STREAM, WS2812_DMA_CHANNEL_IRQ_HT);       // reset flag
8
        ws2812_setup_dma_buf (0);
9
    }
10
    if (DMA_GetITStatus(WS2812_DMA_STREAM, WS2812_DMA_CHANNEL_IRQ_TC))              // check transfer complete interrupt flag
11
    {
12
        DMA_ClearITPendingBit (WS2812_DMA_STREAM, WS2812_DMA_CHANNEL_IRQ_TC);       // reset flag
13
14
        if (current_dma_buf_pos < current_data_pause_len)
15
        {
16
            ws2812_setup_dma_buf (1);
17
        }
18
        else
19
        {
20
            DMA_Cmd (WS2812_DMA_STREAM, DISABLE);                                   // disable DMA
21
            ws2812_dma_status = 0;                                                  // set status to ready
22
        }
23
    }
24
#elif defined (STM32F10X)
25
    if (DMA_GetITStatus(WS2812_DMA_CHANNEL_IRQ_HT))                                 // check half-transfer interrupt flag
26
    {
27
        DMA_ClearITPendingBit (WS2812_DMA_CHANNEL_IRQ_HT);                          // reset flag
28
        ws2812_setup_dma_buf (0);
29
    }
30
    if (DMA_GetITStatus(WS2812_DMA_CHANNEL_IRQ_TC))                                 // check transfer complete interrupt flag
31
    {
32
        DMA_ClearITPendingBit (WS2812_DMA_CHANNEL_IRQ_TC);                          // reset flag
33
34
        if (current_dma_buf_pos < current_data_pause_len)
35
        {
36
            ws2812_setup_dma_buf (1);
37
        }
38
        else
39
        {
40
            DMA_Cmd (WS2812_DMA_STREAM, DISABLE);                                   // disable DMA
41
            ws2812_dma_status = 0;                                                  // set status to ready
42
        }
43
    }
44
#endif
45
}

In ws2812_dma_start() wird dieser dann eingeschaltet:
1
    DMA_ITConfig(WS2812_DMA_STREAM, DMA_IT_TC | DMA_IT_HT, ENABLE);                 // enable transfer complete and half transfer interrupt

DMA_IT_TC: Transfer Complete Interrupt
DMA_IT_HT: Half Transfer Interrupt

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Nachtrag:

Es wird hier ein zirkulärer DMA-Buffer verwendet, der immer nach der 
Hälfte eines DMA-Transfers nachgeladen wird. Du musst Dir das so 
vorstellen wie eine Uhr mit nur einem Sekundenzeiger:

Immer, wenn der Sekundenzeiger unten ist (nach der ersten halben 
Umdrehung), werden die Daten für die übernächste(!) Hälfte nachgeladen.

Immer, wenn der Sekundenzeiger oben ist (zweite halbe Umdrehung), werden 
die Daten für die übernächste(!) Hälfte nachgeladen.

Es ist immer die übernächste Hälfte, die nachgeladen wird, da ja die 
nächste Hälfte bereits im DMA-Buffer liegt. So kann das Programm ganz 
bequem und ohne Zeitnot nachladen.

Insgesamt sind im DMA-Buffer lediglich die Daten für 2 LEDs drin (48 
Bit). Dann läuft das so:
1
1. Füllen Daten für LED 1 und LED 2
2
2. HT-Interrupt (nach Übertragung LED 1): Füllen Daten für LED 3
3
3. TC-Interrupt (nach Übertragung LED 2): Füllen Daten für LED 4
4
Allgemein:
5
4. HT-Interrupt (nach Übertragung LED n): Füllen Daten für LED n + 2
6
5. TC-Interrupt (nach Übertragung LED m): Füllen Daten für LED m + 2
7
6. Weiter bei 4.
Im DMA-Buffer sind bei Auftreten eines Interrupts immer noch die Daten 
für die LED n + 1 drin. Du füllst dann die Daten für LED n + 2 nach.

Der Vorteil ist: Der DMA-Buffer muss nur die Daten für 2 LEDs halten 
können. Bei den klassischen DMA-Transfers brauchst Du bei vielen LEDs 
(zum Beispiel 200) auch einen ebensolchen großen Buffer. Da sind dann 
schnell schon mal 10KB RAM und mehr belegt.

Die ganze Prozedur - wie oben bechrieben - ist zwar ein wenig 
komplizierter, aber wenn man es einmal verstanden und korrekt umgesetzt 
hat, macht es richtig Spaß.

: Bearbeitet durch Moderator
von Bernd (Gast)


Lesenswert?

Hallo Frank,
danke für die Erklärung.
Eine Timerperiode entspricht doch einem Bit, richtig? Die 24 Byte für 
eine LED (also der halbe DMA Buffer) enthalten die 24 Timerwerte die für 
eine LED rausgeschoben werden. Muss dann nicht noch nach jeder 
Timerperiode nachgeladen werden, oder macht das der DMA automatisch?
Ich habe auch libs gesehen, wo noch Tuner Interrupts dafür verwendet 
werden. Diesen Teil habe ich bei dir nicht verstanden.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Bernd schrieb:
> Muss dann nicht noch nach jeder Timerperiode nachgeladen werden, oder
> macht das der DMA automatisch?

Das macht der DMA automatisch unter Zuhilfenahme der 
WS2812_DMA_CHANNEL_ISR().

Sind die ersten 24 Bits (0-23) raus, kommt der HT-Interrupt und füllt 
die Bits 48-71 nach. Sind dann die zweiten 24 Bits (24-47) raus, kommt 
dann der TC-Interrupt und füllt die Bits 72-95 nach. Und so weiter...

Wenn ich in Deinen WS2812-Source reinschaue, hast Du das aber alles 
auskommentiert. Kann ja gar nicht mehr gehen.

: Bearbeitet durch Moderator
von Bernd (Gast)


Lesenswert?

Frank M. schrieb:
> Wenn ich in Deinen WS2812-Source reinschaue, hast Du das aber alles
> auskommentiert. Kann ja gar nicht mehr gehen.

Ich habe es in zwei Callback Funktionen ausgelagert. 
WS2812_DMA_TransferHalfHandler() und 
WS2812_DMA_TransferCompleteHandler() die dann durch 
HAL_DMA_IRQHandler(&WS2812_DMAHandle) in WS2812_DMA_CHANNEL_ISR() 
aufgerufen werden.

Ich habe es leider nicht zum Laufen bekommen und mein kleines Projekt 
von HAL auf StdPeriphLib umgestellt. Jetzt läuft es mit deinem 
Originalcode.
Danke dafür!

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Bernd schrieb:
> Ich habe es leider nicht zum Laufen bekommen und mein kleines Projekt
> von HAL auf StdPeriphLib umgestellt.

Das war wahrscheinlich das sinnvollste.

> Jetzt läuft es mit deinem Originalcode.

Freut mich, gratuliere :-)

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Frank M. schrieb:
> Der Vorteil ist: Der DMA-Buffer muss nur die Daten für 2 LEDs halten
> können.

 Es ist diskutabel, ob das unbedingt ein Vorteil ist.
 Warum rechnet niemand die dauernde Herumspringerei und die dafür
 benötigte (verlorene) Zeit als Nachteil ?


> Bei den klassischen DMA-Transfers brauchst Du bei vielen LEDs
> (zum Beispiel 200) auch einen ebensolchen großen Buffer. Da sind dann
> schnell schon mal 10KB RAM und mehr belegt.

 Nein, bestimmt nicht.
 Ich mache es mit SPI und zwar mit 4 DMA-bit für 1 RGB-bit weil es
 so viel schneller ist als mit 3 DMA-bit für 1 RGB-bit.
 Bei 200LEDs ergibt das 2400Byt für SPI-Array.
 Die 600Byt für RGB-Array hat man sowieso - ob mit Timer oder SPI.
 Insgesamt sind das 3000Byt - woher du die 10KB her hast, ist für
 mich schleierhaft.

 Dafür aber die unbestreitbaren Vorteile:
 Fire and Forget - 99.999% der Zeit braucht sich der Prozessor nicht
 um DMA-Transfer zu den LEDs zu kümmern.
 Die ganze Prozedur ist viel einfacher und schneller.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Warum rechnet niemand die dauernde Herumspringerei und die dafür
>  benötigte (verlorene) Zeit als Nachteil ?

Weil diese benötigte Zeit verschwindend gering ist. Auf der WordClock24 
laufen Animationen mit über 400 LEDs absolut flüssig. Rechne doch mal 
aus, was das kostet, das kann man vergessen.

Marc V. schrieb:
> Ich mache es mit SPI und zwar mit 4 DMA-bit für 1 RGB-bit weil es
>  so viel schneller ist als mit 3 DMA-bit für 1 RGB-bit.

Schön, wo kann man sich Deinen Code anschauen?

>  Bei 200LEDs ergibt das 2400Byt für SPI-Array.

Es gibt durchaus Anwendungen mit mehr als 200 LEDs.

>  Die 600Byt für RGB-Array hat man sowieso - ob mit Timer oder SPI.

Ja.

>  Insgesamt sind das 3000Byt - woher du die 10KB her hast, ist für
>  mich schleierhaft.

Hast Du da nicht die notwendigen Bytes für die 50usec Pause nach einem 
Frame vergessen? Oder wie machst Du das? Da kommt nämlich auch noch 
etwas zusammen - gerade wenn man die neueren WS2813 nimmt. Die benötigen 
sogar 280usec Pause zwischen den Frames. Aber okay, vernachlässigen wir 
das mal.

Rechnen wir doch mal:

Sei N die Anzahl der LEDs, dann ist der RAM-Verbrauch nach Deiner 
Methode:

RAM = 3 x N + 12 x N = 15 x N

Beispiele:

200 LEDs:  3000 Bytes
400 LEDs:  6000 Bytes
800 LEDs: 12000 Bytes

Nach meiner Methode:

RAM = 3 x N + 48

Beispiele:

200 LEDs:   648 Bytes
400 LEDs:  1248 Bytes
800 LEDs:  2400 Bytes

Meine Methode benötigt also rund ein Fünftel gegenüber Deiner Methode.

Bei der WC24, die etwas mehr als 400 LEDs ansteuert, liegt die Ersparnis 
bei knapp 5KB RAM. Bei einem STM32F103C8T6 mit lediglich 20KB RAM, der 
beim WordClock-Projekt eingesetzt wird, kann dieser Speicher durchaus 
sinnvoller eingesetzt werden. Manche Erweiterungen in den letzten Jahren 
wären ohne die obigen Optimierungen gar nicht mehr möglich gewesen.

> Fire and Forget- 99.999% der Zeit braucht sich der Prozessor nicht
> um DMA-Transfer zu den LEDs zu kümmern.

Das gilt auch noch, wenn man die obige Optimierung einbaut, dann sinds 
halt 99,8 Prozentz. Ich habe es jedenfalls nicht geschafft, die 
WS2812-Transfers durch irgendwelche gleichzeitig ablaufenden Aktionen 
kaputtzukriegen.

>  Die ganze Prozedur ist viel einfacher und schneller.

Einfacher: ja
Schneller: minimal, vernachlässigbar
RAM: erheblich schlechter, s.o.

Es kommt halt immer auf die vorhandenen Ressourcen an.

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Warum rechnet niemand die dauernde Herumspringerei und die dafür
>  benötigte (verlorene) Zeit als Nachteil ?

Nach längerem Nachdenken komme ich sogar zu dem Schluss, dass der Trick 
mit dem zirkulären DMA-Buffer sogar reale Zeit spart. Und zwar aus 
folgendem Grund:

Müssen die kompletten 2400 Bytes vor der Anzeige in den DMA-Buffer 
geschoben werden, benötigt man dafür eine Zeit T, bevor überhaupt etwas 
zu sehen ist.

Um den lediglich 48 Byte großen zirkulären Buffer zu füllen, braucht man 
nur ein fünfzigstel der Zeit, also T/50. Die andere Zeit wird erst 
während der Übertragung beansprucht, benötigt also keine zusätzliche 
Real-Time mehr. Die Anzeige erfolgt also eher und ist genauso schnell.

Das zusätzlich verwendete Double-Buffering wurde hier noch gar nicht 
betrachtet. Hier kann während einer DMA-Übertragung schon der nächste 
Frame komplett ausgerechnet und auch schon der zweite 48 Byte große 
Buffer vorbereitet werden, so dass hier ohne irgendwelche großen 
Denkpausen ununterbrochen gefeuert werden kann.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Frank M. schrieb:
> Marc V. schrieb:
>> Ich mache es mit SPI und zwar mit 4 DMA-bit für 1 RGB-bit weil es
>>  so viel schneller ist als mit 3 DMA-bit für 1 RGB-bit.
>
> Schön, wo kann man sich Deinen Code anschauen?

 Wozu ?
 Das ist ganz normaler Code - wenn bit in RGB_Arr[x] == 0, dann ist
 Nibble in SPI_Arr[y] = 0xE0 / 0x0E, else SPI_Arr[y] = 0x80 / 0x08.

> Hast Du da nicht die notwendigen Bytes für die 50usec Pause nach einem
> Frame vergessen? Oder wie machst Du das? Da kommt nämlich auch noch

 Was für Bytes meinst du ?
 Das verstehe ich nun wirklich nicht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Wozu

Du meinst also, dem TO bei der Portierung eines Moduls helfen zu können, 
wenn Du ein wenig Prosa schreibst: "Ich mache das so: ..." und sonst 
ausschließlich an einer konkreten Lösung, die zudem mittlerweile beim TO 
erfolgreich läuft, auch noch herummäkelst. Sorry, kann ich nicht 
nachvollziehen.

Wenn man sogar noch in höchsten Tönen prahlt mit einem Source, den man 
gar nicht vorzeigen will, dann ist das für mich indiskutabel. Wir können 
uns auch gerne über physikalische Phänomene in schwarzen Löchern 
unterhalten. Ist genauso wenig zielführend.

Marc V. schrieb:
> Was für Bytes meinst du ?

Die Null-Bytes, mit denen die meisten im Netz herumschwirrenden 
WS2812-DMA-Routinen garantieren, dass nach der Übertragung 50usec lang 
Ruhe auf der Leitung ist.

Für mich ist hier EOD.

: Bearbeitet durch Moderator
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Frank M. schrieb:
> Du meinst also, dem TO bei der Portierung eines Moduls helfen zu können,
> wenn Du ein wenig Prosa schreibst: "Ich mache das so: ..." und sonst
> ausschließlich an einer konkreten Lösung, die zudem mittlerweile beim TO
> erfolgreich läuft, auch noch herummäkelst.

 Wer mäkelt herum?
1
> Der Vorteil ist: Der DMA-Buffer muss nur die Daten für 2 LEDs halten
2
> können.
 Ich war ganz einfach nicht mit obiger Aussage von dir einverstanden.

 Und wieso dies herummäkeln "an einer konkreten Lösung, die zudem
 mittlerweile beim TO erfolgreich läuft" sein soll, ist wiederum
 eine Aussage von dir, dessen Logik sich mir völlig entzieht.

Frank M. schrieb:
> Wenn man sogar noch in höchsten Tönen prahlt mit einem Source, den man
> gar nicht vorzeigen will, dann ist das für mich indiskutabel.

 Wer prahlt wo mit was ?
 Ich habe weder dich noch dein Program schlechtgemacht oder
 angegriffen, noch habe ich mit irgendeinem Source geprahlt,
 wozu solche Töne ?

 Dein Benehmen könnte man als komplexbeladen bezeichnen, aber ich
 will das natürlich nicht tun.

> Marc V. schrieb:
>> Was für Bytes meinst du ?
>
> Die Null-Bytes, mit denen die meisten im Netz herumschwirrenden
> WS2812-DMA-Routinen garantieren, dass nach der Übertragung 50usec lang
> Ruhe auf der Leitung ist.

 Ich benutze dafür ein Timer mit Flag.
 Auch Null-Bytes garantieren natürlich nicht, dass auf der Leitung
 Ruhe ist.
 Aber selbst wenn:
 Bei 800KHz braucht man für 50us 40bit oder 5Bytes.
 Für 300us (WS2813) braucht man 30Bytes.
 Wie und mit welcher Mathematik du aber trotzdem auf die Differenz von
 7000Bytes kommst, ist mir wiederum unklar.

> Für mich ist hier EOD.

 Eine Diskussion setzt Dialog voraus, bei dir ist es überwiegend ein
 Monolog, deswegen solltest du EOM schreiben.

: Bearbeitet durch User
von imperator (Gast)


Lesenswert?

Frank M. schrieb:
> Sei N die Anzahl der LEDs, dann ist der RAM-Verbrauch nach Deiner
> Methode:
>
> RAM = 3 x N + 12 x N = 15 x N
>
> Beispiele:
>
> 200 LEDs:  3000 Bytes
> 400 LEDs:  6000 Bytes
> 800 LEDs: 12000 Bytes
>
> Nach meiner Methode:
>
> RAM = 3 x N + 48
>
> Beispiele:
>
> 200 LEDs:   648 Bytes
> 400 LEDs:  1248 Bytes
> 800 LEDs:  2400 Bytes

Was ist wenn man von beiden Varianten eine "bessere" macht?! Auch beim 
SPI kann man HT, TC nutzen.

Bei 200leds alle 33khz in die ISR zu hüpfen ist auch ein bissschen 
overhead.

Jeder hat einen gewissen Einsetzzweck, da kann es schon bei einen oder 
fire and forget reichen.

Ich sehe bei SPI nur Vorteile.

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.