Forum: Mikrocontroller und Digitale Elektronik [AVR-GCC] Gelassen läufts (Man braucht nicht für jedes Neo-Pixel 3 oder 4 byte Ram)


von Dominik (Gast)


Lesenswert?

Hallo,
ein weiterer Dankeschön-Code-Schnipsel.

Vorwort:
WS2812? was ist das? Kurze Internetrecherche, cool, die muss ich haben!
One wire..., perfekt, unbegrenzte Lichteffekte mit einem Tiny45 und 
genug Pins frei für Schalter und I2C/SPI... Das nächste Update für das 
Puppenhaus meiner Tochter und dann schauen wir mal weiter was das 
Ambiente im Bad betrifft, Pixelmatrix fürs Kinderzimmer?... Was alles 
geht-  perfekt!
.ba. dein Freund und Helfer, schnell ein paar m geordert und noch 
während ich drauf warte: Studium des Datenblattes, man könnte ja schon 
mal ein Testprogramm schreiben... 800kHz Takt... Oha, nur 10 Takte pro 
Bit, das wird auch in Assembler spannend, irgendwie müssen die Bits ja 
erstmal entstehen wenn ich die Kette nicht immer einfarbig leuchten 
lassen will. Nochmals recherchiert, kann ja nicht sein, dass man der 
einzige mit dem Problem ist...
Ernüchterung, fast alle verwenden 3 Byte Ram pro Pixel und pfeifen die 
dann in einem Burst raus... So hatte ich mir das nicht gedacht, von 
wegen attiny45, gut 80 LEDs und dann ist Schluss... Sch.... Doch nen 
großen Mega für ne "popelige" Lichterkette an EINEM PIN nehmen, 
Absolutes NOGO...

Lösung:
Glücklicherweise fand ich dann dies hier:
https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/

Danke Josh!

Den Quelltext habe ich nicht gelesen (kann sein, dass meiner daher fast 
identisch ist), aber anhand der Idee und der guten Beschreibung kam für 
den Tiny folgendes dabei raus (hier schon in der neuen Variante für die 
RGBW-Leds (32 statt 24bit mit separatem Weiß):

F_CPU 16000000 !
Ausgabepin ist PB1, aber Achtung muss in den Inline-Assembler-Blöcken 
angepasst werden (sbi/cbi)!
Datenrichtung vor Funktionsaufruf setzen!
Für andere Frequenzen die "NOPS" im inline Assembler anpassen aber auch 
hier Vorsicht: trotz vermeintlich besserer Lesbarkeit keine einzelnen 
Zeilen verwenden, folgendes kann böse ins Auge gehen:
1
asm volatile("nop");
2
asm volatile("nop");
3
asm volatile("nop");
4
asm volatile("nop");
5
asm volatile("nop");
6
asm volatile("nop");

Wenn dies mehrfach im Programm vorkommt macht der Compiler, insbesondere 
bei -Os gerne mal einen Block kürzer und springt dann in den nächsten 
ein, dann ist das exakte Timing natürlich zum Teufel oder zumindest 
einen Takt länger.

Hier der Code-Schnipsel, viel Spaß damit (die 5µs nicht ausreizen, der 
Einsprung in die Funktion benötigt ja auch Zeit), vorzugsweise einen 
kleinen Ring-Puffer von 16 oder 32 Pixeln anlegen der asynchron gefüllt 
wird und an diese Funktion entleert wird:
1
/** Function for One-Wire Neo-Pixels RGBW (GRBW) (here 16Mhz)**/
2
/** Function has to be called within 5µs from last call, otherwise data is latched**/
3
/** An array of 4 bytes would be more performant, but this is more human readable **/
4
void LED_sendNeoPixelRGBW(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
5
{
6
  uint8_t register byte;    //The Data to transmit
7
  for(uint8_t color=0; color<4; color++)
8
  {
9
    switch(color)  //If you need every tick, change r g b w to an array (GRBW!)
10
    {
11
      case 0:
12
        byte=g;
13
      break;
14
      case 1:
15
        byte=r;
16
      break;
17
      case 2:
18
        byte=b;
19
      break;
20
      case 3:
21
        byte=w;
22
      break;
23
    }
24
    for(uint8_t bit=0; bit<8; bit++)
25
    {
26
      //Check MSB of byte to transfer
27
      if((byte&0b10000000)==0)  //Bit is zero  //400ns +/- 150ns 6Ticks @16Mhz /3 Ticks @8Mhz
28
      {
29
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE)  //This is the critical section and timing is very important
30
        {
31
          asm volatile("/*T0H 400+/-150ns*/" "\n\t"
32
                "sbi 0x18,1    /*Set Pin High*/" "\n\t"         //(PORTB|=(1<<PB1);
33
                "nop       /*125  @16Mhz / 250ns@8Mhz*/" "\n\t"
34
                "nop       /*187,5@16Mhz / 375ns@8Mhz*/" "\n\t"
35
                "nop       /*250  @16Mhz / 500ns@8Mhz*/" "\n\t"
36
                "nop       /*312,5@16Mhz / 625ns@8Mhz*/" "\n\t"
37
                 "cbi 0x18,1    /*Set Pin Low*/");             //PORTB&=(uint8_t)(~(1<<PB1));
38
        }
39
      }              
40
      else            //Bit is one  //850 +/- 150ns
41
      {
42
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE)  //Not so critical section, but we want fo follow timing from datasheet
43
        {
44
          asm volatile("/*T1H 850+/-150ns*/" "\n\t"
45
                "sbi 0x18,1    /*Set Pin High*/" "\n\t"         //(PORTB|=(1<<PB1);
46
                "nop       /*125  @16Mhz / 250ns@8Mhz*/" "\n\t"
47
                "nop       /*187,5@16Mhz / 375ns@8Mhz*/" "\n\t"
48
                "nop       /*250  @16Mhz / 500ns@8Mhz*/" "\n\t"
49
                "nop       /*312,5@16Mhz / 625ns@8Mhz*/" "\n\t"
50
                "nop       /*375  @16Mhz / 750ns@8Mhz*/" "\n\t"
51
                "nop       /*437,5@16Mhz / 875ns@8Mhz*/" "\n\t"
52
                "nop       /*500  @16Mhz / 1000ns@8Mhz*/" "\n\t"
53
                "nop       /*562,5@16Mhz / 1125ns@8Mhz*/" "\n\t"
54
                "nop       /*625  @16Mhz / 1250ns@8Mhz*/" "\n\t"
55
                "nop       /*687,5@16Mhz / 1375ns@8Mhz*/" "\n\t"
56
                "nop       /*750  @16Mhz / 1500ns@8Mhz*/" "\n\t"
57
                 "cbi 0x18,1    /*Set Pin Low*/");             //PORTB&=(uint8_t)(~(1<<PB1));
58
        }
59
      }  
60
      /*Uncritical section, just make sure that data line lowtime is long enough, here you can fire interrupts, 
61
      but please come back within 5µs-"nops" */
62
      byte<<=1; //Shift left for new MSB
63
      asm volatile("/*T0_1L 850+/-150ns*/" "\n\t"
64
                "nop       /* 62,5@16Mhz / 125ns@8Mhz*/" "\n\t"
65
                "nop       /*125  @16Mhz / 250ns@8Mhz*/" "\n\t"
66
                "nop       /*187,5@16Mhz / 375ns@8Mhz*/" "\n\t"
67
                "nop       /*250  @16Mhz / 500ns@8Mhz*/" "\n\t"
68
                "nop       /*312,5@16Mhz / 625ns@8Mhz*/" "\n\t"
69
                "nop       /*375  @16Mhz / 750ns@8Mhz*/" "\n\t"
70
                "nop       /*437,5@16Mhz / 875ns@8Mhz*/" "\n\t"
71
                "nop       /*500  @16Mhz / 1000ns@8Mhz*/" "\n\t"
72
                "nop       /*562,5@16Mhz / 1125ns@8Mhz*/" "\n\t");
73
    }
74
  }
75
}

: Verschoben durch User
von stm32?! (Gast)


Lesenswert?

Bei allem Respekt davor dass du das in ASM passend zurechtfrickelst: 
Warum tut man sich das in Zeiten von stm32 fuern Euro vom Chinesen an?

von Dominik (Gast)


Lesenswert?

stm32?! schrieb:
> Bei allem Respekt davor dass du das in ASM passend zurechtfrickelst:
> Warum tut man sich das in Zeiten von stm32 fuern Euro vom Chinesen an?

Zum einen tatsächlich weil ich noch bestimmt 50 jungfräuliche Attiny45 
in der µC-Box habe und zum anderen weil ich für ein LED-Blinklicht schon 
nen Atmega für mit "Kanonen auf Spatzen schießen" halte.

Aber in der Tat macht ein Cortex Sinn wenn man anfängt mit den Pixeln 
ganze Bildschirme zu bauen auf denen auch was "Richtiges" angezeigt 
werden soll. Obiger Quelltext (quasi als Pseudocode) fällt natürlich bei 
nahezu unbegrenzten Ressourcen nicht mehr ins Gewicht dann löst man das 
sicher mit DMA.

Für das Ambiente im Puppenhaus tut´s aber auch ein kleiner 8 bitter ;-)

Gruß Dominik

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.