mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik STM32F103 Bytes nach Bits (DMA SPI)


Autor: DvdKhl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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
=> 100Hz * 16 * 32 = 51.2kHz Timer
=> 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.
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).
#include <malloc.h>

void main() {
    int ledCount = 256
    char *src = calloc(1, ledCount);
    char *dst = calloc(1, ledCount / 8);

    for(int i = 0; i < ledCount; i++) {
        src[i] = i;
    }

    for(int pwmStep = 0; pwmStep < 32; pwmStep++) {
        //Latch LedController Data to Output
        for(int i = 0; i < ledCount; i += 8) {
            dst[i] = 
                (src[i | 0] > pwmStep ? 0x01 : 0) |
                (src[i | 1] > pwmStep ? 0x02 : 0) |
                (src[i | 2] > pwmStep ? 0x04 : 0) |
                (src[i | 3] > pwmStep ? 0x08 : 0) |
                (src[i | 4] > pwmStep ? 0x10 : 0) |
                (src[i | 5] > pwmStep ? 0x20 : 0) |
                (src[i | 6] > pwmStep ? 0x40 : 0) |
                (src[i | 7] > pwmStep ? 0x80 : 0);
        }
        //Transfer dst via DMA-SPI to LedControllers
    }
}

Sobald ich wieder zuhause bin füge ich noch ein paar Details hinzu (ASM 
Generiert vom C Code, sowie Anzahl der benötigten Takte).

Autor: DvdKhl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oben geposteter code benötigt 2219 Takte.
Mit folgendem Code konnte ich es noch auf 1868 Takte verringern:
int ledCount;
uint8_t *src;
uint8_t *dst;
int *STCSR = (int *)0xE000E010;
int *STRVR = (int *)0xE000E014;
int *STCVR = (int *)0xE000E018;

volatile unsigned int *DWT_CYCCNT = (unsigned int *)0xE0001004;
volatile unsigned int *DWT_CONTROL = (unsigned int *)0xE0001000;
volatile unsigned int *SCB_DEMCR = (unsigned int *)0xE000EDFC;
volatile unsigned int count;

uint8_t *lookup;
void loop2(uint8_t pwmStep) {
  volatile unsigned int start = *DWT_CYCCNT;
  //Latch LedController Data to Output
  uint8_t *lookupShift = lookup + (pwmStep << 6);
  for (int i = 0; i < ledCount; i += 8) {
    dst[i >> 3] = 
        (lookupShift[src[i | 0]] << 0) |
        (lookupShift[src[i | 1]] << 1) |
        (lookupShift[src[i | 2]] << 2) |
        (lookupShift[src[i | 3]] << 3) |
        (lookupShift[src[i | 4]] << 4) |
        (lookupShift[src[i | 5]] << 5) |
        (lookupShift[src[i | 6]] << 6) |
        (lookupShift[src[i | 7]] << 7);
  }
  count = *DWT_CYCCNT - start;
  //Transfer dst via DMA-SPI to LedControllers
}

int main() {
  *SCB_DEMCR = *SCB_DEMCR | 0x01000000;
  *DWT_CYCCNT = 0; // reset the counter
  *DWT_CONTROL = *DWT_CONTROL | 1; // enable the counter
  
  lookup = calloc(1, 64 * 64);
  for (size_t i = 0; i < 64; i++) {
    for (size_t j = 0; j < 64; j++) {
      lookup[i | (j << 7)] = i > j ? 1 : 0;
    }
  }
  
  ledCount = 256;
  src = calloc(1, ledCount);
  dst = calloc(1, ledCount / 8);

  for (size_t i = 0; i < ledCount; i++) src[i] = i % 64;

  for (uint8_t pwmStep = 0; pwmStep < 32; pwmStep++) {
    loop2(pwmStep);
  }
}

Hier der Assembler Dump der loop2 Methode:
Disassembly of section .text.loop2:
00000000 <loop2>:
   0:   e92d 47f0       stmdb   sp!, {r4, r5, r6, r7, r8, r9, sl, lr}
   4:   4e2e            ldr     r6, [pc, #184]  ; (c0 <loop2+0xc0>)
   6:   4a2f            ldr     r2, [pc, #188]  ; (c4 <loop2+0xc4>)
   8:   6833            ldr     r3, [r6, #0]
   a:   4c2f            ldr     r4, [pc, #188]  ; (c8 <loop2+0xc8>)
   c:   6811            ldr     r1, [r2, #0]
   e:   681d            ldr     r5, [r3, #0]
  10:   6822            ldr     r2, [r4, #0]
  12:   b082            sub     sp, #8
  14:   9501            str     r5, [sp, #4]
  16:   eb01 1080       add.w   r0, r1, r0, lsl #6
  1a:   2a00            cmp     r2, #0
  1c:   d048            beq.n   b0 <loop2+0xb0>
  1e:   2200            movs    r2, #0
  20:   4f2a            ldr     r7, [pc, #168]  ; (cc <loop2+0xcc>)
  22:   4d2b            ldr     r5, [pc, #172]  ; (d0 <loop2+0xd0>)
  24:   6839            ldr     r1, [r7, #0]
  26:   f042 0302       orr.w   r3, r2, #2
  2a:   5ccb            ldrb    r3, [r1, r3]
  2c:   f042 0e01       orr.w   lr, r2, #1
  30:   f811 c00e       ldrb.w  ip, [r1, lr]
  34:   f811 8002       ldrb.w  r8, [r1, r2]
  38:   f042 0e03       orr.w   lr, r2, #3
  3c:   5cc3            ldrb    r3, [r0, r3]
  3e:   f810 a00c       ldrb.w  sl, [r0, ip]
  42:   f811 c00e       ldrb.w  ip, [r1, lr]
  46:   f042 0e04       orr.w   lr, r2, #4
  4a:   f810 9008       ldrb.w  r9, [r0, r8]
  4e:   009b            lsls    r3, r3, #2
  50:   f811 800e       ldrb.w  r8, [r1, lr]
  54:   f042 0e05       orr.w   lr, r2, #5
  58:   ea43 034a       orr.w   r3, r3, sl, lsl #1
  5c:   f810 a00c       ldrb.w  sl, [r0, ip]
  60:   f811 c00e       ldrb.w  ip, [r1, lr]
  64:   f042 0e06       orr.w   lr, r2, #6
  68:   ea43 0309       orr.w   r3, r3, r9
  6c:   f810 9008       ldrb.w  r9, [r0, r8]
  70:   f811 800e       ldrb.w  r8, [r1, lr]
  74:   f042 0e07       orr.w   lr, r2, #7
  78:   f811 e00e       ldrb.w  lr, [r1, lr]
  7c:   f810 c00c       ldrb.w  ip, [r0, ip]
  80:   ea43 03ca       orr.w   r3, r3, sl, lsl #3
  84:   f810 1008       ldrb.w  r1, [r0, r8]
  88:   ea43 1309       orr.w   r3, r3, r9, lsl #4
  8c:   f810 e00e       ldrb.w  lr, [r0, lr]
  90:   ea43 134c       orr.w   r3, r3, ip, lsl #5
  94:   ea43 1381       orr.w   r3, r3, r1, lsl #6
  98:   6829            ldr     r1, [r5, #0]
  9a:   ea43 13ce       orr.w   r3, r3, lr, lsl #7
  9e:   ea4f 0ed2       mov.w   lr, r2, lsr #3
  a2:   f801 300e       strb.w  r3, [r1, lr]
  a6:   6823            ldr     r3, [r4, #0]
  a8:   3208            adds    r2, #8
  aa:   4293            cmp     r3, r2
  ac:   d8ba            bhi.n   24 <loop2+0x24>
  ae:   6833            ldr     r3, [r6, #0]
  b0:   681b            ldr     r3, [r3, #0]
  b2:   9901            ldr     r1, [sp, #4]
  b4:   4a07            ldr     r2, [pc, #28]   ; (d4 <loop2+0xd4>)
  b6:   1a5b            subs    r3, r3, r1
  b8:   6013            str     r3, [r2, #0]
  ba:   b002            add     sp, #8
  bc:   e8bd 87f0       ldmia.w sp!, {r4, r5, r6, r7, r8, r9, sl, pc}

Hat noch jemand eine Idee wie ich es weiter verbessern kann?

Autor: Stefan K. (stefan64)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
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

Autor: DvdKhl (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das hört sich nach einer sehr guten Idee an, danke.
Bin schon dabei es umzusetzen.

Ich melde mich nochmal wenn ich es zum laufen gebracht habe.

Autor: David K. (dvdkhl)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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
void hd3cDriverPlaneTick(HD3CDriver *d) {
  if (d->_ledBufferState) return;
  
  d->_ledPwmData = d->_getPlaneData(d, d->_tag);

  uint32_t *ledBuffer = d->_ledBuffer;
  size_t ledBufferIntCount = (d->planeLedCount * d->ledPwmSteps) >> 5;
  for (size_t i = 0; i < ledBufferIntCount; i++) {
    ledBuffer[i] = 0;
  }  
  
  size_t planeByteCount = d->planeLedCount >> 3;
  for (size_t i = 0; i < d->planeLedCount; i += 8) {
    size_t j = i >> 3;
    d->_ledBuffer[(d->_ledPwmData[i + 0] * planeByteCount) + j] += 0x01;
    d->_ledBuffer[(d->_ledPwmData[i + 1] * planeByteCount) + j] += 0x02;
    d->_ledBuffer[(d->_ledPwmData[i + 2] * planeByteCount) + j] += 0x04;
    d->_ledBuffer[(d->_ledPwmData[i + 3] * planeByteCount) + j] += 0x08;
    d->_ledBuffer[(d->_ledPwmData[i + 4] * planeByteCount) + j] += 0x10;
    d->_ledBuffer[(d->_ledPwmData[i + 5] * planeByteCount) + j] += 0x20;
    d->_ledBuffer[(d->_ledPwmData[i + 6] * planeByteCount) + j] += 0x40;
    d->_ledBuffer[(d->_ledPwmData[i + 7] * planeByteCount) + j] += 0x80;
  }
  
  size_t planeLedIntCount = d->planeLedCount >> 5;
  for (size_t j = planeLedIntCount; j < ledBufferIntCount; j += planeLedIntCount) {
    for (size_t i = 0; i < planeLedIntCount; i += 8) {
      ledBuffer[j + i + 0] += ledBuffer[j - planeLedIntCount + i + 0];
      ledBuffer[j + i + 1] += ledBuffer[j - planeLedIntCount + i + 1];
      ledBuffer[j + i + 2] += ledBuffer[j - planeLedIntCount + i + 2];
      ledBuffer[j + i + 3] += ledBuffer[j - planeLedIntCount + i + 3];
      ledBuffer[j + i + 4] += ledBuffer[j - planeLedIntCount + i + 4];
      ledBuffer[j + i + 5] += ledBuffer[j - planeLedIntCount + i + 5];
      ledBuffer[j + i + 6] += ledBuffer[j - planeLedIntCount + i + 6];
      ledBuffer[j + i + 7] += ledBuffer[j - planeLedIntCount + i + 7];
    }
  }
  d->_ledBufferState = 1;
}

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.

Autor: Nico W. (nico_w)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
David K. schrieb:
> for (size_t i = 0; i < ledBufferIntCount; i++) {
>     ledBuffer[i] = 0;
>   }

Wie wäre es hier noch mit nem memset?

Autor: David K. (dvdkhl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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
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);

Autor: Nico W. (nico_w)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Schräg. -O3 ist aber nicht immer das schnellste. Bei meinen Projekten 
war -Os meistens bei meinen kritischen Stellen schneller.

Autor: Stefan K. (stefan64)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: David K. (dvdkhl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
        O0     O3
memset  26460  8567
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:
void hd3cDriverPlaneTick(HD3CDriver *d) {
  if (d->_ledBufferState) return;
  
  d->_ledPwmData = d->_getPlaneData(d, d->_tag);

  uint32_t *ledBuffer = d->_ledBuffer;
  size_t ledBufferIntCount = (d->planeLedCount * d->ledPwmSteps) >> 5;
  for (size_t i = 0; i < ledBufferIntCount; i++) {
    ledBuffer[i] = 0;
  }  
  //memset(d->_ledBuffer, 0, (d->planeLedCount * d->ledPwmSteps) >> 3);
  
  size_t planeByteCount = d->planeLedCount >> 3;
  for (size_t i = 0; i < d->planeLedCount; i += 8) {
    size_t j = i >> 3;
    d->_ledBuffer[(d->_ledPwmData[i + 0] * planeByteCount) + j] += 0x01;
    d->_ledBuffer[(d->_ledPwmData[i + 1] * planeByteCount) + j] += 0x02;
    d->_ledBuffer[(d->_ledPwmData[i + 2] * planeByteCount) + j] += 0x04;
    d->_ledBuffer[(d->_ledPwmData[i + 3] * planeByteCount) + j] += 0x08;
    d->_ledBuffer[(d->_ledPwmData[i + 4] * planeByteCount) + j] += 0x10;
    d->_ledBuffer[(d->_ledPwmData[i + 5] * planeByteCount) + j] += 0x20;
    d->_ledBuffer[(d->_ledPwmData[i + 6] * planeByteCount) + j] += 0x40;
    d->_ledBuffer[(d->_ledPwmData[i + 7] * planeByteCount) + j] += 0x80;
  }

  
  size_t planeLedIntCount = d->planeLedCount >> 5;
  for (size_t j = planeLedIntCount; j < ledBufferIntCount; j += planeLedIntCount) {
    for (size_t i = 0; i < planeLedIntCount; i += 8) {
      ledBuffer[j + i + 0] += ledBuffer[j - planeLedIntCount + i + 0];
      ledBuffer[j + i + 1] += ledBuffer[j - planeLedIntCount + i + 1];
      ledBuffer[j + i + 2] += ledBuffer[j - planeLedIntCount + i + 2];
      ledBuffer[j + i + 3] += ledBuffer[j - planeLedIntCount + i + 3];
      ledBuffer[j + i + 4] += ledBuffer[j - planeLedIntCount + i + 4];
      ledBuffer[j + i + 5] += ledBuffer[j - planeLedIntCount + i + 5];
      ledBuffer[j + i + 6] += ledBuffer[j - planeLedIntCount + i + 6];
      ledBuffer[j + i + 7] += ledBuffer[j - planeLedIntCount + i + 7];
    }
  }
  asm volatile("" : : : "memory");
  d->_ledBufferState = 1;
}


int *STCSR = (int *)0xE000E010;
int *STRVR = (int *)0xE000E014;
int *STCVR = (int *)0xE000E018;

volatile unsigned int *DWT_CYCCNT = (unsigned int *)0xE0001004;
volatile unsigned int *DWT_CONTROL = (unsigned int *)0xE0001000;
volatile unsigned int *SCB_DEMCR = (unsigned int *)0xE000EDFC;
volatile unsigned int count;

uint8_t *src;
uint8_t* getPlaneData2(HD3CDriver *d, HD3CGraphics *g) {
  return src;
}

int main() {
  *SCB_DEMCR = *SCB_DEMCR | 0x01000000;
  *DWT_CYCCNT = 0; // reset the counter
  *DWT_CONTROL = *DWT_CONTROL | 1; // enable the counter
  
  hd3cDriver = hd3cDriverCreate();
  hd3cDriverInit(hd3cDriver);
  hd3cDriverSetDataProvider(hd3cDriver, NULL, getPlaneData2);
  
  src = calloc(1, 256);
  for (size_t i = 0; i < 256; i++) src[i] = i % 32;
  
  asm volatile("" : : : "memory");
  volatile unsigned int start = *DWT_CYCCNT;
  asm volatile("" : : : "memory");
  hd3cDriverPlaneTick(hd3cDriver);
  asm volatile("" : : : "memory");
  count = *DWT_CYCCNT - start;
  asm volatile("" : : : "memory");

  return 0;
}

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.