Arduino inline ASM Fragen.

OP #7040043
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.
#7040140
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
OP #7040192
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 ...
#7040197
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?
OP #7040202
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 ;-)
Moderator Persönliche Seite #7040216
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.
#7040237
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
#7040254
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
OP #7040259
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
#7040282
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.
#7040512
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.
#7040523
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"
#7042593
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]);
Moderator Persönliche Seite #7043446
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 von einem Moderator gelöscht.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren