NeoPixel - Urtavla
ATmega328P.pdf ( Register summary on p 428 )

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).
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 ä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. 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.
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.)
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.
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[].
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)
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).
neopixel_seconds.txt
(for Arduino store code as: neopixel_seconds\neopixel_seconds.ino)( Eller kanske så här ?
neopixel_clock.txt )
William Sandqvist willsandqvist@gmail.com