Autor: tmf
Redakcja: Dondu
Artykuł jest częścią cyklu: Wstęp do mikrokontrolerów XMEGA Atmel'a.
W pierwszej części mini cyklu o diodach WS2812B udało nam się zmusić do współpracy te diody, chociaż sposób w jaki to uczyniliśmy był niezbyt elegancki.
W niniejszej części poznamy w jaki sposób osiągnąć to samo, lecz z wykorzystaniem dostępnych układów peryferyjnych mikrokontrolera.
Wykorzystujemy sprzęt
Aby wyeliminować wcześniej wspomniane wady musimy zmienić podejście do sposobu generowania przebiegu sterującego LEDami. Warto zauważyć, że dane przesyłane są szeregowo, z wykorzystaniem modulacji szerokości impulsu. To ostatnie stwierdzenie nasuwa nam zapewne na myśl próbę wykorzystania PWM … Można, ale niekoniecznie będzie to najwygodniejsze.Skupmy się więc na transmisji szeregowej… Możemy ją realizować przy pomocy różnych interfejsów, ale praktycznie w każdym mikrokontrolerze mamy do dyspozycji interfejsy SPI oraz UART. Z różnych powodów, o których za chwilę, wykorzystamy interfejs UART, co jest wygodniejsze w przypadku XMEGA.
Jeśli wykorzystujemy zwykłe AVR-y wygodniejsze jest użycie interfejsu SPI (zainteresowani znajdą odpowiednie przykłady w II wydaniu książki „Język C dla mikrokontrolerów AVR. Od prostych do zaawansowanych aplikacji”).
Przy okazji wspomnę, że w nowym wydaniu uwzględniono zmiany, jakie zaszły w świecie AVR w ciągu ostatnich 3 lat, czyli np. zmianę AVR Studio na Atmel Studio. Znajdziesz tam także podstawy posługiwania się nowym IDE, tworzenia oraz debugowania w nim projektów. Dowiesz się więcej o zmianach w kompilatorze avr-gcc, a szczególnie o tzw. nazwanych przestrzeniach adresowych, które ułatwiły dostęp do danych umieszczonych w pamięci FLASH mikrokontrolera. Odkryjesz, jak wykorzystać piloty telewizyjne do sterowania pisanym programem i jak połączyć program w języku C z asemblerem. Miłośnicy LED-ów zrozumieją, jak obsługiwać wielokolorowe matryce z wykorzystaniem peryferii dostępnych w AVR oraz jak sprzętowo realizować wymianę danych z popularnymi diodami ze sterownikiem WS2812B.
Spójrzmy na wygląd typowej ramki UART:
Ramka UART |
Jak widzimy, składa się ona z bitu startu (niskiego poziomu logicznego trwającego przez cały czas trwania bitu), pola składającego się z 5-9 bitów danych oraz co najmniej jednego bitu stopu. Dodatkowo w ramce może występować dodatkowy bit stopu oraz parzystości, ale ponieważ nie możemy nimi bezpośrednio sterować pominiemy je.
Teraz, jeśli spojrzymy na sposób transmisji danych do WS2812B zauważymy, że czas trwania bitu możemy podzielić na trzy równe okresy o czasie trwania około 400 ns każdy:
Podział czasu trwania bitu |
Jeśli w czasie trwania pierwszych dwóch okresów mamy stan wysoki to odbierany jest bit o wartości 1, jeśli pierwszy okres ma stan wysoki, a dwa kolejne niski to odbierany jest bit o wartości 0. Widzimy, że wartość odbieranego bitu determinuje okres P2, w czasie trwania okresu P1 stan zawsze jest wysoki, a w czasie trwania P3 stan zawsze jest niski.
Teraz mamy już z górki – każdy bit USART może określać nam stan logiczny panujący podczas wyznaczanego przez niego okresu, wystarczy, że czas trwania nadawania bitu równy jest naszemu okresowi, tzn. ok. 400 ns. I tak, jeśli nadamy ciąg 0b100 to odebrany zostanie bit o wartości 0, a jeśli 0b110 to odebrany zostanie bit o wartości 1.
Proste, prawda? Proste, ale nie do końca. Korzystając z UART napotkamy na poważny problem – każda transmisja rozpoczyna się nadaniem bitu startu o wartości 0 i kończy bitem stopu o wartości 1. Stanu tych bitów nie możemy zmieniać – są one generowane sprzętowo. I tu pojawia się problem, gdyż będą one nam psuły transmisję.
Czytelnicy moich książek wiedzą, że nie ma sytuacji beznadziejnych i zwykle znajdujemy jakieś rozwiązanie. Przedstawione tu rozwiązanie jest w dodatku proste – wystarczy zanegować wyjście TxD UART – wtedy bit startu będzie równy nie 0, a jeden. I to jest bardzo dobra wiadomość – dlaczego? Gdyż każdy nadawany bit dla WS2812B rozpoczyna się od stanu wysokiego trwającego co najmniej jeden okres (P1 – 400 ns).
Co prawda, jeśli dokonamy inwersji sygnału to bit stopu zmieni się z 1 na 0, ale to nam w niczym nie przeszkadza, a nawet jak się za chwilę okaże - pomaga. Tylko jak w prosty sposób odwrócić polaryzację wyjścia TxD? Możemy zastosować zewnętrzny inwerter, lecz w XMEGA sytuacja jest prostsza – wystarczy ustawić bit INVEN danego pinu IO, w efekcie pojawiający się na nim sygnał będzie miał odwróconą polaryzację.
M.in. ta możliwość dostępna w XMEGA powoduje właśnie, że w tej rodzinie do komunikacji z WS2812B lepiej wykorzystać UART, z kolei w pozostałych AVR-ach, brak możliwości inwersji stanu wyjścia TxD powoduje, że pozostaje nam interfejs SPI, chyba, że zdecydujemy się na użycie zewnętrznego inwertera.
Po inwersji ramka UART wygląda następująco:
Inwersja ramki UART |
Warto zwrócić uwagę na fakt, że po inwersji bit stopu ma wartość 0 a nie 1. Daje to nową możliwość – otóż jak pamiętamy w okresie P3 stan magistrali musi być niski, ten niski stan wprowadzany przez hardware UART jako bit stopu możemy więc wykorzystać jako element trzeciego nadawanego bitu danych. Dzięki temu możemy skrócić pole danych ramki UART do 7 bitów, dzięki czemu nieznacznie przyśpieszymy transmisję danych do układu WS2812B.
Teraz, tak jak to pokazałem na poniższym rysunku, zmieniając stan wysyłanych bitów możemy generować przebieg dostosowany do WS2812B:
Mapowanie bitów w ramkę UART |
Widzimy, że w jednej ramce UART możemy zmieścić informację o 3 bitach przesyłanych do diody – to co odbierze dioda określają bity B0, B3 i B6 ramki UART, stan pozostałych bitów jest stały i służy tylko do wygenerowania odpowiedniego przebiegu. Jak widzimy z wady, jaką było dodawanie dodatkowych bitów do ramki UART, uczyniliśmy zaletę, jaką jest możliwość transmisji 3 bitów danych dzięki dostępnym 9-ciu slotom czasowym.
Co prawda wydłużając pole danych ramki UART do 9 bitów moglibyśmy nadać w tej samej transmisji jeszcze jeden, czwarty, bit do układu WS2812B, ale operowanie 9-bitowymi polami danych jest niewygodne i nie będziemy z tego korzystać.
Implementacja pomysłu
Wiemy już jak przetransmitować dane, czas na praktyczną realizację. W tym celu wykorzystamy port USARTC0 mikrokontrolera XMEGA, którego wyjście TxD dostępne jest na pinie PC3. Podłączymy więc to wyjście z wejściem DI pierwszej diody.Tym razem zaczniemy od funkcji inicjalizacyjnej, ponieważ musimy odpowiednio zainicjować USART, będzie ona nieco bardziej skomplikowana niż inicjalizacja IO z pierwszego przykładu:
void USART_init() { PORTC_OUTSET=PIN3_bm; PORTC_DIRSET=PIN3_bm; //Pin TxD musi być wyjściem PORTC_PIN3CTRL=PORT_INVEN_bm; //Zaneguj wyjście - domyślnie będzie w stanie niskim USARTC0.CTRLC=USART_CHSIZE_7BIT_gc; //Ramka 7 bitów, bez parzystości, 1 bit stopu //Szybkość transmisji 2 Mbps, jeden bit trwa ok. 500 ns - pamiętaj, aby nie korzystać z generatora frakcyjnego USARTC0.BAUDCTRLA=1; USARTC0.BAUDCTRLB=0; USARTC0.CTRLB=USART_TXEN_bm | USART_CLK2X_bm; //Włącz nadajnik USART }
Inicjalizacja UART wymaga, aby pin TxD (a więc w naszym przykładzie PC3) był ustawiony jako wyjście w stanie 1, co zapewniają nam dwie pierwsze instrukcje. Ponieważ wyjście TxD chcemy zanegować, korzystamy z rejestru kontrolnego pinu (PORTC_PIN3CTRL) i ustawiamy znajdujący się tam bit INVEN. Dalej konfigurujemy USART wybierając 7-bitowe pole danych, bez parzystości, z jednym bitem stopu. Na koniec musimy jeszcze zapewnić, aby czas trwania bitu wynosił około 400 ns.
Dzięki czemu czas trwania trzech bitów wyniesie około 1200 ns, a więc tyle, ile wynosi okres transmisji bitu do układu WS2812B. Tu jednak napotkamy na pewien kłopot. Okres 400 ns odpowiada szybkości transmisji równej 2,5 Mbps. Problem w tym, że dla taktowania MCU równego 32 MHz nijak nie da się uzyskać transmisji USART o szybkości 2,5 Mbps, gdyż musielibyśmy podzielić zegar taktujący przez wartość niebędącą liczbą naturalną!
Tu znawcy XMEGA zapewne się ucieszą – pamiętacie bowiem, że w XMEGA UART wyposażony jest w tzw. generator frakcyjny umożliwiający uzyskanie niecałkowitych stopni podziału, w związku z tym bez większych problemów uzyskany owe 2,5 Mbps.
Zanim jednak się ucieszycie – dobra rada – zapomnijcie o wykorzystaniu generatora frakcyjnego. Dlaczego? Aby odpowiedzieć na to pytanie musielibyśmy się zagłębić w sposób jego działania – zainteresowanych odsyłam do noty procesora lub moich książek.
Stąd też, pozostaje nam wykorzystanie tylko preskalera UART. A więc wracamy do problemu polegającego na niemożności uzyskania szybkości UART równej 2,5 Mbps przy taktowaniu 32 MHz. Możemy więc albo zmienić taktowanie, albo… ponownie skorzystać z elastyczności układu WS2812B.
Ponieważ toleruje on, w rozsądnych granicach, zmiany czasów, wybierzemy szybkość transmisji równą 2 Mbps, co da nam czas trwania okresu równy 500 ns, a całego bitu 1500 ns, co mieści się jeszcze w specyfikacji układu WS2812B. Dla tej szybkości przy FCPU równym 32 MHz musimy podzielić zegar przez 16, co umożliwia wpisanie do BAUDCTRL wartości 1 i ustawienie bitu CLK2X.
Na koniec, ponieważ interesuje nas tylko nadawanie, odblokowany zostaje nadajnik UART – pin związany z odbiornikiem, możemy wykorzystać do innych celów. Od tego momentu, jeśli będziemy chcieli coś wysłać do diody, to możemy do tego celu wykorzystać UART:
void USART_putchar(USART_t * const usart, char ch) { while(!(usart->STATUS & USART_DREIF_bm)); USARTC0.DATA=ch; }
Powyższa funkcja sprawdza, czy w buforze nadajnika jest wolne miejsce i jeśli tak to wpisuje do niego dane do wysłania. Musimy tylko pamiętać o jednym – ponieważ do kodowania jednego bitu odbieranego przez WS2812B potrzebujemy aż 3 bity UART, wysyłane dane musimy odpowiednio zakodować. W efekcie, aby wysłać 24-bity określające kolor, musimy przesłać przez UART aż 8 bajtów. Schemat kodowania pokazany został poniżej:
Mapowanie kolorów w bajty danych |
Pamiętaj, że ze względu na negację sygnału TxD przed kodowaniem musimy zanegować bity określające kolor – bit o wartości 0 otrzymujemy przez wpisanie na odpowiednią pozycję 1 i vice versa.
Bity startu i stopu generowane są automatycznie. Zgodnie z powyższym schematem, jeśli chcemy, aby pierwsza dioda miała kolor czerwony musimy wysłać następującą sekwencję:
USART_putchar(&USARTC0, 0b11011011); //Nadajemy G - 0b000 USART_putchar(&USARTC0, 0b11011011); //Nadajemy G - 0b000 USART_putchar(&USARTC0, 0b10011011); //Nadajemy G - 0b00 i R - 0b1 USART_putchar(&USARTC0, 0b10010010); //Nadajemy R - 0b111 USART_putchar(&USARTC0, 0b10010010); //Nadajemy R - 0b111 USART_putchar(&USARTC0, 0b11011010); //Nadajemy R - 0b1 i B - 0b00 USART_putchar(&USARTC0, 0b11011011); //Nadajemy B - 0b000 USART_putchar(&USARTC0, 0b11011011); //Nadajemy B - 0b000
Proste, prawda? Jednak kodując „ręcznie” kolory łatwo o pomyłkę i przydałaby się funkcja, która na podstawie podanych składowych GRB zwróciłaby nam 8 bajtów, które musimy przesłać przez UART. Nic prostszego jak taką funkcję napisać.
Jak widzimy wystarczy wsuwać na odpowiednie pozycje poszczególne bity określające składowe kolorów. Zadanie pozornie jest proste, jednak nie dla C, które nie implementuje operacji rotacji bitów. W efekcie odpowiednik kodu w C będzie albo bardzo „pokręcony”, albo bardzo nieefektywny. A ponieważ, o czym będzie za chwilę, efektywność funkcji transkodującej może być istotna, napiszemy ją w asemblerze.
Powodem, dla którego napiszemy tą funkcję w asemblerze nie jest niewystarczająca moc obliczeniowa XMEGA, lecz brak wygodnych operatorów rotacji bitów w języku C.
Przy okazji pokażę, jak łączyć asembler i kod C. Funkcja będzie dosyć długa (asembler jest bardzo rozwlekłym językiem), ale tak naprawdę składa się ona tylko z ośmiu powtórzeń tego samego kodu, umożliwiającego umieszczenie na odpowiedniej pozycji bajta danych UART bitów kodujących kolory.
Zacznijmy od nagłówka funkcji – deklaruje on, że funkcja przyjmuje jako argumenty trzy składowe koloru, oraz wskaźnik do bufora (o długości co najmniej 8 bajtów) w którym umieszczona zostanie transkodowana sekwencja:
void WS2812B_transcodeGRB(uint8_t green, uint8_t red, uint8_t blue, void *buffer) //Przekonwertuj podany kolor w formacie GRB na ciąg 8 bajtów dla USART { asm volatile( "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "COM %0 \n\t" "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G7 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G6 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G5 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający G7-G5 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G4 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G3 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G2 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający G4-G3 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G1 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %0 \n\t" "ROR R16 \n\t" //Wsuń G0 "ROR R16 \n\t" "ROR R16 \n\t" "COM %1 \n\t" "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R7 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający G1-G0 i R7 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R6 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R5 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R4 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający R6-R4 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R3 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R2 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R1 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający R3-R1 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %1 \n\t" "ROR R16 \n\t" //Wsuń R0 "ROR R16 \n\t" "ROR R16 \n\t" "COM %2 \n\t" "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B7 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B6 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający R0 i B7-R6 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B5 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B4 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B3 "ROR R16 \n\t" "ST %a3+, R16 \n\t" //Zapisz bajt zawierający B5-B3 "LDI R16, 0b01001001 \n\t" //Maska do transkodowania "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B2 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B1 "ROR R16 \n\t" "ROR R16 \n\t" "ROL %2 \n\t" "ROR R16 \n\t" //Wsuń B0 "ROR R16 \n\t" "ST %a3, R16 \n\t" //Zapisz bajt zawierający B2-B0 ::"r" (green), "r" (red), "r" (blue), "e" (buffer): "r16"); }
Ponieważ całe ciało funkcji napisane jest w asmeblerze, pokrótce tylko napiszę o co w tym chodzi. Argumenty %0, %1 i %2 to odpowiednio zmienne green, red i blue, a %3 to wskaźnik do bufora buffer. Ponieważ nasza funkcja modyfikuje rejestr R16, został on wyszczególniony na liście elementów modyfikowanych (clobber list), dzięki czemu kompilator jeśli zajdzie taka potrzeba automatycznie zachowa jego zawartość.
Samo składanie danych polega na wykorzystaniu operacji rotacji w prawo szablonu zawierającego wzorzec generowanego przebiegu (LDI R16, 0b01001001), na który nakładamy w odpowiednich pozycjach kolejne bity określające kolor. Po skompletowaniu wszystkich 8 bitów, bajt danych ląduje w buforze buffer. Całość może wydawać się na pierwszy rzut oka zawiła, ale bliższa analiza ujawni prostotę tego rozwiązania.
Przy okazji mały konkurs – jeśli ktoś ma lepsze (szybsze) rozwiązanie to zapraszam to jego pokazania. Podpowiem, że da się jeszcze wprowadzić całkiem spore optymalizacje.
Powyższą funkcję możemy użyć do transkodowania formatu zapisu koloru:
uint8_t bufor[8]; WS2812B_transcodeGRB(0, 0, 64, bufor);
Efektem działania powyższej instrukcji jest umieszczenie w tablicy bufor 8 bajtów odpowiadających za wybranie koloru GRB równego {0, 0, 64}, które możemy wysłać poprzez USART.
I to tyle, widzimy, że wysyłanie danych do WS2812B za pomocą USART nie jest trudne, leczy wymaga przekodowania formatu zapisu koloru.
Trochę się z tym namęczyliśmy, pora więc podsumować zyski i straty.
Stratą z pewnością jest konieczność transkodowania kolorów z formatu GRB na 8 bajtów nadających się do wysłania przez UART. A zyski? Są bardzo istotne, chociaż może nie dla wszystkich od razu takie oczywiste.
Przede wszystkim w sposób całkowicie sprzętowy wysyłamy do WS2812B 3 bity danych. Normalnie zajęłoby nam to co najmniej 3600-4500 ns, w naszym przykładzie trwa to dokładnie tyle samo, tyle, że w czasie wysyłania danych rdzeń CPU może zamiast czekać w pustych pętlach realizując instrukcje delay, zająć się czymś pożytecznym, np. obliczeniami czy generowaniem grafiki, którą chcemy przy pomocy naszych diod wyświetlić. Ponieważ generowanie impulsów odbywa się sprzętowo, nie musimy blokować przerwań – ich wystąpienie nie zrujnuje nam timingów, gdyż nie mają one wpływu na działanie UART.
Ponieważ rejestr nadajnika UART jest zbuforowany, możemy zapisać do niego dwa bajty, a więc wysłać bez ingerencji CPU aż 6 bitów danych – daje nam to ok. 9 µs na przygotowanie kolejnej porcji danych, a przy taktowaniu 32 MHz przekłada się to na możliwość wykonania nawet 288 instrukcji asemblera. Tak naprawdę kolejny bajt musimy przesłać po czasie nie dłuższym niż czas rozpoznawany jako RESET magistrali, czyli mamy do dyspozycji co najmniej ok. 10-20 µs. Warto też zauważyć, że ponieważ mikrokontroler nie jest zajęty generowaniem przebiegu sterującego LEDami, pokazane rozwiązanie świetnie się nada szczególnie w przypadku szybszych mikrokontrolerów – nie będą one marnowały cykli na pustych pętlach.
Oczywiście, ktoś może zapytać, czy warto poświęcać UART do realizacji w sumie tak trywialnego zadania? To oczywiście zależy, jednak w przypadku XMEGA mamy do dyspozycji nawet 8 interfejsów UART w jednym układzie, stąd poświęcenie jednego do realizacji komunikacji z WS2812B nie jest żadną stratą.
Pamiętaj, że jeśli brakuje ci układu peryferyjnego, np. kolejnej instancji UART, to znaczy, że źle wybrałeś mikrokontroler do zadania. Jedynym sensownym rozwiązaniem jest jego zmiana – protezowanie kodu zazwyczaj nie prowadzi do dobrych efektów.
Do pobrania
Poniżej znajdziesz projekt Atmel Studio zawierający kompletny omawiany przykład. Miłego korzystania!
WS2812B-USART.ZIP (kopia)
Ok, a czy da się to wszystko zrobić jeszcze lepiej? Jak sądzisz…?
Zajmiemy się tym w kolejnym odcinku. :-)
DMA i event system?
OdpowiedzUsuńWitam. Zastanawiam się, czy nie da się zakodować bitów uarta/spi za pomocą XCL z XMEGA E. Może przerzutnik?
OdpowiedzUsuń:) Nie chcę uprzedzać faktów. Ale czekamy na propozycję takiego rozwiązania, najlepsze zapewne zostaną nagrodzone.
UsuńW necie jest przykład Atmela na podstawie którego można to wykonać.
Usuńhttp://www.atmel.com/images/atmel-42164-at03335-manchester-transceiver-using-the-usart-and-xcl-modules-on-xmega-e_application-note.pdf
Tu niestety mamy ciut bardziej skomplikowaną sytuację niż kodowanie manchester. Ale zachęcam do prób - dla osoby, która pierwsza pokaże jak wykorzystać XCL do komunikacji z WS2812B nagroda!
Usuń"Przy okazji mały konkurs – jeśli ktoś ma lepsze (szybsze) rozwiązanie to zapraszam to jego pokazania. Podpowiem, że da się jeszcze wprowadzić całkiem spore optymalizacje."
OdpowiedzUsuńvoid WS2812B_transcodeGRB(uint8_t green, uint8_t red, uint8_t blue, void *buffer)
{
asm volatile(
"LDI R16, 8" "\n\t"
"L_dl1:" "\n\t"
"LDI R17, 0b00100100" "\n\t"
"ROL %2" "\n\t"
"ROL %1" "\n\t"
"ROL %0" "\n\t"
"BRCC L_dl2" "\n\t"
"SBR R17,0" "\n\t"
"L_dl2:" "\n\t"
"ROL %2" "\n\t"
"ROL %1" "\n\t"
"ROL %0" "\n\t"
"BRCC L_dl3" "\n\t"
"SBR R17,3" "\n\t"
"L_dl3:" "\n\t"
"ROL %2" "\n\t"
"ROL %1" "\n\t"
"ROL %0" "\n\t"
"BRCC L_dl4" "\n\t"
"SBR R17,6" "\n\t"
"L_dl4:" "\n\t"
"COM R17" "\n\t"
"ST %a3+, R17" "\n\t"
"DEC R16" "\n\t"
"BRNE L_dl1" "\n\t"
::"r" (green), "r" (red), "r" (blue), "e" (buffer): "r16", "r17"
);
}
NIe wiem czy działa, bo nie mam Xmega i WS2812B. ;)
Jeszcze inna wersja, wszystko zależy od biegłego posługiwania się assemblerem.
Usuńvoid WS2812B_transcodeGRB2(uint8_t green, uint8_t red, uint8_t blue, void *buffer)
{
asm volatile(
"LDI R16, 8" "\n\t"
"L_dl1%=:" "\n\t"
"LDI R17, 0b00100100" "\n\t"
"BST %0,7" "\n\t"
"BLD R17,0" "\n\t"
"BST %0,6" "\n\t"
"BLD R17,3" "\n\t"
"BST %0,5" "\n\t"
"BLD R17,6" "\n\t"
"ROL %2" "\n\t"
"ROL %1" "\n\t"
"ROL %0" "\n\t"
"ROL %2" "\n\t"
"ROL %1" "\n\t"
"ROL %0" "\n\t"
"ROL %2" "\n\t"
"ROL %1 " "\n\t"
"ROL %0" "\n\t"
"COM R17" "\n\t"
"ST %a3+, R17" "\n\t"
"DEC R16" "\n\t"
"BRNE L_dl1%=" "\n\t"
::"r" (green), "r" (red), "r" (blue), "e" (buffer): "r16", "r17"
);
}
wygląda na to, że działa :)
UsuńSterowanie tymi diodami bezpośrednio z portu Xmega nie jest najlepszym rozwiązaniem. Minimalne napięcie zasilania dla tych diod to 3,5V. Pewnie najbardziej praktyczne i ekonomiczne będzie zasilanie standardowym zasilaczem 5V. Przy 5V minimalny stan napięcia jaki powinno się podać na wejście DIN diody to 0,7Vcc czyli około 3,5V. Według specyfikacji Xmega, przy standardowym napięciu zasilania 3,3V, typowe napięcie na wyjściu w stanie wysokim to 3,1V czyli już poza specyfikacją diody, ale minimalne gwarantowane napięcie na porcie w stanie wysokim to już tylko 2,6V, czyli dość daleko od wymaganego minimum 3,5V. Zalecane zatem jest użycie konwertera poziomów. Oczywiście bezpośrednie podłączenie portu do diody może skutkować poprawnym działaniem, ale można spodziewać się też tego, że mogą wypadać pojedyncze bity, jak i również przy zmianie warunków zewnętrznych np. temperatury wszystko może przestać działać.
OdpowiedzUsuńOczywiście masz rację, ale... jak zwykle diabeł tkwi w szczegółach. Napięcia wyjściowe portu IO podawane są w sytuacji, gdy jest on obciążany prądowo. Prąd polaryzacji wejścia DI diody WS2812B wynosi tylko 1 uA, a więc wyjście XMEGA praktycznie nie jest obciążane, stąd dla stanu wysokiego panuje na nim praktycznie Vcc, a dla niskiego GND. Oczywiście dla XMEGA zasilanego z 3,3V, a diod z 5V ciągle jesteśmy poza specyfikacją, ale tylko nieznacznie. Możemy zrobić dwie rzeczy - zwiększyć nap. zasilające XMEGA do dopuszczalnego 3,6V, lub zasilić diody napięciem ciut niższym niż 5V. Ponieważ wymagają one i tak dedykowanego zasilania, a ze względu na duży prąd, najplepiej użyć przetwornicy impulsowej, ustawienie napięcia 4-4,5V zamiast 5V nie jest problemem. Oczywiście trzeba tak cudować tylko jeśli chcemy być całkowicie w zgodzie z notami producentów, co jest podejściem chwalebnym. W praktyce stan wysoki jest prawidłowo rozpznawany nawet jeśli nie jesteśmy całlkiem zgodni ze specyfikacją (oczywiście nie jest to zalecane podejście), warto też zauważyć, że problem dotyczy wyłącznie pierwszej diody, gdyż do kolejnych trafia już sygnał regenerowany.
UsuńTrzeba też liczyć się z tym, że często sterownik jest umieszczony z dala od pierwszej diody, w skrajnych przypadkach mogą to być metry przewodu. Dochodzą więc niekorzystne pojemności i indukcyjności, które wraz z częstotliwością dochodzącą do 1MHz tworzą dodatkowe obciążenie dla portu oraz warunki sprzyjające różnym oscylacjom. W takim wypadku przy takiej konstrukcji trzeba liczyć się z czynnikiem losowym działania układu.
UsuńPrzy stosowanym formacie przesyłu danych raczej nie ma możliwości aby zagwarantować poprawność transmisji na odległość metrów. Te diody wymagają raczej, aby sterujący mikrokontroler był blisko nich. Przy większych odległościach dodałbym przy diodach sterujący nimi mikrokontroler, komunikujący się z resztą układu po np. RS485, lub CAN. Oczywiście jeśli zależy nam na absolutnej pewności transmisji. Jeśli robimy oświetlenie na choinkę, to raczej sporadyczne przekłamania są bez znaczenia.
UsuńJak wysłać 4 bajty danych do WS2812 przy użyciu 9-bitowej ramki USART? Z tego co mi się wydaje to trzeba do tego aż 12 bitów, a 9 bitów danych+bit startu i bit stopu=11 bitów. Skąd wziąć ten jeden dodatkowy bit?
OdpowiedzUsuńW tekście pisze o wysyłaniu 4 bitów, a nie bajtów, przy pomocy 9-bitowej ramki. A 9-bitowa ramka danych ma bit startu, bit stopu i opcjonalny bit parzystości/stopu - łacznie 12 bitów, czyli wszystko się zgadza.
Usuń