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.
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
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.
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.
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"
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
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?
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.
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?
@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.
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ß.
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.
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.
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!
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 :-)
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.
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.
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.
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.
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.
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.
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.