SmartHub – moduł sterowania listwą LED WS2812B – podejście pierwsze

Pierwszy wpis związany z postępem prac nad projektem będzie wyjątkowo bardziej hardwarowy niż softwarowy. Dotyczy prac nad modułem sterowania listwą z indywidualnie adresowanymi diodami LED typu WS2812B, która umożliwia zapalenie każdej diody na osobny kolor. Zapraszam do czytania.

Diody RGB LED ze sterownikiem WS2812B (znane także pod nazwą Magic Led i Adafruit NeoPixel) umożliwiają tworzenie bardzo ciekawych animacji, ponieważ każdą z diod można zapalić osobno (każda może mieć ustawiony inny kolor lub być wyłączona). Przykładowe możliwości można obejrzeć na poniższych filmikach:

Dodatkowo u mnie w mieszkaniu listwa LED będzie  zamontowana w suficie podwieszanym, co już dla zwykłej listwy RGB (a nawet jednokolorowej – w tym białej) daje ciekawe efekty:

10869504_528008447341932_7646113362758256381_o 1040192_798456270222960_9034225634410026710_o
Źródło: https://www.facebook.com/perfectconstruction, https://www.facebook.com/ZAPPAranzacje/

Diody te są zintegrowane ze sterownikiem (tak naprawdę nazwa WS2812B to nazwa sterownika) i można je łączyć szeregowo. Przesyłanie odbywa się za pomocą protokołu podobnego do 1-Wire. Zaletą jest to, że przesyłanie odbywa się jednym przewodem, więc do działania wystarczy użycie jednego pinu mikrokontrolera. Podłączenie taśmy odbywa się za pomocą 3 przewodów: +5V (zasilanie), DIN (digital in – wejście danych o kolorach) i GND (masa). Diody do pracy wymagają napięcia 5V. Pojedyncza dioda przy pełnej jasności pobiera maksymalnie 60 mA, co daje 9W pobieranej mocy dla metrowej listwy zawierającej 30 diod. Każda z diod posiada 256 poziomów jasności. Liczba kolorów jaką można uzyskać to ponad 16 milionów (2^24 – 3 bajty). Diody te są naprawdę bardzo jasne.

Zasada działania magistrali jest następująca: aby zapalić diodę należy przesłać 3 bajty w kolejności GRB. Dla wielu diod należy przesłać takie paczki bajtów jedna po drugiej. Każdy ze sterowników znajdujący się w diodach pobiera z przesłanego ciągu bajtów część należącą do niego, po czym przesyła dalej ciąg pomniejszony o te 3 bajty. Dzieje się tak do momentu pojawienia się sygnału resetującego, czyli sygnału niskiego trwającego ponad 50 µs.

Sterownik ten działa z częstotliwością przesyłania danych 800 Khz. Oznacza to, że przesłanie 1 bitu trwa około 1250 ns (1,25 µs), a 1 bajtu 10 µs (0,01 ms). Przesłanie danych do 100 diod trwa około 3 ms. Moja docelowa listwa będzie miała około 160 diod – przesyłanie dla niej zajmie około 4,8 ms.
Z kolei według specyfikacji diody odświeżają się z częstotliwości nie mniejszą niż 400 Hz – oznacza to, że dioda może zmieniać swój kolor co 2,5 ms. Jak widać w moim przypadku i tak nie osiągnę takiej szybkości odświeżania – 200 fps i tak jest bardzo dobrym wynikiem. Jak się później okaże jest to tylko wynik teoretyczny – sterownik wysyłający dane traci także dodatkowy czas na wyliczenie kolorów dla animacji. Taka prędkość byłaby do osiągnięcia jedynie wtedy, gdyby sterownik cały czas zajmował się jedynie przesyłaniem gotowych danych dla animacji.

Listwy są dostępne w wariantach po 30, 60 i 144 diod na metr – w wersji zwykłej i do zastosowań w pomieszczeniach o dużej wilgotności. W użyciu jako listwa w suficie podwieszanym liczba 30 diod jest wystarczająca. Zamawiając obawiałem się, że 60 diod byłoby lepsze, ale po uruchomieniu 30 diodowej listwy okazało się, że jest to wystarczające zagęszczenie.

Opis modułu

Zadaniem modułu ma być sterowanie listwą LED. Docelowo ma umożliwiać sterowanie z poziomu głównej aplikacji umieszczonej na Raspberry Pi. Komunikacja z tą aplikacją będzie po RS485 lub WiFi. Na tym etapie nie będzie to zrealizowane. Teraz moduł będzie posiadał zamkniętą listę animacji uruchamianych po sobie. Zmiana będzie możliwa za pomocą przycisku podłączonego bezpośrednio do mikrokontrolera.

Podłączenie do Arduino

Do pierwszych testów działania listwy po podłączeniu bezpośrednio do Arduino warto użyć, krótszej listwy. Pozwoli to na użycie mniejszego zasilacza np. 2A od telefonu. Po drugie ciężko rozłożyć na biurku 5 metrową listwę.

Listwę należy podłączyć do Arduino według następującego schematu:

leds_basic

Ze względu na duży pobór prądu nawet dla 1 metrowej listwy należy użyć zewnętrznego zasilania. Zasilanie z Arduino pozwoli jedynie na zapalenie 3 diod w pełnej jasności (ok. 180 mA).

Masę zasilacza (-) łączymy z masą mikrokontrolera (GND) i masą listwy (GND). Moja listwa miała przewód od masy w kolorze białym. Są także listwy, które mają przewód od masy czarny.

Plus zasilacza łączymy z przewodem zasilającym listwy (oznaczony na listwie +5V). Przewód zasilający listwy jest koloru czerwonego.

Ostatnim krokiem jest podłączenie pinu 6 mikrokontrolera z przewodem DIN listwy. Przewód ten jest koloru zielonego. Trzeba uważać, żeby nie podłączyć listwy odwrotnie – po drugiej strony listwy w miejscu wejścia szyny danych DIN jest jej wyjście (potrzebne właśnie do szeregowego łączenia diod). Można użyć innego pinu PWM mikrokontrolera (oznaczony znakiem ~), ale ten jest domyślnie wpisany w przykładach biblioteki Adafruit NeoPixel, którą wykorzystamy.

Wszystkie powyższe połączenia najlepiej robić przy odłączonym od USB mikrokontrolerze i odłączonym od prądu zasilaczu. Jeżeli chcecie to robić przy podłączonym zasilaniu to należy najpierw podłączyć masy, a dopiero potem pozostałe przewody. W przeciwnym wypadku można łatwo spalić pierwszą diodę – mi to się nie udało (jak na razie :-)). Odłączając listwę należy odłączać przewody w odwrotnej kolejności.

Po podłączeniu wszystkiego można zainstalować bibliotekę Adafruit NeoPixel w Arduino IDE: https://github.com/adafruit/Adafruit_NeoPixel. Nie będę opisywał instrukcji instalacji biblioteki oraz samego Arduino IDE, ponieważ łatwo wyszukać te informacje – instrukcja instalacji biblioteki znajduje się pod podanym linkiem.

Kolejnym krokiem jest uruchomienie Arduino IDE i wybranie przykładu: Adafruit NeoPixel -> strandtest. W zależności od ilości diod podłączonej listwy należy odpowiednio zaktualizować parametr odpowiadający za liczbę diod w linii 16 tego przykładu:

Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800);

Jak widać w przykładzie wpisana jest liczba 60. Należy ją odpowiednio zaktualizować.

Po tej zmianie można wgrać szkic do Arduino. Po chwili diody na listwie powinny ożyć i wyświetlać fajne zmieniające się animacje. Efektu z uruchomienia przykładu dla 5 metrowej listwy na zdjęciu poniżej:

ws2812-8

Czas na przeniesienie projektu

Skoro na płytce stykowej wszystko działa można przejść do przenoszenia projektu na płytkę prototypową. Nie chce mi się bawić z wytrawianiem własnej płytki. Przy okazji poćwiczyłem lutowanie. Jak będę miał ostateczny projekt na płytce prototypowej to zlecę wykonanie płytki jakiejś firmie na podstawie projektu.

Początkowo kupiłem 2 mikrokontrolery Attiny: 13A i 2313A, oba w wersji przewlekanej (końcówka PU), na które chciałem przenieść projekt. Kupiłem te 2, bo wcześniej widziałem tutoriale ich dotyczące (Link 1 i Link 2). Chciałem użyć tego mniejszego, bo zajmie mniej miejsca na płytce, ale jakby był za słaby, to miałem w razie czego tego większego. Nie sprawdzałem ich parametrów, bo nie chciałem tracić czasu na zapoznawanie się z tym tematem – po prostu chciałem od razu zmierzyć się z zadaniem przeniesienia projektu.

Rozpocząłem od przenoszenia projektu na Attiny13A. Konfiguracja Arduino pod tę płytkę sprawiła mi trochę problemów, ponieważ instrukcja z linku pierwszego dotyczyła wersji 1.0.x, a ja miałem wersję 1.6.7. Mogłem ściągnąć starsze Arduino, ale chciałem skorzystać z aktualnego. Wrzuciłem pliki z instrukcji do analogicznych folderów nowej wersji Arduino. Po kilku próbach nowa płytka pojawiła się na liście dostępnych płytek. Niestety podczas prób kompilacji szkicu lub wgrania bootloadera na płytkę pojawiały się błędy. Wtedy znalazłem bibliotekę działającą z nową wersją Arduino, która dodawała wsparcie dla kilku mikrokontrolerów Attiny: https://github.com/damellis/attiny. Biblioteka nie obsługiwała żadnego z posiadanych przeze mnie mikrokontrolerów, ale mogłem podejrzeć zawartość pliku boards.txt w tej bibliotece i na podstawie niego poprawić błędy kompilacji. Gdy błędy związane z brakującymi wpisami w pliku boards.txt zostały naprawione spróbowałem wgrać bootloader. Niestety wtedy też pojawił się błąd, tym razem inny, związany z błędną sygnaturą urządzenia. Upewniłem się, że dobrze podłączyłem mikrokontroler do Arduino i spróbowałem jeszcze kilka razy. Nic to nie dało, więc spróbowałem wgrać przykładowy szkic – blink (mruganie diodą). Okazało się, że wgrało się pomyślnie. Wygląda na to, że podczas którejś próby bootloader się wgrał, ale kolejne próby się wywalały. Korzystając z tego przykładu zidentyfikowałem, która nóżka mikrokontrolera jaki to pin. Po tym przeszedłem do wgrania przykładu strandtest. Niestety szkic był za duży. Zajmował ponad 2 Kb pamięci. Attiny 13a ma tylko 1 Kb pamięci flash. Próbowałem usunąć jak najwięcej kodu, żeby cokolwiek się zmieściło, ale nawet po skasowaniu wszystkich animacji szkic zajmował ponad 2 Kb. Tego dnia zakończyłem próby przeniesienia szkicu na mikrokontroler.

Attiny 2313A ma z kolei 2 Kb pamięci flash, ale to nadal było za mało. Chciałem tylko sprawdzić działanie płytki na podstawowym przykładzie mrugania diodą. Tu z podłączeniem poszło szybciej ze względu na doświadczenie zdobyte przy podłączeniu poprzedniego mikrokontrolera. Wszystko przeszło poprawnie i nawet bootloader się sprawnie załadował – bez błędów. Nawet później dodałem wsparcie dla tego mikrokontrolera do wcześniej wspomnianej biblioteki: https://github.com/czesiu/attiny/tree/ide-1.6.x-boards-manager. Nie obyło się bez zgłoszenia błędu w Arduino: https://github.com/arduino/Arduino/issues/4595.

Jako, że już miałem tę bibliotekę obcykaną to sprawdziłem ile pamięci mają mikrokontrolery przez nią wspierane. Attiny 45/85 mają odpowiednio 4 i 8 Kb pamięci flash. Do tego są rozmiaru małego Attiny 13a. Attiny 44/84 mają tyle samo pamięci flash, ale są większe – mają 14 nóżek zamiast 8. Więc kupiłem Attiny 85 i po tym już wszystko zadziałało. Przynajmniej na tym etapie 🙂

Teraz mogłem przejść do kolejnego etapu jakim jest projektowanie płytki. Adafruit zaleca dodanie kondensatora elektrolitycznego 1000µF 6,3V do filtrowania zasilania oraz rezystora około 480 om do ograniczenia prądu płynącego do szyny danych.

Kondensator ma zapobiec skokom napięcia zasilacza i wygładzić je – zazwyczaj mogą się pojawić w momencie odpalania zasilacza i zwłaszcza dla gorszych zasilaczy. Jednak warto go zastosować, żeby nie upalić pierwszej diody, tym bardziej, że parametry zasilacza po czasie ulegają pogorszeniu. Ja kupiłem kondensator o odpowiedniej pojemności, ale napięciu 10V. W przypadku kondensatorów ważne jest, aby minimalna wartość zasilania została zachowana. Ten kondensator może się naładować maksymalnie do 10V, ale w tym wypadku zostanie naładowany tylko do 5V (bo takie jest napięcie zasilacza). Kondensator elektrolityczny należy odpowiednio podłączyć – nóżka od minusa jest oznaczona prostokątem na nadruku kondensatora:

kondensator minus

Z kolei zadaniem rezystora podłączonego między pinem mikrokontrolera, a linią danych listwy jest ograniczenie przepływającego prądu – przyda się w przypadku spięć lub przepięć. Rezystor nie ogranicza napięcia, a jedynie prąd. Za duży prąd mógłby także spalić pierwszą diodę. Ja kupiłem rezystor 360 om, bo także będzie dobry do tego zadania.

Więcej informacji odnośnie zaleceń dotyczących tej listwy LED można znaleźć tutaj.

Dobry projekt płytki powinien także zawierać rezystor podciągający (pull up) dla pinu RESET mikrokontrolera – zapobiegnie to przypadkowym restartom podczas dotykania płytki. RESET w mikrokontrolerze zachodzi, gdy na tym pinie choć przez chwilę pojawi się sygnał niski. Jak nie jest do niczego podłączony to sam mikrokontroler ustala na nim stan wysoki. Dotknięcie płytki może spowodować zwarcie go do masy i restart. Warto także nieużywane piny odpowiednio podłączyć, ale ponieważ projekt jest jeszcze w fazie prototypu to tego nie robiłem. Więcej informacji o prawidłowym podłączeniu mikrokontrolera można znaleźć tutaj.

Poniżej lista zakupionych elementów potrzebnych do przeniesienia projektu:

  • 1 x Płytka prototypowa – u mnie była to płytka uniwersalna dwustronna (30x70x1,5mm) – 4 zł
  • 1 x Kondensator elektrolityczny 1000 uF 10V – do filtrowania zasilania, omówiony wcześniej – 0,80 zł
  • 1 x Rezystor 360 om – ograniczenie prądu DIN – 0,02 zł
  • 1 x Rezystor 10 kilo om – pull up resetu – 0,02 zł
  • 1 x Gniazdo DC 2,1/5,5 do montażu na płytce – 1,50 zł
  • 1 x Podstawka 8 pin pod mikrokontroler – pozwoli na wyjmowanie mikrokontrolera w celu przeprogramowania – 0,10 zł
  • 1 x Mikrokontroler Attiny 85 – 6,50 zł
  • 1 x Obudowa KM-22 (W: 22, S: 60, D: 84) – pasująca do płytki prototypowej, o odpowiedniej wysokości – 5 zł

Teraz można było przejść do lutowania. Okazuje się, że to nie jest takie trudne nawet przy tak małych elementach – wcześniej lutowałem o wiele większe elementy np. kabel do głośnika i podobne. Poniżej zdjęcia płytki po zlutowaniu:

ws2812b-1 ws2812b-4
ws2812b-2 ws2812b-3 ws2812b-5

Po zmontowaniu płytki i podłączeniu do niej metrowej listwy wszystko działało. Nareszcie. W między czasie zrobiłem obsługę przycisku zmieniającego lub wyłączającego animację bazując na drugim przykładzie z biblioteki Adafruid NeoPixel: buttoncycler. Dodałem wyłączanie listwy po dłuższym przyciśnięciu przycisku. Zwykłe przyciśnięcie przełączało animacje.

Niestety była to zbyt przedwczesna radość. Po zaktualizowaniu programu na 5 metrową listwę (150 diod) wszystko przestało działać. Chwilę zajęło mi dojście do przyczyny problemu, ponieważ zmiana ilości diod w programie to nie jedyna zmiana jaką zrobiłem. Wróciłem do testów na metrowej listwie i okazało się, że wszystko działa. Potem krok po kroku zwiększałem liczbę diod testując na dłuższej listwie. Maksymalną działającą ilością diod było 110.

Po tych testach doszedłem do tego, że przyczyną niedziałania dla większej ilości diod jest brak pamięci RAM. Zgodnie z dokumentacją na stronie Adafruit zużycie pamięci RAM na każdą diodę to 3 bajty. Więc dla 150 diod byłoby to 450 bajtów. Attiny 85 posiada 512 bajtów pamięci SRAM. Teoretycznie powinno starczyć pamięci RAM, tym bardziej, że problem występował już przy liczbie przewyższającej 110 diod, czyli 330 bajtów. Ciężko mi było także oszacować ile pozostałej pamięci zużywa aplikacja, a sposoby programowe na wypisanie wolnej pamięci wydały mi się za skomplikowane (wymagałyby dodania biblioteki SoftwareSerial, służacej do przesyłania danych, której wcześniejsze moje testy zakończyły się porażką).

Wtedy rozpocząłem poszukiwania rozwiązania w internecie. Znalazłem artykuł, który opisywał rozwiązanie podobnego problemu – animacja tysiąca diod z Arduino, które miało tylko 1 Kb pamięci RAM. Adafruit NeoPixel nie zadziałałby w tym wypadku, ponieważ potrzebne by było prawie 3 Kb pamięci RAM.

Wgrałem więc kod z artykułu do Attiny – nie zadziałał, choć małe delikatne mrugnięcia się pojawiały. Zmieniłem czasy transmisji bitów protokołu listwy (T0H, T1H, T0L i T0H), bo wydały mi się za duże w porównaniu z dokumentacją i wtedy chociaż pierwsze kilkanaście diod zaczęło poprawnie mrugać.

Normalnie pewnie bym w tym momencie zrezygnował i stwierdził, że to rozwiązanie jest słabe oraz, że przy podejściu bez buforowania nie da się zrobić płynnej animacji i szukałbym dalej lepszego. Jednak razem z artykułem był filmik udowadniający, że to działa i animacja jest całkiem zacna – ten filmik umieściłem także we wstępie tego artykułu (jako drugi). Wróciłem więc do czytania artykułu – ten z hackaday to był tylko skrót. Pełny artykuł opisywał więcej szczegółów. Moją uwagę zwrócił opis czasów transmisji bitów oraz częstotliwości pracy mikrokontrolera. Arduino z artykułu pracowało z częstotliwością 16 Mhz, a moje Attiny pracowało na wewnętrznym oscylatorze 8 Mhz. Oscylator ten nie jest tak dokładny jak zewnętrzny kwarc, ale w podejściu z buforowaniem jest wystarczający.

Zakupiłem więc kwarce 16 Mhz i 20 Mhz, bo z takimi może współpracować moje Attiny. Pierwsze testy z kodem z artykułu nie zadziałały dla obu. Niestety nie miałem zapisanej wersji ze zmodyfikowanymi czasami transmisji, które wcześniej zadziałały dla części diod. Próbowałem z pamięci zmodyfikować je na podobne wartości i nareszcie pojawił się sukces. Zadziałało już dla kwarcu 16 Mhz. O tym jak podłączyć zewnętrzny kwarc można poczytać tutaj.

Poniżej lista dodatkowych elementów, które trzeba było dołożyć do płytki, z tego powodu:

  • 2 x kondensator ceramiczny 20 pF – 0,05 zł
  • 1 x kwarc 16 Mhz niski – 1 zł
  • 1 x listwa precyzyjna 32 pin – aby można było podmieniać kwarc na płytce – 2,50 zł

Musiałem przeprojektować płytkę i dolutować nowe elementy. Poniżej zdjęcia:

ws2812b-10 ws2812b-11
ws2812b-12 ws2812b-13 ws2812b-14

Po zmontowaniu i testach na 5 metrowej listwie wszystko działało jak należy. Nie zdążyłem jeszcze zrobić obsługi przycisku, który zmienia / wyłącza animacje, ale to zrobię w niedługim czasie. W niedługim czasie także zamontuję listwę w suficie podwieszanym i wtedy napiszę wpis pokazujący działanie modułu w docelowym środowisku.

Podsumowanie

Razem koszt elementów modułu wyniósł niecałe 22 zł. Do tego koszt zasilacza i 6 metrów listwy. Oraz jakieś kable, które miałem. Razem koszt tej części projektu wyniósł około 150 zł, głównie ze względu na to, że listwa i zasilacz były zamawiane bezpośrednio od producenta, czyli z Chin.

Mikrokontrolery, które nie przydały się w tym momencie pewnie posłużą do innych zadań np. jako bezprzewodowy włącznik oświetlenia lub energooszczędny moduł pomiaru temperatury i wilgotności.

W kolejnych wpisach dotyczących modułu będzie o komunikacji RS485, może coś z WiFi z wykorzystaniem ESP8266, a także może coś związanego z aktualizacją mikrokontrolera na taki z większą ilością pamięci SRAM – czyli już z jakąś Atmegą.

Źródła:

  • https://learn.adafruit.com/adafruit-neopixel-uberguide/basic-connections
  • http://www.jarzebski.pl/arduino/komponenty/diody-led-ze-sterownikem-ws2811-ws2812.html
  • http://mirekk36.blogspot.com/2012/04/mikrokontroler-prawidowe-zasilanie.html

2 komentarze


  1. Wow. Podziwiam i gratuluję. Do takiego typu projektów chciałbym i ja kiedyś dojść 🙂

    Odpowiedz

    1. Dzięki 🙂 Rozumiem, że chodzi o część elektroniczną i tworzenie samej płytki? Bo kodu za bardzo nie opisywałem (przynajmniej na razie).

      Odpowiedz

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *