Sterowanie diodami przy pomocy UART
Diody sterowane są impulsami o okresie 1,25 µs ±600 ns (rysunek 6), co daje przepływność na poziomie 800 kb/s.
Transmisja każdego bitu jest podzielona na trzy odcinki czasowe, z których pierwszy zawsze jest stanem wysokim, drugi decyduje o wartości przesyłanej informacji a trzeci zawsze ma stan niski (rysunek 7).
Zatem przepływność UART powinna wynosić 2,4 Mb/s (1,6...4,6 Mb). Ramka transmisyjna UART 7N1 składa się z dziewięciu odcinków czasowych – bit startu, 7 bitów danych i 1 bit stopu. W jednym bajcie można przesłać 3 bity sterujące diodą typu WS2812. Bit startu i stopu ma z góry ustaloną wartość. Niestety bit startu to zero, a bit stopu jeden. Po zanegowaniu sygnału UART otrzymujemy sygnał wymagany przez sterownik diod LED typu WS2812. Aby wysłać trzy bity o wartości 0 poprzez UART należy wpisać wartość $26 (binarnie 100110). Działanie tego rozwiązania zostało zobrazowane przy pomocy tabeli 1.
Na pomarańczowo zaznaczono bity startu i stopu, na żółto stałą wartość jaka musi być wysyłana poprzez UART, litery A, B, C ustalają wartości bitów 1, 2, 3 przesyłanych do diody LED.
Jaką maksymalną przepływność można uzyskać przy pomocy UART w AVR? Według noty katalogowej: Fosc/8·UBRR+1. Dla 20 MHz i UBRR=1 otrzymamy 1,25 Mb/s czyli dwa razy za wolno. Okazało się, że wpisując zero dla UBRR otrzymujemy przepływność 2,5 Mb/s (rysunek 8).
Transmisję da się zrealizować na przerwaniach nawet bez wstawki w asemblerze, co pokazuje listing 1.
SIGNAL(USART1_TX_vect){
if (pozScr){
UDR1 = *(scr+pozScr++);
pozScr &=SCRLEN-1;
}
if (pozScr >= SCRLEN) FL_TransEnd=true;
}
Wynik działania programu obsługi przerwania został pokazany na rysunku 9. Czas obsługi przerwania wynosi 4,44 µs więc na pewno nie będzie zinterpretowany przez diody jako reset. Po optymalizacji kodu, której wynik pokazano na listingu 2 (niepotrzebne operacje zakomentowano) czas przerwania zmniejszył się do 4,08 µs. Łączna oszczędność 7 rozkazów/cykli ma dość duże znaczenie z racji bardzo częstego wywoływania przerwania.
ISR(USART1_TX_vect, ISR_NAKED){
asm volatile (
// usunięta operacja na nieużywanym R1 i R0
// "push r1 \n\t"
// "push r0 \n\t"
// "in r0, 0x3f \n\t" // SREG
// "push r0 \n\t"
// "eor r1, r1 \n\t"
"push r18 \n\t"
//SREG na stos przy użyciu używanego później R18 (+3)
"in r18, 0x3f \n\t" // SREG
"push r18 \n\t"
//Dalej tak jak zrobił kopilator
"push r24 \n\t"
"push r25 \n\t"
"push r30 \n\t"
"push r31 \n\t"
);
//I dotychczasowy kod:
if ( pozScr ){
UDR1 = *(scr+pozScr++);
pozScr &=SCRLEN-1;
}
if (pozScr >= SCRLEN) FL_TransEnd=true;
asm volatile (
"pop r31 \n\t"
"pop r30 \n\t"
"pop r25 \n\t"
"pop r24 \n\t"
//Dodane SREG przy użyciu R18
"pop r18 \n\t"
"out 0x3f, r18\n\t"
//Dalej normalnie R18
"pop r18 \n\t"
//Usunięte operacje na R0 i R1
// "pop r0 \n\t"
// "out 0x3f, r0\n\t"
// "pop r0 \n\t"
// "pop r1 \n\t"
);
reti();
}
Efekt działania zoptymalizowanej procedury został pokazany na rysunku 10. Niebieski przebieg obrazuje działanie pętli głównej programu, która po skompilowaniu wykonuje się w czterech cyklach maszynowych (listing 3).
loop:
wdg();
PORTA |= _BV(PA3);
PORTA &= ~_BV(PA3);
Jak łatwo zauważyć pomiędzy przerwaniami program główny wykonuje około 16 rozkazów. Dość mało i niewiele da się zrobić w takim czasie. Jeśli jednak weźmiemy pod uwagę to, że transmisja nie musi trwać bez przerwy i pomiędzy paczkami wstawimy pauzę o długości 20 ms, co da odświeżanie zawartości LED na poziomie 50 Hz, to przy 60 diodach LED otrzymamy: 60·1,2 µs·24 bity ok. 1,7 ms, czyli zajętość CPU na poziomie 8,5%. Przykładowe obliczenia obciążenia CPU dla innych parametrów pokazano w tabeli 2.