Bitars banka steg för steg: Arduino kontroll av WS2811, WS2812 och WS2812B RGB lysdioder (5 / 5 steg)
Steg 5: Bitbanging en puls våg på en ATMega328p mikrokontroller
Eftersom Arduino Uno R3 utveckling Styrelsen upprätthåller en 16MHz externa klocksignalen på den inbyggda ATMega328p, utför mikrokontroller en 1 klockcykel instruktion i exakt 62.5ns (1/16 MHz = 62.5ns). Eftersom vi kan ta reda på hur många klockcykler varje anvisning äger, kan vi kontrollera exakt hur många instruktioner måste vi skapa vår signal.
Som vi såg tidigare, för att överföra en 1 till WS281X chip måste vi sända en signal om att stannar på en (hög) maxvärdet för 0.8μs, och sedan stannar vid ett minimivärde (låg) för 0.45μs. Därför vill vi skriva en lista med instruktioner som:
-Ställ in digital stift till hög
-Vänta 0.8μs
-Anger digital pin till låg
-Väntar 0.45μs
I assembler, kan detta uppnås genom följande kod:
ASM flyktiga)
Instruktion klockan Beskrivning PhaseLite överförs
"sbi %0, %1\n\t" / / 2 PIN-hög (T = 2)
"rjmp. + 0\n\t" / / 2 nop nop (T = 4)
"rjmp. + 0\n\t" / / 2 nop nop (T = 6)
"rjmp. + 0\n\t" / / 2 nop nop (T = 8)
"rjmp. + 0\n\t" / / 2 nop nop (T = 10)
"rjmp. + 0\n\t" / / 2 nop nop (T = 12)
"nop\n\t" / / 1 nop (T = 13)
"cbi %0, %1\n\t" / / 2 PIN låg (T = 15)
"rjmp. + 0\n\t" / / 2 nop nop (T = 17)
"rjmp. + 0\n\t" / / 2 nop nop (T = 19)
"nop\n\t" / / 1 nop (T = 20) 1
::
Ingående operander
"I" (_SFR_IO_ADDR(PORT)), //%0
"I" (PORT_PIN) //%1
);
Instruktion
Den första kolumnen innehåller instruktionen församlingen följt av en radmatning och fliken tecken, som gör den slutliga assembler notering genererade av kompilatorn mer läsbar.
Klockan
Den andra kolumnen visar antalet klockcykler varje anvisning äger. För denna uppsättning enkla instruktioner finns endast ett möjligt värde, vi får se senare hur några instruktioner (t.ex. villkorlig) kan ha 1, 2 eller 3 möjliga värden. Kom ihåg att varje klockcykel på 16 MHz Arduino Uno tar 62.5ns.
Beskrivning
Den tredje kolumnen visar en mycket kort beskrivning av vad varje operation gör.
Fas
Använda termen lite löst, använder vi den som anger den kumulativa summan av klockcykler instruktionerna som utförts hittills har vidtagit.
För att skicka ett enda 255 värde — 11111111 i binär — till den WS281X vi måste upprepa denna uppsättning instruktioner 8 gånger. Dessutom, om vi sätter in en 50μs (eller större) paus mellan överföringar av 8-bitars sekvensen, WS281X spärrarna överförda data till dess utgång register. När data är låst, aktiverar den första lysdioden (grön) av WS281X till en maximal ljusstyrkenivå. Arduino skiss inuti bitbang_255.zip visar denna operation.
För att skicka en 0 måste vi ändra den kod som producerar en 1 genom att minska den tid under vilken signalen har en hög (maximum) värde och öka den tid under vilken signalen är låg (minimum). Dessutom bör vi notera att värdena till varje LED alltid bör anges med 8 bitar. Till exempel, om vi ville skicka ett värde av 105 — 1101001 i binär-skulle vi behöva skicka de 8 bitarna 01101001 inklusive den ledande 0. Den kod som producerar en 0 ser ut:
ASM flyktiga)
Instruktion klockan Beskrivning PhaseLite överförs
"sbi %0, %1\n\t" / / 2 PIN-hög (T = 2)
"rjmp. + 0\n\t" / / 2 nop nop (T = 4)
"rjmp. + 0\n\t" / / 2 nop nop (T = 6)
"cbi %0, %1\n\t" / / 2 PIN låg (T = 8)
"rjmp. + 0\n\t" / / 2 nop nop (T = 10)
"rjmp. + 0\n\t" / / 2 nop nop (T = 12)
"rjmp. + 0\n\t" / / 2 nop nop (T = 14)
"rjmp. + 0\n\t" / / 2 nop nop (T = 16)
"rjmp. + 0\n\t" / / 2 nop nop (T = 18)
"rjmp. + 0\n\t" / / 2 nop nop (T = 20) 0
::
Ingående operander
"I" (_SFR_IO_ADDR(PORT)), //%0
"I" (PORT_PIN) //%1
);
Vi kan använda Arduino skiss inuti bitbang_105.zip för att generera signalen vars bild kan ses på skärmdumpen oscilloskop som är kopplade till detta steg.
Nu, för WS281X ska ha vitaktig färg vi vill ha, måste vi skicka inte en utan tre 255 värden – i vilket fall vår signal består av 24 — innan väntar på 50μs uppgifter till spärren. Vi kunde göra detta genom att kopiera-klistra elva monteringsanvisningarna ovan 23 gånger (du kan ge det ett försök ändra bitbang_255.ino skissen). Men koden skulle vara opraktiskt för att skicka värden till mer än en WS281X marker. En bättre lösning vore att skriva en loop som skulle gå igenom 8-bitars värden tills alla tre av dem har skickats.
Skissen inuti bitbang_whitish.zip innehåller en tydlig beskrivning av de åtgärder som vidtas för att uppnå det önskade resultatet. Huvudavsnittet, skrivna i församlingen logik ovan, ser ut som följer:
ASM flyktiga)
Instruktion klockan Beskrivning fas
"nextbit:\n\t" / / - etikett (T = 0)
"sbi %0, %1\n\t" / / 2 signal hög (T = 2)
"sbrc %4, 7\n\t" / / 1-2 om MSB (T =?)
"mov %6, %3\n\t" / / 0-1 tmp ska ställa signal hög (T = 4)
"dec %5\n\t" / / 1 minskning bitcount (T = 5)
"nop\n\t" / / 1 nop (tomgång 1 klockcykel) (T = 6)
"st % a2, %6\n\t" / / 2 set PORT till tmp (T = 8)
"mov %6, %7\n\t" / / 1 reset tmp för att låg (standard) (T = 9)
"breq nextbyte\n\t" / / 1-2 if bitcount == 0 -> nextbyte (T =?)
"rol %4\n\t" / / 1 skifta MSB Vänsterriktad (T = 11)
"rjmp. + 0\n\t" / / 2 nop nop (T = 13)
"cbi %0, %1\n\t" / / 2 signal låg (T = 15)
"rjmp. + 0\n\t" / / 2 nop nop (T = 17)
"nop\n\t" / / 1 nop (T = 18)
"rjmp nextbit\n\t" / / 2 bitcount! = 0 -> nextbit (T = 20)
"nextbyte:\n\t" / / - etikett -
"ldi %5, 8\n\t" / / 1 reset bitcount (T = 11)
"ld %4, %a8+\n\t" / / 2 val = * p ++ (T = 13)
"cbi %0, %1\n\t" / / 2 signal låg (T = 15)
"rjmp. + 0\n\t" / / 2 nop nop (T = 17)
"nop\n\t" / / 1 nop (T = 18)
"dec %9\n\t" / / 1 minskning bytecount (T = 19)
"brne nextbit\n\t" / / 2 om bytecount! = 0 -> nextbit (T = 20)
::
);
Det bästa sättet att förstå av detta avsnitt är att överväga olika fall scenarier och följ församlingen koden rad för rad. Vi vet till exempel att skicka ett värde 255, måste vi skicka 8 bitar med en timing som motsvarar en 1. Med andra ord, den Digital Pin ansluten till WS281X bör förbli hög för 13 cykler (0.8125μs) och låg för 7 (0.4375μs). Koden ovan att uppnå detta? Låt oss se vad som händer när vi börjar överföra:
ASM flyktiga)
"nextbit:\n\t" / / Detta är bara en etikett för att styra hoppen nedan.
"sbi %0, %1\n\t" / / signalen är inställd på hög, instruktion använder 2 cykler.
"sbrc %4, 7\n\t" / / sant. Skicka 255 innebär nuvarande MSB är 'set' (= 1).
"mov %6, %3\n\t" / / Detta är utförat. "tmp" är satt till hög.
"dec %5\n\t" / / Bit överförs, minska bitars räknare.
"nop\n\t" / / behöver på tomgång för att komma till de 13 klockcykler.
"st % a2, %6\n\t" / / skriva "tmp" värdet till hamnen (pin fortfarande hög).
"mov %6, %7\n\t" / / Set "tmp" Low för nästa passera genom öglan.
"breq nextbyte\n\t" / / falskt. Bitars räknare är inte 0, använda 1 cykel och fortsätta.
"rol %4\n\t" / / skifta bytevärdet MSB Vänsterriktad.
"rjmp. + 0\n\t" / / Idle för 2 klockcykler. Etappen nådde T = 13.
"cbi %0, %1\n\t" / / Ställ in signal till låg.
"rjmp. + 0\n\t" / / Idle för 2 klockcykler.
"nop\n\t" / / Idle för 1 klockcykel.
"rjmp nextbit\n\t" / / bitars räknare var inte 0 så hoppa till nästa bit. T = 20.
);
Så instruktionerna som faktiskt få utförat generera en signal på data stift som är 13 cykler hög (0.8125μs) och 7 LOW (0.4375μs), således skicka lite med värdet 1 till WS281X. Om vi fortsätter att studera vad koden gör när resten av bitarna skickas, och vad den gör när värden än 255 används, vi ska få en djupare förståelse av denna särskilt genomförandet av bitbanging.
Personligen hoppas jag att du hittar denna tutorial användbart för att komma igång med bitbanging egna meddelande protokoll närhelst det är nödvändigt!