Hi,
kurz die Rahmenbedingungen (oder direkt zum letzten Absatz springen):
Ich habe einen 16x16x16 Mono-Led Würfel (Pins: 256 Columns + 16 Planes)
den ich über einer selbst entworfenen Platine ansteuern möchte.
Mit dem kleineren Bruder (8x8x8 Würfel) klappt das schon relativ gut
(Ohne DMA).
Jetzt habe ich aber das Problem das dem Mikrocontroller den ich verwende
(STM32F103 @ 72MHz) die Puste ausgeht.
Ich würde den Würfel gerne mit 100Hz Basisfrequenz und 32(+1)PWM Stufen
laufen lassen
1
=> 100Hz * 16 * 32 = 51.2kHz Timer
2
=> 72MHz / 51.2kHz = ~1406 Takte zur Datenübertragung
Nach dem Datasheet des Led Controllers (MBI5026) kann dieser mit bis zu
25MHz angesteuert werden.
Da der µC max. 18MHz SPI unterstützt würde ich diese Frequenz verwenden.
1
256 / 18MHz * 72MHz = 1024 Takte
Ist also weniger als die obere Grenze von 1406 Takten und für Overhead
sollte auch genug Luft sein.
Jetzt kommt das eigentliche Problem.
Die Quelldaten liegen nicht direkt in dem Format vor wie die Bits
gesendet werden müssen.
In den Quelldaten hat jede Led-Column ein Byte, der Bytewert gibt die
Helligkeit vor (Also von 0..32).
Ich muss also aus jedem Byte ein Bit berechnen und an entsprechender
Stelle setzen. Folgender vereinfachter C-Code ist das was ich momentan
verwende.
tl;dr: Kann der Code unten weiter optimiert werden (Cortex M3 / ARM v7)?
Ich müsste unter ~1400 Takte kommen, wenn es nur über Assembler geht
würde ich dazu auch nicht nein sagen.
(Interessant ist nur die innere Scheife).
1
#include<malloc.h>
2
3
voidmain(){
4
intledCount=256
5
char*src=calloc(1,ledCount);
6
char*dst=calloc(1,ledCount/8);
7
8
for(inti=0;i<ledCount;i++){
9
src[i]=i;
10
}
11
12
for(intpwmStep=0;pwmStep<32;pwmStep++){
13
//Latch LedController Data to Output
14
for(inti=0;i<ledCount;i+=8){
15
dst[i]=
16
(src[i|0]>pwmStep?0x01:0)|
17
(src[i|1]>pwmStep?0x02:0)|
18
(src[i|2]>pwmStep?0x04:0)|
19
(src[i|3]>pwmStep?0x08:0)|
20
(src[i|4]>pwmStep?0x10:0)|
21
(src[i|5]>pwmStep?0x20:0)|
22
(src[i|6]>pwmStep?0x40:0)|
23
(src[i|7]>pwmStep?0x80:0);
24
}
25
//Transfer dst via DMA-SPI to LedControllers
26
}
27
}
Sobald ich wieder zuhause bin füge ich noch ein paar Details hinzu (ASM
Generiert vom C Code, sowie Anzahl der benötigten Takte).
Wenn Du den Algo etwas änderst und eine Tabelle für alle 32 PWM-Werte
gleichzeitig erzeugst (die ist dann 1kbyte groß), dann bekommst Du das
wesentlich effektiver hin:
* das komplette dst-Array Null setzen
* für jede Led EIN Bit im dst-Array setzen, und zwar dort,
wo dessen Helligkeit dem PWM-Offset im Array entspricht.
An dieser Stelle wird jede Led genau einmal im PWM-Zyklus angeschaltet.
Nun müssen wir erreichen, dass jede Led nach diesem Einschalten für
diesen PWM-Zyklus eingeschaltet bleibt. Das lässt sich für jeweils 32
Leds parallel ausführen, in dem die dst-Array-Werte jedes PWM-Werts mit
dem jeweils nächsten verODERt und im nächsten abgespeichert werden:
* erste dst-PWM-Spalte mit der 2. Spalte verODERn und in 2. Spalte
speichern.
dann mit 3. Spalte verodern und in 3 Spalte speichern, usw. bis zum
Ende des Arrays.
Da Du mit diesem Verfahren alle 32 PWM-Tabellen berechnest, hast Du
dafür die 32-fache Zeit zur Verfügung. Sinnvollerweise benutzt Du 2
Tabellen im Wechsel, eine wird berechnet, während die 2. über DMA
ausgegeben wird.
Happy programming, Stefan
Dein Tipp ist Gold wert.
Der Code der für jede PWM Stufe läuft, braucht jetzt nur noch 64 Takte +
DMA Start Overhead der hoffentlich nicht groß ist.
Dazu kommt der Code der für jede Ebene läuft. Dieser benötigt 8381
Takte.
Runtergebrochen auf die PWM Stufen brauche ich jetzt also nur noch
64 + (DMA Overhead) + 8190 / 32 = ~320 Takte + (DMA Overhead)
=> CPU Auslastung von ca. 25%
Hier der Code für die Berechnung der PWM Daten
Die Oder Verknüpfungen habe ich durch Addition ersetzt da ich dadurch
nochmals viele Takte einspare.
(Oder zumindest konnte der Compiler so schnelleren Code erzeugen)
Ausgabe von d->_ledPwmData und d->_ledBuffer im Anhang.
Das war was ich zuerst benutzt habe, mit dem gleichen Gedanken das es
schneller ist als die Schleife selbst zu schreiben. Aber danach ist
Anzahl der Ticks stark angestiegen (von 8190 auf 20070 mit Optimierungen
-O3 und von 31534 auf 36262 ohne Optimierungen -O0).
Geändert wurde nur von
David K. schrieb:> uint32_t *ledBuffer = d->_ledBuffer;> size_t ledBufferIntCount = (d->planeLedCount * d->ledPwmSteps) >> 5;> for (size_t i = 0; i < ledBufferIntCount; i++) {> ledBuffer[i] = 0;> }>> nach> memset(d->_ledBuffer, 0, d->planeLedCount * d->ledPwmSteps);
das ist aber nicht die identische Größe. Die Schleife shiftet die Größe
um 5, kopiert aber int32. Das heisst, memset setzt 8* mehr Daten auf
Null.
War das nur ein copy/paste hierher oder hast Du wirklich so verglichen?
Finde ich cool, dass es so funktioniert!
Viele Grüße, Stefan
Stefan K. schrieb:> das ist aber nicht die identische Größe. Die Schleife shiftet die Größe> um 5, kopiert aber int32. Das heisst, memset setzt 8* mehr Daten auf> Null.
Ohje, ich habe in der Tat vergessen bei memset die Anzahl durch 8 zu
teilen.
Dadurch werden die benötigten Takte für memset zwar glaubhafter, die
manuelle Variante ist aber immer noch einige hundert Takte schneller
wenn Optimierung eingeschaltet ist:
1
O0 O3
2
memset 26460 8567
3
manuell 31526 8194
Nach etwas Recherche bin ich auf den Artikel
https://blog.regehr.org/archives/28 gestoßen.
Insbesondere den Punkt "5. Expecting volatile to enforce ordering with
non-volatile accesses".
Deswegen habe ich den Code dementsprechend angepasst. Messungen oben
sind mit diesen Änderungen und hatten also keinen Einfluss darauf das
manuelles nullen schneller ist als memset.
Hier der geupdatete Code mit der main Methode: