Czy malutki, ośmiobitowy mikrokontroler PIC12F683 da radę wysterować dynamiczną kolorową animację na diodach WS2812? Użyty tu MCU ma raptem 2048 bajtów pamięci Flash, 128 bajtów RAM i jest taktowany z częstotliwością do 20 MHz (8 MHz w przypadku wewnętrznego oscylatora). Czy to wystarczy? Przekonajmy się!
Pomysł wziął się stąd, że mam kilkanaście tego typu PICów po starszych projektach i postanowiłem spróbować im nadać nowe życie. Być może coś na święta. Zobaczymy, czy jeszcze zdążę. Na czas publikacji pierwszej części (tego tematu) mam dopiero rozpoczętą drugą.
Tutaj opiszę pierwszą część przygody, czyli uruchomienie WS2812 i ograniczenia wynikającego z tego mikrokontrolera.
Ten temat stanowi część mojego cyklu o mikrokontrolerach PIC i kompilatorze SDCC. Po inne części, zapraszam do tematów poniżej:
Część 1 - Konfiguracja środowiska pracy
https://www.elektroda.pl/rtvforum/topic3635522.html#18304424
Część 2 - Blink LED, piny IO, cyfrowe wejścia i wyjścia
https://www.elektroda.pl/rtvforum/topic3647884.html#18389188
Część 3 - Ustawienia oscylatora. Oscylator wewnętrzny, zewnętrzny, rezonator kwarcowy, PLL
https://www.elektroda.pl/rtvforum/topic3657704.html
Część 4 - Timery, przerwania
https://www.elektroda.pl/rtvforum/topic3676645.html#18580858
Część 5 - Obsługa wyświetlacza siedmiosegmentowego
https://www.elektroda.pl/rtvforum/topic3676650.html#18580877
Część 6 - Sterownik wyświetlacza LED MM5450
https://www.elektroda.pl/rtvforum/topic3845301.html
Powiązany temat o PIC12F:
PIC12F683 i SDCC - tutorial - tworzymy prosty ściemniacz (czytamy noty katalogowe)
Specyfikacja WS2812
WS2812 to indywidualnie adresowalny, kolorowy LED, który łączy w sobie diodę LED RGB oraz kontroler w jednej obudowie. Dzięki temu każdy moduł może być sterowany niezależnie, umożliwiając tworzenie efektów świetlnych, animacji i dynamicznych zmian kolorów w paskach LED, matrycach czy projektach DIY. WS2812 wykorzystuje protokół jednoliniowy do przesyłania danych, co pozwala na łatwe łączenie wielu diod w szeregu przy minimalnej liczbie przewodów. Działa to w ten sposób, że WS2812 są łączone w szereg - wejście DOUT jednej podłączane jest do DIN drugiej, a same ich kontrolery sprytnie przekazują dalej sygnał, przejmując jedynie swój kolor opisany trzema bajtami.
Bajty kodowane są jako para stan wysoki - stan niski, gdzie czas danego stanu musi spełniać specyfikację.
Zgodnie z tabelką - musimy być w stanie wygenerować czasy T0H (0.4 us) i i T1H (0.8 us) z marginesem błędu do 0.15 us (w praktyce nieco więcej).
Pierwsze próby i pomiar czasu instrukcji
Pierwszy PIC jaki rzucił mi się w oczy to PIC12F675, leżał tuż obok tytułowego PIC12F683. Miał on jednak jeszcze mniej pamięci RAM, bo raptem 64 bajty:
Potem porównałem noty i uznałem, że jednak PIC12F683 jest lepszą opcją:
To malutki 8-bitowy MCU w obudowie DIP8, ale tu raczej starczy?
Na początek uruchomiłem PICa. PICKIT2 widzi go bez zewnętrznych elementów:
Do pracy PICa natomiast potrzebny jest rezystor 10 kΩ na MCLR (podciągnięcie do zasilania) oraz wypada dać kondensator odsprzęgający 100 nF pomiędzy zasilaniem a masą.
Zacząłem od próby z wbudowanym oscylatorem - 8 MHz, szybciej się nie da. Ustawia się to w rejestrze OSCCON:
Chciałem zobrazować ile trwa wykonanie ustawienia stanu GPIO. Można to oczywiście policzyć (ze wzoru Fosc/4), ale warto i tak jest zweryfikować rezultat. Przykładowy kod:
Kod: C / C++
Interesuje nas tu czas stanu wysokiego, bo po stanie niskim jest jeszcze skok na początek pętli, a to też instrukcja:
Wychodzi 500 ns... a potrzebujemy 400 ns - 800 ns, troszkę mało.
Migracja do zewnętrznego oscylatora
8 MHz jest raczej za wolne. Trzeba przełączyć się na zewnętrzny oscylator:
Mam akurat takie 20 MHz:
Wystarczy podłączyć go na odpowiednich pinach:
Trzeba go też włączyć w słowie konfiguracyjnym.
Nowy kod - testujemy:
Kod: C / C++
Interesuje nas tu czas stanu niskiego, bo po wysokim jest jeszcze skok:
Jest lepiej, 360 ns jakieś, może mniej.
Pierwsze kolory
Skoro czas jednego rozkazu to około 360 ns, to dwa rozkazy dadzą nam około 720 ns. Drugi rozkaz można wypełnić poleceniem nop - bierność procesora.
Czasy zer i jedynek z WS2812 nie są krytyczne, można w pewnym stopniu odejść od oczekiwanych timingów a diody i tak potencjalnie mogą działać. Z tego powodu liczyłem, że proste sprawdzanie bitów nie popsuje w pełni komunikacji. Podobnie z moimi makrami WS_HIGH i WS_LOW, co do których nie mam pewności - trzeba by sprawdzić w wygenerowanym listingu na co je zamienia kompilator.
Kod: C / C++
W najgorszym wypadku planowałem rozwinąć wysyłanie bajtu tak, aby nie było pętli, albo napisać cały fragment w assemblerze. Innym ciekawym trikiem jest załadowanie całego bajtu do rejestru GPIO i przesuwanie go o jeden bit tak by kolejno wystawiać jego bity na dany pin. Widziałem to kiedyś przy generatorze sygnału PAL, ale tu nie było to potrzebne.
Reszta kodu mówi sama za siebie - przygotowałem na sztywno trzy piksele, czyli dziewięć bajtów. Kolejno osobno kolory.
Kod: C / C++
Potem podłączyłem pasek LED na wybrany GPIO. Zarówno PIC, jak i WS2812 pracują na 5 V, więc nie ma kłopotów z zasilaniem. Pasek LED ma osobno linię danych, zasilania i masy, więc nie trzeba przejmować się wydajnością prądową pinu PICa.
Efekt:
Wygląda na to, że działa, że uzyskane czasy są w granicach tolerancji WS2812, ale czy na pewno?
Pseudoanimacja - zamiana kolorów
W celu weryfikacji działania przygotowałem najprostszą animację - zamianę kolorów. Dwie tablice z danymi wyświetlane z odstępem tak, aby można było zauważyć zmianę.
Kod: C / C++
Wygląda na to, że transfer działa wystarczająco rzetelnie i naprzemiennie wyświetlają się dwa zestawy kolorów. Dane nie są gubione:
Pierwsza animacja
Kolejnym krokiem jest już autentyczna animacja - tylko jaka? Może jakieś płynne przejścia kolorów oparte na funkcji sinus. Można w ten sposób uzyskać różnobarwny efekt, jeśli tylko wprowadzimy pewne przesunięcie pomiędzy wartościami liczbowymi koloru czerwonego, zielonego i niebieskiego. Dla ułatwienia wprowadziłem tablicę z wartościami sinus (w postaci bajtów), choć w dużej mierze to zgubne podejście - na nią samą tracę aż 32 bajty!
Kod: C / C++
Efekt działa, ale jest jeden problem:
Nie można dodać więcej pikseli, bo brakuje pamięci RAM.
Optymalizacje i ograniczenia
Próbowałem różnych podejść, ale nawet przejście na dynamicznie liczone wartości też dużo nie zmienia - SDCC też potrzebuje dla nich pamięci, bo tworzy dla nich pomocnicze funkcje, gdy dana operacja nie jest wspierana przez procesor:
warning: Relocation symbol "_cinit" [0x0022] has no section. (pass 0)
warning: Relocation of section "UDL__moduint_0" failed, relocating to a shared memory location.
error: No target memory available for section "UDL__moduint_0".
error: Error while writing hex file.warning: Relocation of section "IDD_idata_0" failed, relocating to a shared memory location.
error: No target memory available for section "IDD_idata_0".
error: Error while writing hex file.warning: Relocation of section "UDL__mulint_0" failed, relocating to a shared memory location.
error: No target memory available for section "UDL__mulint_0".
error: Error while writing hex file.Dla najlepszej wydajności należałoby to napisać w assemblerze i ograniczyć się do typów mieszczących się w rejestrach, może wtedy udało się ugrać nieco więcej bajtów.
Wersja poniżej obsługuje do około 20 LEDów, czyli 60 bajtów przeznacza na piksele. To niecałe 50% dostępnej pamięci RAM, bo jest jej w sumie 128 bajtów.
Kod: C / C++
Końcowy efekt:
Teraz można implementować już inne animacje, knight rider, goniące ogniki, fajerwerki, tęcze, oczywiście w danej kompilacji firmware pewnie będzie tylko jedna z nich, zależy ile wymaga obliczeń.
Podsumowanie
Na PIC12F683 mamy całościowo 128 bajtów pamięci RAM. Jeden piksel RGB w klasycznym założeniu to 3 bajty, więc zakładając, że połowa RAM idzie na piksele (bo jeszcze zmienne, itd) to mamy 64/3 = około 21 pikselów. W praktyce udało mi się uruchomić do 20 pikselów, choć pewnie kod można by było jeszcze znacznie odchudzić.
Potencjalnie można by próbować to optymalizować, ale nie widzę tu zbyt wielu opcji:
- na PIC12F nie ma raczej żadnych peryferiów, takich jak sprzętowe SPI, co mogłoby wysyłać te bajty
- PIC12F jest raczej za wolny, by generować te bajty w locie
- ciekawym pomysłem byłoby pakowanie pikseli w mniejszej ilości bajtów, ale wtedy z kolei komplikuje się samo animowanie i dekodowanie rezultatów
Pewnie przepisanie całości do assemblera by troszkę pomogło, ale na tym etapie stwierdzam, że te 128 bajtów RAM jest jednak w dużej mierze przeszkodą nie do przejścia pod kątem animacji większej ilości LEDów.
PIC12F jest w stanie kontrolować WS2812, ale tylko krótkie łańcuchy, tym bardziej jeśli chcemy coś jeszcze do niego dodać - kontrolę przyciskiem czy tam różne tryby.
Ciąg dalszy w osobnym temacie.
Załączam projekt (kod + skrypt bat dla SDCC)
Fajne? Ranking DIY Pomogłem? Kup mi kawę.