NeoPixel - Urtavla

Back to start page

Urtavla med adresserbara lysdioder (NeoPixel)

 

Adresserbara lysdioder med tre färger och varierbar ljusstyrka är drömmen för alla oss som älskar blinkande lysdioder! De finns i många utföranden, som enkla lysdioder, som remsor på rullar med 32 stycken per meter, eller som fyrkanter (matriser 16x16).

Men vill man ha många pixels och snabb uppdatering så blir NeoPixel på en Arduino UNO en programmeringsteknisk utmaning (eller till och med en omöjlighet). Tyvärr finns det ingen inbyggd "NeoPixel-enhet" som skulle kunna avlasta processorn på ett liknande sätt som det finns SPI, I2C, UART -enheter.

En annan microcontroler som bättre passar till stora NeoPixel matriser är Raspberry Pico. Den har två processorkärnor, mycket minne, och en programmerbar IO-enhet. IO-enheten kan programeras att utföra all NeoPixel bit-timing och den kan då sköta uppdateringen av mycket stora displayer självständigt och direkt från microcontrolerns minne (med DMA).

 YouTube - Pico PIO

En ring med 12 NeoPixel som högst uppdateras per sekund är dock inget som ställer till problem för en Arduino UNO!

 

För att prova på NeoPixel tekniken, kan en "urtavla" tjäna som ett lämpligt exempel. Exakt tid kan man få med en RTC-modul och detta finns presenterat i samband med  TWI-enheten, så det behöver vi inte bry oss om här.

Ring med 12 NeoPixel Adafruit 12x5050 RGB

NeoPixel på rad

NeoPixel är adresserbara lysdioder med tre färger. De tre färgernas styrka ställs in med tre 8-bitarstal för grönt, rött, och blått. Kommunikationen sker bit för bit över en ledning, en "0" bit har en kort puls med hög nivå, och en "1" bit har en något längre puls med hög nivå. Efter ett uppehåll med låg nivå så kan nästa bit följa. Om uppehållet med låg nivå i stället blir mycket långt, så avslutas mottagningen, och lysdiodernas ljusstyrka ställs då in efter de mottagna ljusvärdena. Ljusstyrkan behålls tills den ställs in på nytt.
Flera NeoPixel kopplas i rad som ett skiftregister. När den första enheten tagit emot "sina" tre 8 bitarstal så kopierar den därefter över alla följande bitar till nästa enhet, som i sin tur gör på samma sätt till sin nästa.
För att adressera en valfri enhet i raden måste man således upprepa tidigare data till alla de enheter som ligger före den önskade enheten innan man kan uppdatera den önskade enheten. Det blir därför naturligt att i en följd uppdatera alla enheter.

För att förhindra att data förvrängs efter att ha kopierats av många enheter så försöker varje enhet att rätta till timingen på de bitar de sänder vidare.

NeoPixel bit timing information

/*       
   NeoPixel bit timing information. Processor f_clk = 16 MHz T = 62,5 ns.
         ______
   "0": |      |______________...     T0H (400 ns) 07 Cycles, T0L (900 ns) 15 Cycles
         ______________
   "1": |              |_________...  T1H (900 ns) 15 Cycles, T1L (600 ns) 10 Cycles

   TLD < 5000 ns accepted as interbit gap 
   TLL > 6000 ns, 6 us to reset and latch

           NeoPixel 12 chain                  
            _____1____     _____2____         ____12____
           |          |   |          |       |          |
    GND ---| GND  GND |---| GND  GND |- ... -| GND  GND |-
D06->-470R-| >DIN >DO |->-| >DIN >DO |- ... -| >DIN >DO |-
POWER +5V -| +5V  +5V |---| +5V  +5V |- ... -| +5V  +5V |-
C 1000uF   |__________|   |__________|       |__________|

*/

Den mest kritiska tiden i timing information ovan är T0H 400 ns. Arduino UNO Processorn har klockfrekvensen 16 MHz och processorn hinner bara med att utföra 7 instruktioner under denna korta tid.
Det är riskabelt att försöka programmera en så kort och exakt "tid" direkt med C språket. C-kompilatorn optimerar koden, den är fri att ta bort "onödiga" instruktioner, liksom att ändra på instruktionernas ordning. Det är därför inte möjligt att  garantera  neopixel-timingen med C instruktioner.

Det går absolut  inte  att använda Arduinomiljöns funktioner  digitalWrite(pin, HIGH)  och  digitalWrite(pin, LOW). De funktionerna tar minst dubbelt så lång tid som de kritiska fördröjningarna! (De utför en rimlighetskontroll av de medskickade parametrarna, och detta tar extra tid).

NeoPixel bit timingen bör därför skrivas i assembler - det vill säga direkt med processorns egna instruktioner.

Assemblerkod

Man kan skriva assemblerkod inuti C-program genom att använda direktivet  asm().
Vi behöver använda tre olika assemblerinstruktioner  sbi port,bit  (set bit),  cbi port,bit  (clear bit) och  nop  (no operation).
(Om interrupt används så behöver detta kunna "stängas av" under bit timingen, för att därefter åter kunna "slås på", det kräver i så fall ytterligare två assemblerinstruktioner,  cli,  sei ).

Assemblerprogrammet blir enkelt. Ingen av dessa operationer påverkar något av processorns register, det hade man annars behövt ta hänsyn till eftersom det skulle kunna få bieffekter på resten av C-programmet.

Assemblerfunktionen  sendBit( ) används för att sända en bit. (Här något förkortad, den innehåller egentligen totalt 42 nop!)

inline void sendBit( unsigned char bitVal ) 
{
    if (  bitVal != 0 )    // bit is 1 
      {
          asm volatile
            (
              "sbi %[port], %[bitnr] \n\t"   // output bit set port  
              "nop \n\t"                     // T1H +13 NOP total 900 ns
               - - - -    11 more nop's here
              "nop \n\t"
              "cbi %[port], %[bitnr] \n\t"   // output bit Clear port    
              "nop \n\t"                     // T1L +8 NOP total 600 ns
               - - - -    6 more nop's here
              "nop \n\t"
              : : 
              [port]    "I" (_SFR_IO_ADDR(NEOPIXEL_PORT )),
              [bitnr]   "I" (NEOPIXEL_pin)
           );                                  
      }

    else                  // bit is 0
      {
         asm volatile 
           (
              "sbi %[port], %[bitnr] \n\t"   // output bit set port   
              "nop \n\t"                     // T0H +4 NOP total 400 ns - this is critical
              - - - -    2 more nop's here
              "nop \n\t"
              "cbi %[port], %[bitnr] \n\t"   // output bit clear port     
              "nop \n\t"                     // T0L +13 NOP total 900 ns
              - - - -    11 more nop's here
              "nop \n\t"
              : :
              [port]    "I" (_SFR_IO_ADDR(NEOPIXEL_PORT )),
              [bitnr]   "I" (NEOPIXEL_pin)
           ); 
      }
}  

(Information om inline assembler för AVR finns här Inline Assembler.)

Ett program för en urtavla

En array med pixeldata

En array  ClockFace[ PIXELS*3 ]  används för att lagra data för alla pixels med tre Bytes per pixel. När någon pixel behöver uppdateras är det rimligt att skicka ut data för alla pixels, i en följd.

En färgpalett

NeopiPixel's tre färger kan blandas till otaliga nyanser. Det finns mycket information om detta på webben, men för att här förenkla användningen så nöjer vi oss med att definiera en enkel palett med 12 färger i programmet.
Färgvärdena kan som högsta värde ha 255, men för att begränsa strömförbrukningen så nöjer vi oss här med högst 25.

 GREEN 0   CHARTREUSE 1   YELLOW 2   ORANGE 3   RED 4   ROSE 5 
 MAGENTA 6   VIOLET 7   BLUE 8   AZURE 9   CYAN 10   SPRING 11 

Med funktionen  setPixelPalettColor( )  kan man lägga in färginformation från paletten i arrayen  ClockFace[].

Sända pixeldata

Funktionen  sendPixel()  sänder data för ett pixel ur arrayen  ClockFace[], den använder funktionen  sendBit( )  för den tidskritiska neopixel-timingen.
För att sända alla pixeldata:

// send all pixels in the "ClockFace" array
for(i = 0; i < 12 ; i++ ) sendPixel( ClockFace + i*3 );
_delay_us (6);  // long delay to trigger all pixels to latch (reset timeout)

Urtavel-programmet

Ett urtavelprogram kan använda olika färger för timme, minut och sekund. Var femte sekund uppdaterar man ringen med de 12 NeoPixelpixlarna. Exakt tid kan hämtas från en RTC-modul. Se i så fall  TWI-enheten.

Exempelprogrammet uppdaterar endast ringen genom att tända en ny pixel var femte sekund (detta lämnar utrymme för mer eget experimenterande).

( Eller kanske så här ?  neopixel_clock.txt )


Back to start page

 


William Sandqvist    willsandqvist@gmail.com