- napięcie zasilania: 5 V (DC),
- maksymalny prąd obciążenia (bez podłączonych diod LED): 70 mA,
- zakres mierzonych temperatur: 0...125°C,
- rozdzielczość pomiaru temperatury: 1°C,
- dokładność pomiaru temperatury: ±0,5°C,
- zakres ustawień temperatur: 0...180°C,
- rozdzielczość ustawień temperatur: 10°C,
- zgodność ze standardami (nazw własnych użyto wyłącznie w celu identyfikacji produktu): Asus Aura Sync, Gigabyte RGB Fusion Ready, MSI Mystic Light Sync, ASRock Polychrome Sync.
Oprogramowanie mikrokontrolera
Plik nagłówkowy, pokazany na listingu 1, definiuje główne ustawienia sprzętowe, a także wprowadza niezbędne typy danych – w tym configType, który upraszcza późniejszą konfigurację diod LED.
typedef struct
{
uint8_t pwmFreq;
uint8_t ovCurr;
uint8_t respType;
uint8_t fineR;
uint8_t fineG;
uint8_t fineB;
}configType;
//Definicje portu sterującego diodami LED
#define ARGB_PORT_NAME PORTB
#define ARGB_PORT_MASK PIN2_bm //PB2
#define ARGB_PORT_AS_OUTPUT ARGB_PORT_NAME.DIRSET = ARGB_PORT_MASK
#define ARGB_PORT_AS_INPUT ARGB_PORT_NAME.DIRCLR = ARGB_PORT_MASK
#define ARGB_PORT_SET ARGB_PORT_NAME.OUTSET = ARGB_PORT_MASK
#define ARGB_PORT_RESET ARGB_PORT_NAME.OUTCLR = ARGB_PORT_MASK
#define ARGB_PORT_IS_SET (ARGB_PORT_NAME.IN & ARGB_PORT_MASK)
#define ARGB_PORT_IS_RESET (!(ARGB_PORT_NAME.IN & ARGB_PORT_MASK))
//Definicje timingów
#define NOP __asm__ __volatile__ ("nop") //50ns
#define T0H NOP; NOP; NOP; NOP //Minimum: 200 ns
#define T0L T0H; T0H; T0H; T0H //Minimum: 800 ns
#define T1H T0H; T0H; T0H; NOP //Minimum: 650 ns
#define T1L NOP; NOP; NOP; NOP //Minimum: 200 ns
#define RESET_TIME 250 //us
#define READ_TIMEOUT 160 //us
#define COMMAND_TIMEOUT 60 //us
//Definicje stałych konfiguracji diod LED
#define PWM_FREQ_1k 0
#define PWM_FREQ_2k 1
#define PWM_FREQ_9k 2
#define PWM_FREQ_18k 3
#define OVERALL_CURR_NORMAL 0
#define OVERALL_CURR_HALVED 1
#define RESPONSE_IS_ID 1
#define RESPONSE_IS_CURRENT 0
//Definicje stałych rozkazów oraz statusów ich wykonania
#define CMD_ASSIGN_ADDRESS 0x10
#define CMD_RESET_ADDRESS 0x20
#define CMD_PING_ADDRESS 0x30
#define CMD_ACTIVATE_ADDRESS 0x40
#define CMD_EXECUTED 0
#define CMD_DISCARDED 1
Listing 1. Plik nagłówkowy modułu obsługi diod ARGB Gen2
Dalej, na listingu 2, pokazano kod funkcji inicjalizacyjnej interfejsu komunikacyjnego, którego wyłącznym zadaniem jest ustawienie kierunku portu danych i jego stanu spoczynkowego (0).
{
//Port sterujący LED, jako wyjściowy ze stanem 0
ARGB_PORT_RESET;
ARGB_PORT_AS_OUTPUT;
}
Listing 2. Kod funkcji inicjalizacyjnej interfejsu ARGB Gen2
Z kolei na listingu 3 pokazano ciała funkcji odpowiedzialnych za przesłanie jednego bitu danych (0 lub 1), przy udziale wspomnianego wcześniej interfejsu komunikacyjnego. Warto zauważyć, że stosowne funkcje zdefiniowano, używając atrybutu __always_inline__, który instruuje kompilator, by ZAWSZE wstawiał w miejscu wywołania rozwinięcie funkcji, nie zaś skok do jej ciała. Jest to o tyle istotne, że operujemy na dość rygorystycznych timingach (rzędu setek nanosekund). W takiej sytuacji – przy częstotliwości taktowania mikrokontrolera równej 20 MHz (okres 50 ns) – jakiekolwiek niepotrzebne (z punktu widzenia programisty, nie kompilatora) wykonanie rozkazu asemblera może rozłożyć całą transmisję danych, całkowicie uniemożliwiając sterowanie wspomnianymi elementami.
//F_CPU = 20 MHz (NOP = 50 ns)
__inline__ void __attribute__((__always_inline__)) argbSendBit0(void)
{
ARGB_PORT_SET;
T0H;
ARGB_PORT_RESET;
T0L;
}
__inline__ void __attribute__((__always_inline__)) argbSendBit1(void)
{
ARGB_PORT_SET;
T1H;
ARGB_PORT_RESET;
T1L;
}
Listing 3. Funkcje odpowiedzialne za przesłanie jednego bitu danych interfejsu ARGB Gen2
Skoro mamy już funkcje umożliwiające przesłanie jednego bitu informacji, pora na funkcję (także odpowiednio zadeklarowaną), umożliwiającą przesłanie kompletnego bajtu danych za pomocą interfejsu ARGB Gen2 – patrz listing 4.
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
if(Byte & 0x80) argbSendBit1(); else argbSendBit0();
if(Byte & 0x40) argbSendBit1(); else argbSendBit0();
if(Byte & 0x20) argbSendBit1(); else argbSendBit0();
if(Byte & 0x10) argbSendBit1(); else argbSendBit0();
if(Byte & 0x08) argbSendBit1(); else argbSendBit0();
if(Byte & 0x04) argbSendBit1(); else argbSendBit0();
if(Byte & 0x02) argbSendBit1(); else argbSendBit0();
if(Byte & 0x01) argbSendBit1(); else argbSendBit0();
}
}
Listing 4. Funkcja odpowiedzialna za przesłanie jednego bajtu danych interfejsu ARGB Gen2
W dalszej kolejności, by uprościć zapis danych do adresowalnych diod LED RGB, przewidziano funkcję pozwalającą na przesłanie kompletnej informacji o kolorze tejże diody LED. Ciało tej funkcji pokazano na listingu 5.
{
argbSendByte(G);
argbSendByte(R);
argbSendByte(B);
}
Listing 5. Funkcja pozwalającą na przesłanie kompletnej informacji o kolorze diody LED typu ARGB Gen2
Na razie wszystko wydaje się proste, gdyż mamy do czynienia z funkcjami niczym nieróżniącymi się od specyfikacji dobrze znanych diod WS2812. To prawda – wspomniałem przecież na wstępie, że diody ARGB Gen2 są wstecznie zgodne ze specyfikacją elementów WS2812, dlatego mogą być obsługiwane przez sterowniki starego typu. Przejdźmy zatem do zagadnień nieco bardziej skomplikowanych. Na listingu 6 pokazano ciało funkcji pozwalającej na policzenie elementów LED znajdujących się na magistrali i korzystającej z trybu odczytu protokołu ARGB Gen2.
{
uint8_t Timeout = 0;
uint8_t Leds = 0;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
//Inicjujemy tryb odczytu
ARGB_PORT_SET;
_delay_us(20);
ARGB_PORT_RESET;
_delay_us(10);
ARGB_PORT_SET;
_delay_us(50);
ARGB_PORT_RESET;
//Konfigurujemy interfejs danych, jako wejściowy, by odczytywać impulsy wysyłane przez diody LED
ARGB_PORT_AS_INPUT;
//Odczytujemy kolejne impulsy odpowiedzi wysyłane przez diody LED. Magistrala podciągnięta jest do masy rezystorem 10k
while(1)
{
//Sprawdzamy czy nie wystąpił time-out oznaczający koniec transmisji lub zupełny brak diod na magistrali
if(ARGB_PORT_IS_RESET)
{
if(++Timeout > READ_TIMEOUT) break;
_delay_us(1);
}
//Liczymy impulsy wysyłane przez diody LED by określić liczbę tychże diod na magistrali danych
if(ARGB_PORT_IS_SET)
{
++Leds;
//Czekamy na zakończenie bieżącego impulsu (stanu wysokiego)
while(ARGB_PORT_IS_SET);
_delay_us(1);
Timeout = 0;
}
}
//Konfigurujemy interfejs danych, jako wyjściowy (domyślnie)
ARGB_PORT_AS_OUTPUT;
}
return Leds;
}
Listing 6. Funkcja pozwalającą na policzenie elementów LED znajdujących się na magistrali ARGB Gen2
Dalej, na listingu 7, widnieje z kolei ciało funkcji pozwalającej na konfigurację diod LED typu ARGB Gen2.
{
uint8_t Byte1, Byte2, Byte3;
//Przygotowujemy dane do wysłania według specyfikacji słów konfiguracyjnych
Byte1 = (Config->pwmFreq<<6)|Config->fineG;
Byte2 = (Config->ovCurr<<7)|(Config->ovCurr<<6)|(Config->respType<<5)|Config->fineR;
Byte3 = (Config->ovCurr<<7)|Config->fineB;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
//Inicjujemy tryb konfiguracji
ARGB_PORT_SET;
_delay_us(20);
ARGB_PORT_RESET;
_delay_us(300);
//Wysyłamy taką samą konfigurację do wszystkich diod LED
for(uint8_t i=0; i<Leds; i++) argbSendColor(Byte2, Byte1, Byte3);
_delay_us(300);
}
}
Listing 7. Funkcja pozwalającą na konfigurację diod LED typu ARGB Gen2
Co ważne, aby stosowną konfigurację przeprowadzić w sposób szybki i łatwy, najlepiej użyć zmiennej typu configType (będącej argumentem wywołania funkcji) i predefiniowanych w pliku nagłówkowym stałych. Należy również podkreślić, że prezentowana funkcja zakłada wysłanie takiej samej konfiguracji do wszystkich diod LED na magistrali, co zwykle okaże się najbardziej pożądane. Natomiast na listingu 8 pokazano kluczową funkcję interfejsu ARGB Gen2, stosowaną w przypadku pracy magistrali danych w topologii gwiazdy – jej zadaniem jest wysłanie rozkazu sterującego (oraz opcjonalny odbiór odpowiedzi).
{
uint8_t cmdStatus = CMD_DISCARDED, Timeout = 0;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
//Inicjujemy tryb rozkazów
ARGB_PORT_SET;
_delay_us(40);
ARGB_PORT_RESET;
_delay_us(12);
//Wysyłamy rozkaz (korzystając z predefiniowanych wartośći) wraz z pożądanym adresem łańcucha LED
argbSendByte(Command|Address);
//Sprawdzamy odpowiedź łańcucha, która to powinna być wysłana prze pierwszą z diod LED w tymże łańcuchu LED (tak zwaną brakmę)
//Odpowiedź ta to impuls stanu wysokiego o długości 60uS wysłany w losowym czasie od 10us do 60uS od zbocza opadającego ostatniego
//bitu rozkazu. Konfigurujemy interfejs danych, jako wejściowy, by odczytać odpowiedź łańcucha biorąc pod uwagę ewentualny time-out
ARGB_PORT_AS_INPUT;
while(1)
{
//Sprawdzamy czy nie wystąpił time-out oznaczający niewykonanie rozkazu
if(ARGB_PORT_IS_RESET)
{
if(++Timeout > COMMAND_TIMEOUT) break;
_delay_us(1);
}
//Sprawdzamy obecność impulsu stanu wysokiego wysłanego przez bramkę łańcucha LED potwierdzającego wykonanie rozkazu
if(ARGB_PORT_IS_SET)
{
//Czekamy na zakończenie bieżącego impulsu (stanu wysokiego)
while(ARGB_PORT_IS_SET);
_delay_us(1);
cmdStatus = CMD_EXECUTED;
break;
}
}
//Konfigurujemy interfejs danych, jako wyjściowy (domyślnie)
ARGB_PORT_AS_OUTPUT;
}
return cmdStatus;
}
Listing 8. Funkcja pozwalająca na przesłanie rozkazu i opcjonalny odbiór odpowiedzi na magistrali typu ARGB Gen2
Przejdźmy teraz do założeń aplikacyjnych sterownika argbController. Przyznam szczerze, że pomysł (choć bardziej pasuje mi tu słowo „zapotrzebowanie”) na ten projekt podsunął mi mój kolega Andrzej. Używając różnego rodzaju podzespołów PC (w tym wyposażonych w podświetlenie ARGB Gen2), zauważył on brak systemu, który w zależności od wyników pomiaru temperatury sterowałby kolorem tego rodzaju podświetlenia. Istnieją co prawda systemy pozwalające zarządzać bogactwem różnych efektów RGB (np. Asus Aura Sync, Gigabyte RGB-Fusion Ready, MSI Mystic Light Sync czy ASRock Polychrome Sync), lecz nie oferują one tak pożądanej, a jednocześnie prostej funkcjonalności, jak zmiana koloru podświetlenia na skutek zmian temperatury elementu monitorowanego. Łatwo wyobrazić sobie zastosowanie takiego systemu do monitorowania stanu radiatora procesora PC lub temperatury wewnątrz obudowy typu desktop: w ramach pełnionej funkcji zmieniałby on (w sposób sugestywny) kolor podświetlenia wentylatora, dając bezpośrednią informację na temat kondycji urządzenia. To oczywiście tylko jedno z wielu możliwych zastosowań, gdyż nic nie każe nam ograniczać się do sprzętu z branży PC.
Schemat ideowy układu pokazano na rysunku 6. Sercem urządzenia jest mikrokontroler ATtiny804, taktowany wewnętrznym oscylatorem o częstotliwości równej 20 MHz i odpowiedzialny za realizację całej założonej funkcjonalności urządzenia. Jako element pomiarowy (termometr) zastosowano scalony, cyfrowy i bardzo dobrze znany element typu DS18B20 produkcji Maxim Integrated, którego obsługę zrealizowano przy użyciu programowej implementacji magistrali 1-Wire. Wybór tego elementu z szerokiej palety dostępnych termometrów scalonych podyktowany był jego dużą dostępnością i niską ceną.
Co więcej: w handlu znaleźć możemy gotowe moduły w formie wodoszczelnych sond pomiarowych, odpornych na warunki zewnętrzne, z zatopionym wewnątrz układem DS18B20, przez co wybór ten stał się jeszcze bardziej oczywisty. Ponadto mikrokontroler nasz obsługuje jednocyfrowy, 7-segmentowy wyświetlacz LED oraz dwa przyciski funkcyjne, oznaczone jako UP i DOWN, które stanowią elementarną wersję interfejsu użytkownika. W konstrukcji układu przewidziano również złącza goldpin przeznaczone do podłączenia wentylatorów (lub innych urządzeń), wyposażonych w interfejs ARGB Gen2 zasilany napięciem 5 V. Zastosowano 2 typy złączy w różnej konfiguracji sygnałów wyjściowych, typowych dla płyt głównych ASUS/ASROCK/MSI lub GIGABYTE. Do zasilania sterownika napięciem 5 V przewidziano z kolei typowe gniazdo MOLEX, stosowane w komputerach klasy PC, gdyż kable tego rodzaju – wyposażone we wtyki żeńskie – znajdują się w każdym komputerze typu desktop (i służą m.in. do zasilania napędów). Do obsługi przycisków funkcyjnych oraz programowej eliminacji drgania styków zastosowano przerwanie od przepełnienia timera TCB0 wbudowanego w strukturę mikrokontrolera, więc możliwa stała się również detekcja krótkiego i długiego naciśnięcia przycisków – bez blokowania programu obsługi aplikacji.
FREQSEL[1:0]: 10*
RSTPINCFG[1:0]: 01*
SUT[2:0]: 111*
EESAVE: 1
*ustawienie domyślne producenta
Montaż i uruchomienie
Schemat montażowy naszego urządzenia pokazano na rysunku 7. Niewielki, dwustronny obwód drukowany przeznaczony jest w zdecydowanej większości do montażu powierzchniowego, który rozpoczynamy od przylutowania mikrokontrolera. Element ten – mimo obudowy SMD – charakteryzuje się dość szerokim rozstawem wyprowadzeń, przez co nie powinien nastręczać problemów podczas lutowania. Następnie montujemy elementy bierne, wyświetlacz LED, by na końcu wlutować złącza goldpin ARGB1, ARGB2 i THERM, przyciski UP i DOWN oraz złącze zasilające MOLEX. W przypadku złącza goldpin ARGB1 należy usunąć nieużywany pin drugi od prawej (w celu zabezpieczenia przed odwrotnym podłączeniem), gdyż gniazda tego rodzaju nie przewidują jego obecności. Poprawnie zmontowany układ nie wymaga jakichkolwiek regulacji i powinien działać tuż po włączeniu zasilania i zaprogramowaniu procesora. Na fotografii tytułowej zaprezentowano wygląd zmontowanej płytki drukowanej urządzenia, widzianej od strony TOP.
Obsługa urządzenia
Obsługa sterownika argbController jest niezmiernie prosta. Do ustawienia mamy dwie wartości: temperaturę początkową i przedział temperatur, przy czym obie wartości regulowane są ze skokiem 10°C, w zakresie od 0°C do 90°C dla temperatury początkowej i od 10°C do 90°C – dla przedziału temperatur. Co ważne, na wyświetlaczu urządzenia temperatury pokazywane są w dziesiątkach °C. Na podstawie wprowadzonych ustawień urządzenie wylicza składowe kolorów RGB i wysyła je (takie same) do wszystkich diod LED we wszystkich podłączonych łańcuchach LED, w cyklach 1-sekundowych (przy czym liczbę podłączonych łańcuchów diod LED oraz liczbę samych diod LED w każdym z tych łańcuchów urządzenie sprawdza wyłącznie podczas włączania zasilania). Jak łatwo się domyślić, temperatura początkowa określa minimalną temperaturę, dla której urządzenie prześle do diod LED składowe kolorów RGB odpowiedzialne za wyświetlenie koloru niebieskiego, zaś temperatura końcowa, obliczona jako suma temperatury początkowej i przedziału temperatur, określa maksymalną temperaturę, dla której urządzenie prześle do diod LED składowe kolorów RGB odpowiedzialne za wyświetlenie koloru czerwonego. Wartości spoza zakresu obcięte zostaną do wartości skrajnych, zaś wszystkie pośrednie powodują przesłanie składowych koloru RGB obliczanych zgodnie z rysunkiem 8.
Nasuwa się pytanie, jak więc wprowadzamy niezbędne ustawienia? Urządzenie po włączeniu zasilania przechodzi do trybu bezczynności, z wyłączonym wyświetlaczem LED (w celu oszczędzania energii). Jakiekolwiek naciśnięcie przycisków funkcyjnych przełącza je w tryb regulacji temperatury początkowej (włączając jednocześnie wyświetlacz LED) – w trybie tym krótkie naciśnięcie przycisków UP lub DOWN skutkuje zmianą wartości temperatury (w pętli). Długie naciśnięcie przycisku UP powoduje z kolei przejście do trybu regulacji przedziału temperatur, w którym – jak poprzednio – krótkie naciśnięcie przycisków UP lub DOWN wywołuje zmianę bieżącej wartości (również w pętli). Długie naciśnięcie przycisku UP powoduje tym razem powrót urządzenia do trybu bezczynności i wyłączenie wyświetlacza z jednoczesnym zapisaniem ustawień w nieulotnej pamięci EEPROM mikrokontrolera, by mogły być wczytane jako startowe podczas włączania urządzenia. Co ważne, program wyposażono dodatkowo w zegar bezczynności, który po upływie 20 sekund braku reakcji ze strony użytkownika powoduje automatyczne przełączenie w tryb bezczynności i wyłączenie wyświetlacza, bez zapisywania dokonanych ustawień w pamięci EEPROM. Dodatkowo każdorazowo podczas dokonywania pomiaru temperatury (czyli co 1 sekundę) sprawdzana jest obecność czujnika temperatury i – w przypadku jego braku (lub problemów na magistrali 1-wire) – na wyświetlaczu widoczna jest informacja o błędzie w postaci litery „E” (dopóki dany problem nie ustąpi).
Robert Wołgajew, EP
- R1: 4,7 kΩ
- R2: 10 kΩ
- R3…R9: 330 Ω
- C1: 100 nF (ceramiczny X7R)
- U1: ATtiny804 (SO14)
- LED: wyświetlacz 7-segmentowy 0,3” typu OPD-S3011LE-BW (wspólna katoda)
- UP, DOWN: microswitch TACT, h=9 mm
- ARGB2, THERM: listwa kołkowa goldpin, prosta, 1×3 pin
- ARGB1: listwa kołkowa goldpin, prosta, 1×4 pin
- MOLEX: gniazdo MOLEX męskie typu C5080WR-F-04P (JOINT TECH)
- SONDA: termometr scalony typu DS18B20 (lub sonda w niego wyposażona)