Forum: Mikrocontroller und Digitale Elektronik Arduino inline ASM Fragen.


von Trulla O. (obertrulla)


Lesenswert?

Es geht um folgenden code auf atmega328p der 7 WS2812 parallel 
ansteuert:
1
#define mask 0b01111111
2
#define ws2812_PORTREG PORTC
3
#define ws2812_DDREG DDRC
4
5
void  ws2812_sendarray(uint8_t *data,uint16_t datlen)
6
{
7
  uint8_t curbyte,ctr;
8
  while (datlen--) 
9
  {
10
    curbyte=*data++;
11
    asm volatile
12
    (
13
    "       st    x,%3  \n\t"    //  set maskhi 
14
    "       rjmp .+0 \n\t"       //  wait 
15
    "       st    x,%1  \n\t"    //  set byte
16
    "       rjmp .+0 \n\t"       //  wait 
17
    "       st    x,%4  \n\t"    //  set masklo 
18
    "       rjmp .+0 \n\t"       //  wait 
19
    : "=&d" (ctr)
20
    : "r" (curbyte), "x" (&ws2812_PORTREG), "r" (mask), "r" (~mask)
21
    );
22
  }
23
}

Frage 1:
Die variable "ctr" brauch ich nicht, aber wenn ich die zeile rausnehme 
reklamiert der compiler. Ich versteh die syntax nicht.

Frage 2:
1
  while (datlen--) 
2
  {
3
    curbyte=*data++;
Sollte auch in asm sein um das timing genau einzustellen. KA wie ich in 
asm einen pointer auf ein array auslese. Funktioniert zwar ohne, aber 
ein delay zwischen den bytes ist sichtbar.

: Bearbeitet durch User
von OMG (Gast)


Lesenswert?

Trulla O. schrieb:
> aber wenn ich die zeile rausnehme
> reklamiert der compiler.

Du schreibst nicht welche Zeile du "rausnimmst".
Du schreibst nicht wie der Compiler "reklamiert".

Wie wär's wenn du präzise Angaben machst?

von Peter D. (peda)


Lesenswert?

Trulla O. schrieb:
> Es geht um folgenden code

Es ist immer ne ganz schlechte Idee, Code aus dem Kontext heraus zu 
reißen.
Magst Du mal den Ursprung verlinken. Da ist bestimmt beschrieben, was 
dieser Code überhaupt machen soll.
Und der Autor wird sich bei "ctr" auch was gedacht haben.

von Falk B. (falk)


Lesenswert?

Trulla O. schrieb:
> ie variable "ctr" brauch ich nicht, aber wenn ich die zeile rausnehme
> reklamiert der compiler. Ich versteh die syntax nicht.

Ich auch nicht ;-) Inline Assembler ist zu 99% write only.
Eine derartige einfache und kurze Funktion würde ich als reinen 
Assembler in einer .S Datei schreiben. Das geht einfacher als man denkt.

https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Assembler_und_Inline-Assembler

https://www.microchip.com/wwwAppNotes/AppNotes.aspx?appnote=en591235

AT1886

von Trulla O. (obertrulla)


Lesenswert?

ctr hab ich eleminiert.
1
    :: "r" (curbyte), "x" (&ws2812_PORTREG), "r" (mask), "r" (~mask)
und %nummer um eins reduzieren da weniger parameter.

Warum hier ST 16-bit-pt verwendet wird und nicht OUT 8-bit-pt ist mir 
nicht klar. Mit OUT bring ich das nicht zum laufen.

Jetzt noch *data in einen 16-bit-pt laden und von dort lesen ...

von Falk B. (falk)


Lesenswert?

Trulla O. schrieb:
> und %nummer um eins reduzieren da weniger parameter.
>
> Warum hier ST 16-bit-pt verwendet wird und nicht OUT 8-bit-pt ist mir
> nicht klar. Mit OUT bring ich das nicht zum laufen.

Warum meinst du, ohne einen Hauch von Kenntnissen zum Thema Inline-ASM 
dort rumschrauben zu müssen? Die Funktion hast du irgendwo her kopiert, 
sie funktioniert. Was soll das Ganze?

von Trulla O. (obertrulla)


Lesenswert?

Das original ist das aus der ws2812_light library.

Wozu das ganze ändern? Das original steuert 1 pin, einen string von LED. 
Ich möchte aber 7 strings von LED ansteuern. 7 mal schneller. Die 
chinesen bauen damit audio spektrum displays.

Es geht darum 7 serielle signale zu produzieren, nicht nur eines. Und du 
wirst zugeben müssen das ist ziemlich genial ✨

Ja, das funktioniert bereits, es geht darum das zu optimieren / 
verstehen. Wahrscheinlich ist bei OUT das timing zu schnell ...

Da fragt man was und alle antworten sind ziemlich demotivierend. ausser 
dem hier:

Falk B. schrieb:
> Ich auch nicht ;-)

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Trulla O. schrieb:
> Warum hier ST 16-bit-pt verwendet wird und nicht OUT 8-bit-pt ist mir
> nicht klar.

Weil sie offenbar nicht garantieren können oder wollen, dass das 
Portregister auch tatsächlich im per OUT adressierbaren Bereich liegt.

"ctr" wird im Original gar nicht benutzt und daher vom Compiler eh 
wegoptimiert.

Trulla O. schrieb:
> Ich möchte aber 7 strings von LED ansteuern. 7 mal schneller.

Dann ist es in der Tat das Einfachste, du folgst Falks Rat und schreibst 
das Ding komplett neu in Assembler und linkst es zum C-Code dazu.

von Oliver S. (oliverso)


Lesenswert?

Trulla O. schrieb:
> Die variable "ctr" brauch ich nicht, aber wenn ich die zeile rausnehme
> reklamiert der compiler. Ich versteh die syntax nicht.

https://rn-wissen.de/wiki/index.php/Inline-Assembler_in_avr-gcc#Instruktionen_und_Constraints

UNd ich würds ja in C schreiben. Wofür hat man schließlich den Compiler
1
#pragma GCC push_options
2
#pragma GCC optimize ("Os")
3
void ws2812_sendarray2(uint8_t *data, uint16_t datlen)
4
{
5
  while (datlen--)
6
  {
7
    uint8_t curbyte = *data++;
8
    ws2812_PORTREG = mask;
9
    asm volatile ("nop");
10
    ws2812_PORTREG = curbyte;
11
    asm volatile ("nop");
12
    ws2812_PORTREG = ~mask;
13
  }
14
}
15
#pragma GCC pop_options

Oliver

von Falk B. (falk)


Lesenswert?

Trulla O. schrieb:
> Das original ist das aus der ws2812_light library.
>
> Wozu das ganze ändern? Das original steuert 1 pin, einen string von LED.
> Ich möchte aber 7 strings von LED ansteuern. 7 mal schneller. Die
> chinesen bauen damit audio spektrum displays.

Ich hab da meine Zweifel, daß ein AVR-Arduino dafür die richtige Wahl 
ist. Denn das Konzept beruht darauf, daß die CPU den Datenstrom vorher 
berechnet und in den RAM schreibt. Die Funktion gibt die nur aus. Dazu 
braucht es aber ne MENGE an RAM. Und ob das dann schneller ist als der 
Standardansatz mit einem Bit, ist fraglich. Für größere CPUs, vor allem 
ARM mit DMA etc. gibt es solche Mehrkanalfuntkionen, die auch wirklich 
mehr Leistung bringen.

WS2812 Ansteuerung

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> UNd ich würds ja in C schreiben.

Was gleich noch mit den Vorteil hat, dass der Compiler automatisch OUT 
benutzt, wenn der Port im dafür erreichbaren Bereich liegt.

von Trulla O. (obertrulla)


Lesenswert?

Oliver S. schrieb:
> UNd ich würds ja in C schreiben.

Geht nicht. Aber mit 3 nop gehts. 🤩🤩🤩
1
#pragma GCC push_options
2
#pragma GCC optimize ("Os")
3
void ws2812_sendarray(uint8_t *data, uint16_t datlen)
4
{
5
  while (datlen--)
6
  {
7
    uint8_t curbyte = *data++;
8
    ws2812_PORTREG = mask;
9
    asm volatile ("nop \n\t nop \n\t nop");
10
    ws2812_PORTREG = curbyte;
11
    asm volatile ("nop \n\t nop \n\t nop");
12
    ws2812_PORTREG = ~mask;
13
    asm volatile ("nop \n\t nop");
14
  }
15
}
16
#pragma GCC pop_options

: Bearbeitet durch User
von Trulla O. (obertrulla)


Lesenswert?

Falk B. schrieb:
> Dazu
> braucht es aber ne MENGE an RAM. Und ob das dann schneller ist als der
> Standardansatz mit einem Bit, ist fraglich.

Es braucht 12.5% mehr ram wie die 1-bit variante. Und es ist 7 mal 
schneller. Wenn alle 8 pins eines ports rausgeführt wären wäre ram 1:1, 
speed 8 fach.

Motivationsvideo: https://www.youtube.com/watch?v=2p-FsQTYz1Q

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Trulla O. schrieb:
> Geht nicht. Aber mit 3 nop gehts. 🤩🤩🤩

Was dann ja auch deine Frage beantwortet, warum im Original st und nicht 
out verwendet wurde. Irgendwie muß man die Zyklen anscheinend 
verballern.

Oliver

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Trulla O. schrieb:
> Es braucht 12.5% mehr ram wie die 1-bit variante. Und es ist 7 mal
> schneller.

Nö. Das ist die Milchmädchenrechnung, vor der ich gewarnt habe. Die 
AUSGABE ist 7x schneller! Aber vorher mußt du die Daten berechnen! Wie 
lange dauert das?

> Wenn alle 8 pins eines ports rausgeführt wären wäre ram 1:1,
> speed 8 fach.

Bla.

Beitrag "Re: Frage zu IR-Remote+LED-Strips an AVR"

Die Funktion brauch keine Umrechnung, da werden die RGB Daten direkt 
ausgegeben und auch in Echtzeit berechnet.

von Falk B. (falk)


Lesenswert?

Oliver S. schrieb:
> Irgendwie muß man die Zyklen anscheinend
> verballern.

Man muss ein relativ genaues Timing einhalten. Schneller bringt da nix, 
außer Farbfehlern.

von Trulla O. (obertrulla)


Angehängte Dateien:

Lesenswert?

Jetzt brauch ich noch einen vernünftigen "datendreher".

von Falk B. (falk)


Lesenswert?

Trulla O. schrieb:
> Jetzt brauch ich noch einen vernünftigen "datendreher".

Das sind 5x8=40 LEDs. Bei 800kbit/s und 24 Bit/LED sind das 1,152ms für 
einen Update mit einer Datenleitung. Das die Dinger intern sowieso nur 
mit ca. 400Hz PWM arbeiten, ist ein Update in weniger als 2,5ms sinnlos. 
Für einen Equalizer sowieso, denn 400Hz Bildrate braucht kein Mensch, 
20-100 reichen.
D.h. bei 100Hz LED-Refresh bleiben dir "nur" ~8,8ms bzw. 88% CPU Last 
übrig, um die Daten für die LEDs zu berechnen.

von Peter D. (peda)


Lesenswert?

Trulla O. schrieb:
> Jetzt brauch ich noch einen vernünftigen "datendreher".

Hier mal für ne 8*12 Matrix:
1
void pwm_set()
2
{
3
  uint16_t *pval = pwm_val; 
4
  uint8_t pdat[PWM_BITS] = {0};               // optimized to register
5
  
6
  for( uint8_t mask = 1; mask; mask <<= 1 ){
7
    uint16_t v = *pval++;
8
#define MOVE_BIT(x) if( PWM_BITS > x && (PWM_POL ? v : ~v) & 1<<x ) pdat[x] |= mask
9
    MOVE_BIT(0);  MOVE_BIT(1);  MOVE_BIT(2);  MOVE_BIT(3);
10
    MOVE_BIT(4);  MOVE_BIT(5);  MOVE_BIT(6);  MOVE_BIT(7);
11
    MOVE_BIT(8);  MOVE_BIT(9);  MOVE_BIT(10); MOVE_BIT(11);
12
    MOVE_BIT(12); MOVE_BIT(13); MOVE_BIT(14); MOVE_BIT(15);
13
  } 
14
#define STORE_PWM(x) if( PWM_BITS > x ) pwm_data[x]  = pdat[x]
15
  STORE_PWM(0);  STORE_PWM(1);  STORE_PWM(2);  STORE_PWM(3);
16
  STORE_PWM(4);  STORE_PWM(5);  STORE_PWM(6);  STORE_PWM(7);
17
  STORE_PWM(8);  STORE_PWM(9);  STORE_PWM(10); STORE_PWM(11);
18
  STORE_PWM(12); STORE_PWM(13); STORE_PWM(14); STORE_PWM(15);
19
}

Beitrag "AVR: Fast-PWM (BAM) 12 Bit für 8 Kanäle"

von Trulla O. (obertrulla)


Lesenswert?

Peter D. schrieb:
> Beitrag "AVR: Fast-PWM (BAM) 12 Bit für 8 Kanäle"

Muss mich da einarbeiten. Versteh nur bahnhof.

von Stefan F. (Gast)


Lesenswert?

Trulla O. schrieb:
> Jetzt brauch ich noch einen vernünftigen "datendreher".

40 LEDs zu je 60 mA brauchen 2,4 A. Vergleiche das mal mit den 
technischen Daten deines Arduino Boardes oder der USB Spezifikation.

von Beo B. (beobachte)


Lesenswert?

Trulla O. schrieb:
> Geht nicht. Aber mit 3 nop gehts. 🤩🤩🤩

Wennschon:
1
void ws2812_sendarray(uint8_t *data, uint16_t datlen)
2
{
3
  cli();  
4
  while (datlen--)
5
  {
6
    uint8_t curbyte = *data++;
7
    ws2812_PORTREG = mask;
8
    asm volatile ("nop \n\t nop \n\t nop");
9
    ws2812_PORTREG = curbyte;
10
    asm volatile ("nop \n\t nop \n\t nop");
11
    ws2812_PORTREG = ~mask;
12
    asm volatile ("nop \n\t nop");
13
  }
14
  sei();
15
}

Und das bit-drehen macht der compiler:
1
struct bitarr8
2
{
3
   uint8_t bit0 : 1;
4
   uint8_t bit1 : 1;
5
   uint8_t bit2 : 1;
6
   uint8_t bit3 : 1;
7
   uint8_t bit4 : 1;
8
   uint8_t bit5 : 1;
9
   uint8_t bit6 : 1;
10
   uint8_t bit7 : 1;
11
};
12
13
// 8 times one byte bits 0..4
14
// in 8 bytes out 8 bytes, or in 5 byte out 8 byte
15
16
void shuffle(struct bitarr8 in[8], struct bitarr8 out[8])
17
{  
18
  // in byte 1 bit 1..8 goes out byte 1..8m bit 1
19
  out[7].bit0 = in[0].bit0;
20
  out[7].bit1 = in[1].bit0;
21
  out[7].bit2 = in[2].bit0;
22
  out[7].bit3 = in[3].bit0;
23
etc ...

Aufruf mit void*:
1
shuffle((void*)&pixelin[i],(void*)&pixelout[i]);

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Beo B. schrieb:
> Und das bit-drehen macht der compiler:

Und du bist dir so sicher, daß das auf dem AVR optimal schnell läuft? 
Ich nicht.

von Beo B. (beobachte)


Lesenswert?

Falk B. schrieb:
> daß das auf dem AVR optimal schnell läuft?

Der konstruktive weg wäre eine einfachere oder schnellere variante 
vorzuschlagen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Beo B. schrieb:
> asm volatile ("nop \n\t nop \n\t nop");

Wenn du den Reset eh schon in C machst, kannst du auch gleich 
_delay_us() benutzen. Das bezieht dann auch gleich die Taktfrequenz mit 
ein, um entweder eine passende Anzahl von NOPs zu generieren oder eine 
entsprechende Schleife.

: Bearbeitet durch Moderator
von chris_ (Gast)


Lesenswert?

Wenn es auf's Zyklen zählen ankommt, würde ich dem C-Compiler nicht 
vertrauen, da ist man bei echten Assemblerbefehlen doch eher auf der 
sicheren Seite.

von Stefan F. (Gast)


Lesenswert?

chris_ schrieb:
> Wenn es auf's Zyklen zählen ankommt, würde ich dem C-Compiler nicht
> vertrauen, da ist man bei echten Assemblerbefehlen doch eher auf der
> sicheren Seite.

Kannst du aber, delay_us() kriegst du selbst nicht besser hin.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

chris_ schrieb:
> da ist man bei echten Assemblerbefehlen doch eher auf der sicheren
> Seite.

Nur dass du dir die eben für jede CPU-Taktfrequenz selbst zusammen 
basteln musst, während der Compiler das für dich automatisch berechnen 
kann. Kleiner als zyklengenau kann er es natürlich auch nicht :-), aber 
das kann er schon. Nicht, weil ein Compiler grundsätzlich besser wäre, 
sondern weil ihm diverse fleißige Programmierer das mal beigebracht 
haben.

(Diese Aussage trifft in der Form erstmal auf den AVR zu. Bei anderen 
Architekturen ist das sicher anders oder gar nicht so machbar.)

Beitrag #7045143 wurde vom Autor gelöscht.
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.