Oto relacja z budowy dwukanałowego ściemniacza opartego o 8-bitowy mikrokontroler PIC12F683 i części z elektrośmieci. Projekt zostanie napisany w języku C przy użyciu kompilatora SDCC. Do kontroli poziomów jasności zostanie użyty pojedynczy enkoder, a jego wciśnięcie będzie przełączać kontrolowany w danym momencie pasek. PWM będzie zrealizowane programowo na timerze i przerwaniu, ponieważ przedstawiany tu PIC posiada tylko jeden sprzętowy kanał do generowania impulsów, a moje założenia wymagają kontroli co najmniej dwóch pasków.
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ązane tematy o PIC12F:
PIC12F683 i SDCC - tutorial - tworzymy prosty ściemniacz (czytamy noty katalogowe)
Świąteczne animacje WS2812 na PIC12F683 - ile LEDów obsłuży 128 bajtów RAM?
Wstęp
To już kolejny projekt na malutkim ośmiobitowcu - tym razem oparty o enkoder. Już pokazywałem kiedyś ściemniacz, dlatego tym razem zdecydowałem się na takie urozmaicenie. Dodatkowo moja poprzednia prezentacja była oparta o mikroC, gdzie część mechanizmów jest gotowa, a dziś tworzę od 0 w SDCC. Może to kogoś zainteresuje, ale jeszcze rzut oka na użyty MCU.
2048 bajtów Flash na instrukcje i stałe dane, 128 bajtów RAM na zmienne... to nie jest zbyt dużo pamięci, ale na ściemniacz powinno starczyć. Gorzej, że mamy tylko jedno PWM, a ja chcę obsłużyć dwa paski:
Niewątpliwie PWM będę realizować programowo, poprzez operacje na GPIO.
Przygotowanie części
Swoje konstrukcje staram się opierać o części z elektrośmieci, więc zacząłem od mojej kolekcji wybranych płytek:
Rzucił mi się w oczy moduł z radia samochodowego. Jest tam wyświetlacz, kontroler i enkoder:
Kontroler to PD6340A. Nie znalazłem w sieci jego noty katalogowej, choć znalazłem schemat radia. Podłączony jest on do głównego sterownika jakimś dziwnym dwupinowym protokołem.
Enkoder wylutowałem za pomocą topnika i plecionki - nakładam topnik na luty, a potem rozgrzanym grotem przyciskam do nich plecionkę. Plecionka zbiera spoiwo z padów. Potem wyciągam element, jeszcze pomagając sobie lutownicą, bo nie zawsze całe spoiwo się zbierze.
Potem określiłem wyprowadzenia enkodera.
Mamy tutaj:
- właściwy enkoder - czyli "nieskończony potencjometr", którego wyjścia to trzy piny - masa i dwa sygnały, to te trzy nóżki. Pobieliłem je i dodałem przewody:
- dodatkowy przycisk - po prostu wciśnięcie enkodera zwiera obwód
Działanie enkodera na oscyloskopie
Podciągnąłem oba wyjścia (skrajne nóżki) enkodera do zasilania przez rezystory 10 kΩ. Środkową nóżkę podłączyłem do masy. Sondy oscyloskopu podłączyłem do wyjść. Zaobserwujmy, co się na nich dzieje.
Obracanie w prawo:
Stan niski najpierw pojawia się na kanale pierwszym (żółty przebieg).
Obracanie w lewo:
Stan niski najpierw pojawia się na kanale drugim (niebieski przebieg).
W przypadku braku ruchu mamy ciągle stan wysoki na obu wyjściach.
Mamy już punkt zaczepienia - trzeba to jeszcze wykryć. Załóżmy, że próbkujemy dostatecznie szybko. W przypadku braku ruchu oba piny cyfrowe zwrócą nam 11, ale w momencie rozpoczęcia ruchu otrzymamy 10 lub 01, w zależności od tego w którą stronę obracany jest enkoder.
Tyle, że tych stanów jest więcej - jak mamy 2 piny (2 bity), to mamy 2^2 = 4 możliwe stany. Dodatkowo chcemy badać stan wcześniej i potem, więc mamy dwie migawki po 2 bity, w sumie 2^3 = 8 możliwości.
Rozpisujemy wedle oscyloskopu przejścia dla ruchu (załóżmy) w prawo:
- 0b00 -> 0b01
- 0b01 -> 0b11
- 0b11 -> 0b10
- 0b10 -> 0b00
Każde inne przejście oznacza ruch w przeciwną stronę, za wyjątkiem braku zmiany.
Można to już wpisać w kod...
Pierwsze kroki z enkoderem
Najpierw załączamy nagłówek użytego PICa i ustawiamy jego konfigurację - oscylator wewnętrzny, brak watchdoga.
Kod: C / C++
Potem uruchamiamy PICa, wyłączamy piny anologowe i komparatory, inicjujemy tryb pracy pinów (dwa wejścia - TRISIO), ustawiamy wewnętrzny oscylator (OSCCON), włączamy wbudowane rezystory pull up (OPTION_REG i WPU - by ustawić domyślnie stan wysoki na wyjściach enkodera) oraz inicjujemy zmienne ich początkowymi wartościami.
Kod: C / C++
Teraz pora na właściwą logikę enkodera. Korzystam tu z tego, że oba piny są tuż obok siebie w rejestrze GPIO. Po prostu przesuwam go o 4 by dostać się do czwartego bitu a potem wykonuję iloczyn logiczny z bitowym 0b11 by tylko uzyskać te dwie wartości. Potem je porównuję tak jak opisałem wcześniej. Na koniec określam, czy był ruch w prawo czy w lewo i na bazie tego na ten moment tylko włączam lub gaszę ściemniacz.
Kod: C / C++
Podstawa ściemniacza
Enkoder działa, można zrealizować już ściemniacz. Najlepiej byłoby zrobić to na sprzętowym PWM, ale PIC12F683 ma tylko jedno takie wyjście, a ja docelowo chcę mieć dwa. Z tego powodu zrobiłem proste programowe PWM. Zliczam wykonania pętli (od 0 do 100) i porównuję bieżący indeks z wartością ściemniacza, którą też może edytować enkoder. W ten sposób uzyskuję płynną regulację wypełnienia:
Kod: C / C++
Poprawki - większa częstotliwość
Na tym etapie wyszedł pierwszy minus programowego PWM. Moje DIMMER_MAX jest za duże - 100% wypełnienia wymaga zbyt wielu kroków. Lepsze byłoby sprzętowe PWM, ale tu jest tylko jeden taki moduł, więc mi nie starczy. Wdrożyłem najbardziej oczywistą poprawkę czyli zmniejszyłem DIMMER_MAX do 50, co skutkowało dwukrotnym zwiększeniem częstotliwości generowanego sygnału.
Kod: C / C++
Przycisk i rozdzielenie ściemniacza
Wiele enkoderów ma dodatkowo zawarty w sobie przycisk. Jest to najzwyklejszy microswitch, nie ma tu większej filozofii. Oddelegowałem dla niego jeden pin GPIO w trybie wejścia z wbudowanym rezystorem podciągającym. Pozwala to wymusić na nim domyślny stan wysoki. Wciśnięcie przycisku zwiera go do masy. W momencie wciśnięcia przycisku przerzucam wybrany ściemniacz - teraz program obsługuje dwa osobne ściemniacze. Nie ma jeszcze w kodzie obsługi drgań styków (debouncingu), ale można pomiędzy GPIO a masę dać kondensator o małej wartości, takiej jak 100 nF.
Stan ściemniaczy opisuję osobnymi zmiennymi - nie pokusiłem się na razie o tablicę, choć przy ich większej ilości zdecydowanie należałoby jej użyć.
Kod: C / C++
Program przetestowałem i tyle starcza, by obsługiwać dwa ściemniacze bez problemów z drganiem styków.
Testy z LED
Oglądanie przebiegów na oscyloskopie to jedno, ale prawdziwych testów z LED nic nie zastąpi. Może na razie starczą pojedyncze diody. GPIO PICa spokojnie zwykłego LEDa wysteruje:
Konieczna poprawka stabilności
Krótkie testy z pojedynczymi LEDami jednak pokazały, że praktyka nie jest jednak tak elegancka jak teoria. Sporadycznie szybkie obracanie generowało błędne odczyty, co nieco utrudniało nastawienie największej lub najmniejszej jasności.
Niewątpliwie trzeba bardziej zabezpieczyć ten sterownik i dodać śledzenie bieżącego i poprzedniego stanu.
Postanowiłem użyć sprawdzonego rozwiązania z Arduino. Wciąż bez przerwań, ale rzetelniej:
https://github.com/brianlow/Rotary/blob/master/Rotary.cpp
Kod: C / C++
Ta metoda wykorzystuje maszynę stanów i bada, czy enkoder wykonał pełen ruch w jedną lub drugą stronę właśnie wedle tych stanów. W razie złego kroku, odpowiednio "cofa" stan na odpowiedni wcześniejszy etap. Tylko konkretne finalne stany oznaczają ruch - tutaj z załączonymi bajtami DIR_CW oraz DIR_CCW.
Implementacja stanów z kolei sprowadza się tu do trzymania ich samych indeksów w tablicy - przykładowo będąc w stanie R_START jesteśmy w pierwszym wierszu tablicy, teraz mamy odczyt z dwóch GPIO (4 wartości możliwe) i tymi indeksami określamy kolumnę z pierwszego wiersza, a wartość z tej kolumny jest indeksem nowego stanu (oraz ew. kodem ruchu DIR).
Tablica jest tak dobrana, by obsługiwać wszystkie możliwe przejścia pomiędzy stanami, dlatego zawiera aż 7*4=28 możliwości.
Warto zajrzeć np. do drugiego wiersza - indeks R_CW_FINAL - tam widać, że jak coś pójdzie nie tak, to nie otrzymujemy informacji o DIR_, tylko wracamy do R_START.
Po tych zmianach nie potrafię już w żaden sposób wywołać niepoprawnych odczytów.
Przerzucenie ściemniacza na timer
Drugą konieczną poprawką jest przeniesienie obsługi ściemniacza do sprzętowego timera. Taki timer może działać z określoną przez nas częstotliwością i wywoływać przerwanie, w którym obsłużymy programowe tworzenie PWM. Nie mogę tu użyć po prostu gotowego PWM, bo ten PIC ma tylko jeden sprzętowy PWM (oparty o CCP1).
Co trzeba zrobić by uruchomić Timer0 z przerwaniem?
Najpierw w OPTION_REG ustawiamy bit 5 (źródło zegara Timer 0) oraz bity 2-1 (preskaler):
Potem włączamy w INTCON ogólne przerwania oraz przerwania przepełnienia Timer 0:
Trzeba jeszcze ustawić wartość TMR0, czyli tą, która będzie odliczana. Przerwanie wywoła się w momencie przepełnienia 0xFF - > 0x00 tej wartości.
Osobiście chciałem w miarę szybkie przerwanie, więc dobrałem preskaler 1:1, a więc skoro Timer0 chodzi z częstotliwością Fosc/4, to TMR0 jest inkrementowany z częstotliwością 2 MHz. Teraz wystarczy dobrać wartość TMR0 tak, by nasze przerwanie wywoływało się około z częstotliwością 50 kHz. Wynika to stąd, że mam 50 stopni jasności (DIMMER_MAX) a chcę na wyjściu mieć około 1 kHz. Zawsze w razie czego mogę znacząco zmniejszyć DIMMER_MAX i odciążyć CPU.
Ostatnią rzeczą o której trzeba pamiętać jest resetowanie TMR0 do pożądanej wartości w przerwaniu oraz czyszczenie flagi przerwania timera.
Cały kod:
Kod: C / C++
Dobór tranzystora
Często przy demontażu różnych elektrośmieci wylutowuję i odkładam tranzystory. Przed wylutem sprawdzam ich parametry, by zbierać to, czego mogę użyć. Któregoś dnia w przetwornicy od podświetlenia monitora trafiło się kilka D454, a właściwie AOD454.
To tranzystor MOSFET z kanałem typu N o dość dużym prądzie drena (12 A przy Vgs=10) i małej rezystancji w stanie otwarcia. PIC12F sterowałby go bezpośrednio ze swoich pinów (dokładniej: przez rezystor), więc napięcie na bramce wynosiłoby 5 V. Z noty katalogowej wynika, że przy Vgs=5 V Rds(on) wynosi 47 mΩ, co jest akceptowalną wartością. Nie każdy tranzystor da się wysterować z tak niskiego napięcia. Szczegółowe zachowanie tranzystora należy sprawdzić na wykresach w nocie katalogowej.
To oczywiście znaczne uproszczenie, ale w takim prostym przypadku mój wybór się sprawdził.
Testy z paskiem LED
Chciałem sprawdzić jak bardzo będzie się grzać mój tranzystor. Dobór tranzystora wraz z rezystorem bramkowym nie jest sprawą trywialną i daleko wykracza poza temat tej krótkiej prezentacji z PIC więc nie oczekiwałem rewelacyjnych rezultatów, ale i tak musiałem sprawdzić, czy w ogóle to zadziała.
Tranzystor był przylutowany na mały kawałek laminatu pokrytego miedzią, więc odprowadzenie ciepła było dość zredukowane, ale może to i lepiej - zawsze można polepszyć osiągi.
Wszystko testowałem z zasilaczem laboratoryjnym przy napięciu 12 V, dołączałem kolejne paski LED by zwiększać obciążenie.
Swoją drogą, odradzam Wam testowania pasków LED w ten sposób, nawet rolki się od ciepła wyginają, warstwa kleju pod paskiem też ulega degradacji.
Przy obciążeniu 0.66 A tranzystor jest zimny. Te 40 °C to tylko na skutek odbicia w spoiwie.
Przy obciążeniu 1.85 A dopiero tranzystor zaczyna się grzać, osiąga niecałe 30 °C.
Przy obciążeniu 2.9 A tranzystor przekracza 35 °C.
Przy obciążeniu 5.1 A tranzystor osiąga 65 °C.
Pewnie jakby tylko dodać jakiś radiator, to temperatura by jeszcze spadła.
Wyniki są dla mnie w pełni zadowalające.
Zapis do wewnętrznego EEPROM
Został jeszcze jeden mankament. Teraz ściemniacz zapomina swoje ustawienie po wyłączeniu go z zasilania. Przez większość czasu nie jest to problem, bo raczej zakładamy, że PIC jest zasilany cały czas, ale niektórzy mogą mieć sporadyczne przerwy w dostawie energii i nie jest pożądane, by ściemniacz zapominał wtedy swoje ustawienie.
W przypadku bardzo krótkich zaników zasilania można by kombinować i dawać dodatkowe kondensatory na linii zasilania PICa, ale raczej nie ma to sensu na dłuższą metę.
Na szczęście nasz malutki PIC ma całe 256 bajtów nieulotnej pamięci EEPROM:
Wbudowany EEPROM jest kontrolowany przez kilka rejestrów o nazwach zaczynając się od EE:
EEDAT przechowuje bajt danych, EEADR jego adres. EECON1 kontroluje operacje na pamięci.
Operacje zapisu i odczytu też są opisane w nocie katalogowej.
Odczyt jest najprostszy - po prostu ustawiamy adres, bit RD a potem odczytujemy dane.
Zapis jest podobny, ale nieco trudniejszy - włączamy zapis, wyłączamy przerwania, a potem wysyłamy magiczne wartości 0x55 0xAA, i dopiero wtedy rozpoczynamy zapis i czekamy na jego sukces.
Odczyt wystarczy wykonać raz po starcie MCU, zapis wykonuję z każdą zmianą.
Kod: C / C++
Optymalizacja zużycia EEPROM
Zapis z każdą zmianą nie jest optymalnym podejściem. Obracając enkoder tych zmian będziemy mieć mnóstwo, przecież przy 50 poziomach jasności mamy tu dobre 50 zapisów. Będzie to szybciej zużywać EEPROM, choć przy jego wytrzymałości, można solidnie argumentować, że naprawdę trzeba wiele takich cyklów "ściemniania" by mu zaszkodzić:
Mimo to podam prosty sposób jak można by to ulepszyć. Po prostu trzeba zredukować liczbę zapisów - można np. zapisywać po skończeniu operacji przez użytkownika. Pojawia się pytanie jak to wykryć - chyba najprościej w momencie zmiany poziomu jasności resetować licznik cykli na daną stałą wartość i cały czas w tle go zmniejszać, o ile nie jest zerowy. Gdy po zmniejszeniu osiągnie zero, wtedy wykonujemy zapis.
Podobny zabieg jest przydatny (a może i przydatniejszy) gdy zapisujemy bezpośrednio w pamięci Flash, która może mieć mniej przewidywanych cykli usuwania/zapisu.
Podsumowanie
Tym razem PIC12F683 w pełni podołał postawionemu zadaniu, a pewnie nawet dałoby się więcej pasków LED w ten sposób wysterować - ogranicza nas tylko ilość wolnych GPIO. Mając 8 pinów, w tym masę i zasilanie, zostaje nam 6 kandydatów do użycia. Niby jest MCLR, ale można mu wyłączyć rolę RESET i użyć jako GP3. GP3 może być tylko wejściem, ale na przycisk się nada. A więc 6 pinów, w tym 3 na enkoder - tego raczej nie przeskoczę. Zostają w takim razie 3 piny na paski LED. Dałoby się zrobić wersję RGB. Dobrany tranzystor też dobrze sobie radzi. 65 °C przy 5 A to raczej dobry wynik. Można by teraz pomyśleć o jakiejś obudowie z druku 3D i mamy funkcjonalny ściemniacz.
Fajne? Ranking DIY Pomogłem? Kup mi kawę.