Skocz do zawartości

Miganie diodami z własnej funkcji


Pomocna odpowiedź

Rzeczywiście, tak szybko, że nic nie rozumiem. Ale pewnie za cienki jestem albo za słabo się staram. Po prawdzie nie staram się w ogóle...

Kolega encelados (i wielu innych w tym dziale) dopiero zaczyna, jest bardzo początkującym hobbystą a Ty powinieneś brać to pod uwagę jeśli chcesz być dobrze zrozumiany. Albo poświęć na swoje pisanie więcej uwagi i zrób to porządnie, albo.. nawet ten krótki czas zmarnujesz. Nie planuję żadnego multipleksowania ani oporników o różnych wagach podłączać, bo to nie te czasy, nie ten "target" i nie takie założenia.

Rozumiem, że nie chciało Ci się przeczytać całego wątku więc możesz nie być zorientowany o co chodzi. Specjalnie dla Ciebie przypomnę w skrócie: ma być jeden (słownie: jeden) semafor z kilkoma (8?) komorami czyli diodkami LED. Udało mi się namówić enceladosa na użycie inteligentnych LEDów RGB z wbudowanym sterownikiem szeregowym. Tu nie trzeba żadnych drabinek czy regulowanych źródeł prądowych (ja bym tak zrobił zamiast zabawy w oporniki) więc rozwiązanie wydaje się prostsze niż DAC budowany na piechotę. Wystarczy jeden pin procesora i daisychain od jednej diodki do drugiej - dostajesz 256 poziomów na każdą składową RGB w każdej diodce niezależnie. Proste jak cep, prawda? To tyle w kwestii szczegółów budowy. Skupmy się na oprogramowaniu. Jak rozumiem proponujesz tablice. To fajnie, bo zdaje się Autor wątku właśnie doczytał co to jest i z pewnością chętnie zobaczy je w programie. Wcześniej nie miało to sensu więc i przykłady musiały być skrajnie trywialne. Wyobrażasz sobie, że są ludzie umiejący mniej niż Ty? (tak na szybko?).

------------------------------------------------------------------------------

[ Dodano: 25-03-2016, 22:10 ]

Inteligentne diody LED RGB pojawiły się całkiem niedawno a już(?) zdążyły zrobić karierę. Ktoś bowiem wpadł na pomysł, że można układ sterujący trzema kolorami czerwonym, zielonym i niebieskim wbudować do wnętrza obudowy diody. Dostajemy zatem element zasilany jak wiele układów cyfrowych wprost z 5V i mający wbudowany sterownik PWM umożliwiający programowanie z zewnątrz jasności każdej z trzech diod LED niezależnie. Niestety, obowiązuje kilka standardów sterowania takimi diodami: starszy dwuprzewodowy i nowszy - jednoprzewodowy oraz chyba ze trzy różne protokoły szeregowe. Nie wszystkie są równie popularne a z tego co widzę oferowane są albo diody/taśmy ze sterownikami WS2801/21 (dwa kabelki z procesora) i WS2812 (jeden kabelek sterujący):

http://botland.com.pl/650-paski-led-adresowane

Wiele firm sprzedaje te diodki jako pojedyncze sztuki lub w zestawach o różnych kształtach i wielkościach (nie pracuję w żadnej z tych firm 🙂 ):

http://www.tme.eu/pl/katalog/#search=ws2812&s_field=accuracy&s_order=DESC

https://www.adafruit.com/category/168

Ja postanowiłem skupić się na diodkach wyposażonych w kontroler 2812:

https://www.adafruit.com/datasheets/WS2812.pdf

https://www.adafruit.com/datasheets/WS2812B.pdf

Właściwie gdy przejrzy się powyższe strony katalogowe to wszystko staje się jasne. Każda obudowa ma 4 (lub 6 w wersji S) nóżki. Dwie z nich podłączamy do zasilania: VCC do +5V, VSS do GND. Na wejście DI podajemy odpowiednio spreparowany sygnał cyfrowy z procesora i.. gotowe. Jeśli ktoś zechce mieć więcej punktów świetlnych (każdy oczywiście niezależnie sterowany), robi łańcuch jak na choinkę: wyjście DO jednej diody łączy z wejściem DI następnej. Zasilanie rozprowadza do wszystkich równolegle i tyle. Zestaw musi być łańcuchem tylko z punktu widzenia drogi sygnału sterującego. Diodki można przecież ułożyć w prostokątny ekran (matrycę), okrąg, napis czy cokolwiek bądź.

Warto pamiętać, że diody WS2812 są naprawdę jasne. Wysterowując dany kolor na maxa praktycznie nie da się na niego patrzeć z odległości mniejszej niż 2m a załączone wszystkie kolory razem - dające wtedy światło białe - po prostu boleśnie rażą w oczy. Jedna obudowa może pobierać prąd do 60mA więc budując większe zestawy warto pamiętać o przyzwoitym zasilaczu. Ekran 8x8=64 pixelowy będzie pobierał w szczycie ponad 3.8A! Tak więc do biednego Arduino UNO nie powinno się podłączać więcej jak 5 diodek gdy zasilamy to z USB i może z 8-10 gdy mamy zasilacz zewnętrzny np. 9V i korzystamy z pokładowego stabilizatora +5V. Na tej stronie opisano eksperymenty z transmisją sygnałów (i błędami) przez łańcuchy 5 i 10 tys. punktów:

http://hypnocube.com/2013/12/design-and-implementation-of-serial-led-gadgets/

A teraz najciekawsze czyli jak tym sterować. W szczegółach wszystko jest opisane w danych katalogowych a w skrócie? Każdy pixel potrzebuje 3x8 bitów. Dzięki takiej ilości informacji ładowanej do sterownika diody, każdy kolor może mieć jedną z 256 jasności. Zero to oczywiście ciemność, 255 to jasność maksymalna.

A łańcuch? Protokół przesyłania informacji jest tak pomyślany, że dłuższa przerwa w transmisji danych oznacza początek przesyłania. Każdy sterownik pixela włączonego w łańcuch "pochłania" pierwsze 24 bity jakie do niego przyjdą po takiej dłuższej przerwie. Gdy już się "nasyci" swoimi bitami, resztę tego co przychodzi na wejście przesyła dalej. Jeżeli więc zbudujemy łańcuch z 3 diodek i wyślemy informację składającą się np. z 72 bitów, to pierwsze 24 trafią do pixela najbliższego procesorowi, następne 24 do kolejnego a ostatnie 24 bity znajdą się w sterowniku ostatniego punktu. Nie trzeba żadnych adresów, sygnałów wyboru układu itd. Proste prawda?

Każdy bit wysyłany jest jako impuls pewnej ustalonej długości i następująca po nim przerwa, szczegóły w danych katalogowych. Nie ma chyba sensu pisać machania pinami samodzielnie tym bardziej, że są gotowe biblioteki. Ja użyłem tej z Adafruit - po prostu działa.

Jako pierwszy - w celach zapoznawczych z ideą używania tej biblioteki wrzucam program, który na 5-pixelowym pasku statycznie zapali następujące kolory: czerwony, zielony, niebieski, czarny, biały:

#include <Adafruit_NeoPixel.h>

#define LICZBA_DIODEK  5
#define PIN_RGB        6

Adafruit_NeoPixel
 pasek = Adafruit_NeoPixel(LICZBA_DIODEK, PIN_RGB, NEO_GRB + NEO_KHZ800);

void setup()
{
 pasek.begin();

 pasek.setPixelColor(0, pasek.Color(100,0,0));
 pasek.setPixelColor(1, pasek.Color(0,100,0));
 pasek.setPixelColor(2, pasek.Color(0,0,100));

 pasek.setPixelColor(4, pasek.Color(150,150,150));

 pasek.show();
}

void loop()
{}

Idea jest taka:

1. Konstruktor klasy (Adafruit_NeoPixel) najważniejsze co robi to rezerwuje sobie gdzieś w pamięci RAM procesora obszar w którym będzie przechowywał stan każdej diodki w pasku. Ponieważ dostaje od nas liczbę diodek to "wie" ile pamięci zarezerwować. Każdy pixel potrzebuje 3 bajtów, bo ma trzy składowe: R, G i B.

2. Za pomocą funkcji setPixelColor() ustawiamy kolor wskazanego pixela, ale na razie tylko w pamięci RAM procesora. Funkcja Color() zamienia trzy osobne składowe RGB (każda z zakresu 0-255) na jedną liczbę składowaną w pamięci. Dla przykładu: Color(100,0,0) oznacza czerwony na jasność 100 a zielony i niebieski wyłączone.

3. Dopiero po przygotowaniu w pamięci wyglądu całego paska, "wypluwamy" jego stan z RAMu do rzeczywistych diodek funkcją show()

Proste, prawda? Uwagi jakie od razu się nasuwają są następujące:

a. Pożerany jest RAM w procesorze. I tak mamy go mało (2K), a tutaj jeszcze 3 bajty na każdy pixel. Za dużo to mała ATmega328 ich nie wysteruje, choć 400 i tak jest niezłym wynikiem 🙂

b. Czas wykonania funkcji wysyłającej show() jest proporcjonalny do liczby pixeli. Ponieważ format bitów i całej ramki jest określony przez producenta diodek, nie ma zmiłuj: wysłanie 100 pixeli (2400 bitów) zajmie procesorowi jakieś 3.1ms. Niby niedużo, ale jest to działanie blokujące wszystko inne, nawet przerwania. Warto pamiętać. Z drugiej strony, jeżeli nie robimy kostki 12x12x12 lub ekranu 320x240 🙂 to dla krótkich, kilkudiodowych pasków czasy będą nieznaczące, a większość czasu będzie zajmowało wygenerowanie koniecznej przerwy przed lub po ramce danych.

  • Lubię! 1
Link to post
Share on other sites

No tak, to był trywialny przykład odpalenia 5-punktowego paska LEDów RGB i wprawka do używania biblioteki Adafruit. Czas na coś ciekawszego. Dziś mam trochę dłuższy program, który udaje prawdziwy semafor. Nie traki kolejowy, bo na tych się nie znam, tylko drogowy - samochodowy. Definiuję sobie 5 diodek w pasku, ale program używa tylko trzech. Pozostałe zostają przez cały czas w stanie dziewiczym, czyli czarnym.

Dla ułatwienia i przejrzystości programu zrobiłem sobie kilka 32-bitowych stałych, które przechowują 8 podstawowych kolorów. Na początku programu zdefiniowana jest jasność pojedynczego koloru (JAS_MAX). Można ją podkręcić aż do 255, ale do zabawy na biurku 60 i tak jest wystarczające. W przypadku gdy świecą dwa na raz (np. w żółtym: R+G) byłoby wyraźnie jaśniej niż dla pojedynczego (np. czerwonego), dlatego składowe kolorów podwójnych są trochę przyciemnione (JAS_POL) a dla białego - w którym świecą wszystkie trzy kolory, jasność składowych jest jeszcze mniejsza (JAS_TRZY). Oczywiście można z tym eksperymentować do woli. Np. zauważyłem, że żółty składający się po równo z czerwonego i zielonego nie jest ładny. Trzeba by tu trochę z zawartością składowych poeksperymentować.

Do przechowywania wzorców świecenia w każdym stanie użyłem tablicy dwuwymiarowej semafor[][]. Pierwszy indeks jest numerem stanu semafora. Drugi indeks jest numerem diodki (pixela) w semaforze. Z tablicy odczytuję zatem stan wszystkich 5 diodek dla każdego stanu semafora. Żeby jednak to mogło zadziałać, w funkcji setup() muszę tablicę nafaszerować danymi początkowymi. Potem jest już łatwo: główna pętla programu to wywoływane w kółko trzy funkcje:

- sprawdz_klik() która oddaje kod zdarzenia (jeżeli wystąpiło) lub zero gdy nic się nie wydarzyło,
- analizuj_klik() która na podstawie otrzymanego kodu zdarzenia zmienia stan semafora,
- ustaw semafor, która ustawia kolory wszystkich diodek w pasku na podstawie aktualnego stanu semafora.

Dalej jest już tylko wypchnięcie nowych (lub tych samych) kolorów na pasek i oczekiwanie 10ms.

Pamiętajcie, że żadna z funkcji wywoływanych w głównej pętli nie może być blokująca. Każda musi się wykonywać w maksymalnie krótkim czasie, nie może na nic czekać i musi wracać najszybciej jak umie - to cała idea tzw. non-preemptive scheduling. Każda funkcja zajmuje tak mało czasu procesora jak tylko może umożliwiając "życie" kolejnym funkcjom. Inaczej cały program zwiśnie. Taktem pracy takiego systemu jest właśnie jedyny delay() w pętli głównej. Wkrótce i jego się pozbędziemy 🙂

Nie robiłem już obsługi prawdziwych przycisków, bo nie każdy ma dołączone do swojego Arduino jakieś switche. Wykorzystałem za to port szeregowy i oczekuję na znaki tamtędy przychodzące. Na ich podstawie rozpoznaję dwa zdarzenia: 'N' - oznaczające popchnięcie stanu semafora o 1 do przodu i 'P' - cofające stan o 1. Myślę, że zrozumienie działania poniższego programu nikomu nie powinno sprawić trudności:

#include <Adafruit_NeoPixel.h>

#define LICZBA_DIODEK  5
#define PIN_RGB        6

#define JASN_MAX       60
#define JASN_POL       (JASN_MAX/2)
#define JASN_TRZY      (JASN_MAX/3)

#define LICZBA_STANOW  4

Adafruit_NeoPixel
 pasek = Adafruit_NeoPixel(LICZBA_DIODEK, PIN_RGB, NEO_GRB + NEO_KHZ800);

const uint32_t
 color_black   = pasek.Color(0, 0, 0),
 color_red     = pasek.Color(JASN_MAX, 0, 0),
 color_green   = pasek.Color(0, JASN_MAX, 0),
 color_blue    = pasek.Color(0, 0, JASN_MAX),
 color_yellow  = pasek.Color(JASN_POL, JASN_POL, 0),
 color_cyan    = pasek.Color(0, JASN_POL, JASN_POL),
 color_magenta = pasek.Color(JASN_POL, 0, JASN_POL),
 color_white   = pasek.Color(JASN_TRZY, JASN_TRZY, JASN_TRZY);

uint32_t
 semafor[LICZBA_STANOW][LICZBA_DIODEK];

uint8_t
   klik,
   stan = 0;

// ============================= SETUP ================================
void setup()
{
 semafor[0][0] = color_red;
 semafor[0][1] = color_black;
 semafor[0][2] = color_black;

 semafor[1][0] = color_red;
 semafor[1][1] = color_yellow;
 semafor[1][2] = color_black;

 semafor[2][0] = color_black;
 semafor[2][1] = color_black;
 semafor[2][2] = color_green;

 semafor[3][0] = color_black;
 semafor[3][1] = color_yellow;
 semafor[3][2] = color_black;

 pasek.begin();
 Serial.begin(9600);
}
// =========================== MAIN LOOP ==============================
void loop()
{
 klik = sprawdz_klik();
 analizuj_klik(klik);
 ustaw_semafor(stan);
 pasek.show();
 delay(10);
}
// --------------------------------------------------------------------
void ustaw_semafor(uint8_t numer_stanu)
{
 for(uint8_t led = 0; led < LICZBA_DIODEK; led++)
   pasek.setPixelColor(led, semafor[numer_stanu][led]);
}
// --------------------------------------------------------------------
uint8_t sprawdz_klik(void)
{
 uint8_t ch = 0;
 if (Serial.available())
   ch = Serial.read();
 return ch;
}
// --------------------------------------------------------------------
void analizuj_klik(uint8_t klik_code)
{
 switch (klik_code)
 {
   case 'N':
     if (++stan >= LICZBA_STANOW)
       stan = 0;
     break;
   case 'P':
     if (stan-- == 0)
       stan = LICZBA_STANOW-1;
     break;
 }
}

Acha, elementy tablicy semafor[][] są 32-bitowe, bo 3 kolory po 8 bitów każdy to 24 bity a takich 'intów' w C nie ma. Najbliższe większe mamy 32-bitowe i takich użyłem do przechowywania kolorów diodek. W następnych wersjach programu zrobię to lepiej, a jutro będziemy mrugać dowolną liczbą diodek w dowolnym stanie semafora 🙂

----------------------------------------------------------------------------------------

[ Dodano: 28-03-2016, 00:04 ]

Dziś - tak jak zapowiadałem - nauczymy nasz semafor mrugać. Zgodnie z tą samą co poprzednio ideą trzeba tak modyfikować funkcje, by te na nic nie czekały. Na szczęście istnieje w systemie pomiar czasu - jest nim przecież pętla główna wołająca każdą funkcję co 10ms. Dzięki temu każda z nich "wie", że wykonuje się 100 razy na sekundę i to wykorzystałem do zrobienia mrugania.

Sama modyfikacja jest bardzo prosta:

1. Stworzyłem tablicę zawierającą tyle 1-bitowych znaczników ile mamy stanów razy liczba diodek. Na początku wpisuję do niej, w odpowiednią pozycję wartość true - każdy może wpisać swoje znaczniki mrugania w inne miejsca tej tablicy. Warunek jest jeden: mrugająca diodka musi mieć w tym stanie jakiś kolor różny od czarnego, bo mruganie..

2. .. polega na okresowym wygaszaniu diodki, tj. zamianie jej domyślnego koloru na czarny. Okres mrugania zrobiłem na liczniku obracanym w funkcji ustaw_semafor() która do tej pory po prostu przepisywała kolory diodek z wzorca semafor[][] do stanu paska przechowywanego gdzieś w bibliotece Adafruit. Wystarczyło dodać warunek, by czasami diodka dostawała kolor czarny 🙂 i proszę, mrugają:

#include <Adafruit_NeoPixel.h>

#define LICZBA_DIODEK  5
#define PIN_RGB        6

#define JASN_MAX       60
#define JASN_POL       (JASN_MAX/2)
#define JASN_TRZY      (JASN_MAX/3)

#define LICZBA_STANOW  4

#define OKRES_MRUGANIA  30

Adafruit_NeoPixel
 pasek = Adafruit_NeoPixel(LICZBA_DIODEK, PIN_RGB, NEO_GRB + NEO_KHZ800);

const uint32_t
 color_black   = pasek.Color(0, 0, 0),
 color_red     = pasek.Color(JASN_MAX, 0, 0),
 color_green   = pasek.Color(0, JASN_MAX, 0),
 color_blue    = pasek.Color(0, 0, JASN_MAX),
 color_yellow  = pasek.Color(JASN_POL, JASN_POL, 0),
 color_cyan    = pasek.Color(0, JASN_POL, JASN_POL),
 color_magenta = pasek.Color(JASN_POL, 0, JASN_POL),
 color_white   = pasek.Color(JASN_TRZY, JASN_TRZY, JASN_TRZY);

uint32_t
 semafor[LICZBA_STANOW][LICZBA_DIODEK];

uint8_t
 klik,
 stan = 0;

boolean
 mruganie[LICZBA_STANOW][LICZBA_DIODEK];

// ============================= SETUP ================================
void setup()
{
 semafor[0][0] = color_red;
 semafor[0][1] = color_black;
 semafor[0][2] = color_black;
 semafor[0][3] = color_black;
 semafor[0][4] = color_green;

 semafor[1][0] = color_red;
 semafor[1][1] = color_yellow;
 semafor[1][2] = color_black;
 semafor[1][3] = color_black;
 semafor[1][4] = color_green;

 semafor[2][0] = color_black;
 semafor[2][1] = color_black;
 semafor[2][2] = color_green;
 semafor[2][3] = color_red;
 semafor[2][4] = color_black;

 semafor[3][0] = color_black;
 semafor[3][1] = color_yellow;
 semafor[3][2] = color_black;
 semafor[3][3] = color_red;
 semafor[3][4] = color_black;

 mruganie[1][4] = true;          // W stanie 1 bedzie mrugac diodka nr 4
 mruganie[3][3] = true;          // a w stanie 3 diodka nr 3 :)

 pasek.begin();
 Serial.begin(9600);
}
// =========================== MAIN LOOP ==============================
void loop()
{
 klik = sprawdz_klik();
 analizuj_klik(klik);
 ustaw_semafor(stan);
 pasek.show();
 delay(10);
}
// --------------------------------------------------------------------
void ustaw_semafor(uint8_t numer_stanu)
{
 static uint8_t
   licznik_mrugania;

 if (++licznik_mrugania >= OKRES_MRUGANIA)
   licznik_mrugania = 0;

// Domyślnie dioda dostaje kolor pobrany z wzorca semafor[][]
//   ale gdy jej flaga mrugania jest ustawiona a licznik jest < połowy swojego zakresu, diodkę wygaszamy:

 for(uint8_t led = 0; led < LICZBA_DIODEK; led++)
 {
   pasek.setPixelColor(led, semafor[numer_stanu][led]);
   if (mruganie[numer_stanu][led] && (licznik_mrugania < OKRES_MRUGANIA/2))
       pasek.setPixelColor(led, color_black);
 }
}
// --------------------------------------------------------------------
uint8_t sprawdz_klik(void)
{
 uint8_t ch = 0;
 if (Serial.available())
   ch = Serial.read();
 return ch;
}
// --------------------------------------------------------------------
void analizuj_klik(uint8_t klik_code)
{
 switch (klik_code)
 {
   case 'N':
     if (++stan >= LICZBA_STANOW)
       stan = 0;
     break;
   case 'P':
     if (stan-- == 0)
       stan = LICZBA_STANOW-1;
     break;
 }
}

Jutro mam w planie dwie alternatywne rozszerzenia:

- albo zajmiemy się dorobieniem funkcji umożliwiającej kontrolę diodek przez port szeregowy (ustawianie kolorów, jasności, mrugania itp) poprzez wpisywanie ciekawszych komend klawiaturowych zamiast trywialnych N i P,
- albo popracujemy nad udawaniem na LEDach powolnych żarówek.

Co wybrać?

Link to post
Share on other sites

Dziękuję bardzo za wyczerpujące rozwinięcie tematu 😃

Co wybrać na jutro?

Trudny wybór, bo ciekawi mnie i jedno i drugie.

To może tak: najpierw popracujmy nad udawaniem na LEDach powolnych żarówek.

A potem (pojutrze?) zajmiemy się dorobieniem funkcji umożliwiającej kontrolę diodek przez port szeregowy (ustawianie kolorów, jasności, mrugania itp) poprzez wpisywanie ciekawszych komend klawiaturowych zamiast trywialnych N i P 🙂

Ciekawi mnie też czy gdybym zechciał wyjść z "piaskownicy" Arduino (oraz Arduino IDE) i zaprogramować jakiegoś AVR-ka (np. ATmegę 328P) pisząc program w C w np. Atmel Studio 7.0 i wysyłając go do μC przez programator USBasp - to w takim programie mogę również korzystać z biblioteki Adafruit_NeoPixel? Czy to jest tylko dla Arduino?

Link to post
Share on other sites

Biblioteka korzysta z wielu funkcji dostępnych w środowisku Arduino, głównie tych zapewniających arduinową "niezależność" od portów i pinów I/O poprzez nazywanie linii liczbami lub krótkimi nazwami (pinMode, digitalWrite, ale też digitalPinToBitMask itp). Musiałbyś to wszystko napisać, na szczęście zostały popełnione inne biblioteki. Tu masz przykład kolejnej:

https://github.com/cpldcpu/light_ws2812

której możesz używać pisząc na gołego AVRa.

Na pewno sam znajdziesz kilka innych pytając wyszukiwarkę np. o "ws2812 avr library".

Wieczorem wrzucę kod "żarówkowy" bo teraz zbyt ładna pogoda na siedzenie przy kompie. Czas na rower 🙂

[ Dodano: 28-03-2016, 22:08 ]

Żarówka jaka jest każdy widzi. Ma włókno nagrzewane prądem do temperatury w której zaczyna ono emitować promieniowanie w paśmie widzialnym. Ponieważ zapalanie i gaszenie żarówki jest de facto nagrzewaniem i studzeniem jej włókna to jest oczywistym, że nie następuje to w czasie zerowym. Nie jest to także proces liniowy, tzn. żarówka nie świeci liniowo coraz mocniej w czasie a raczej przypomina to ładowanie kondensatora:

Dlaczego tak się dzieje? Głównie dlatego, że rezystancja włókna bardzo zależy od temperatury - rośnie gdy włókno podgrzewamy i maleje gdy włókno jest coraz zimniejsze. Tak więc na początku - gdy żarówka jest zimna - podłączenie napięcia powoduje przepływ bardzo dużego prądu i wydzielanie się dużej ilości ciepła. Wtedy temperatura szybko rośnie. Jednak już wiemy, że jej wzrost, to wzrost rezystancji - więc prąd bardzo szybko spada. W końcu stabilizuje się na takim poziomie, na którym doprowadzana energia elektryczna równoważy straty związane z promieniowaniem światła i ciepła. Żeby zasymulować podobne zachowanie na diodzie LED musimy więc udawać temperaturę włókna, która "nie zdąża" za podawanym napięciem. Jeżeli coś będzie chciało naszą diodo-żarówkę zapalić, tj. zmienić jej jasność w pewnej chwili od 0 do 100% (tak teraz działa semfor), powinniśmy zrobić funkcję, która wyznacza aktualną jasność żarówki wg krzywej grzania i chłodzenia. Ta jasność będzie opóźnioną i trochę wygiętą wersją podanego napięcia tak, jak napięcie na kondensatorze ładowanym przez opornik "spóźnia się" za napięciem podanym ze źródła (wykres powyżej).

Najważniejszym spostrzeżeniem w tej sytuacji jest to, że prędkość zmian jasności (lub napięcia na kondensatorze) zależy wprost od różnicy między wartością zadaną a aktualną. Na początku, gdy żarówka jest zimna a napięcie już zostało podane (czyli wartość docelowa/pożądana temperatury jest wysoka) temperatura/jasność rośnie bardzo szybko. Gdy jasność rośnie, ma coraz bliżej do tej docelowej i prędkość zmian maleje. Gdy temperatura włókna dojedzie do temperatury końcowej, prędkość zmian spada do zera 🙂 Kierunek tych zmian zależy oczywiście od tego, czy zapalamy czy gasimy, ale w obu przypadkach proces wygląda podobnie. By "zrobić" żarówkę z LEDa wystarczy więc proste obliczanie różnicy między jasnością zadaną a jasnością aktualną i takie modyfikowanie tej aktualnej, by całość pracowała wg stałej czasowej podobnej do żarówki:

void setup()
{
 Serial.begin(9600);
}

void loop()
{
uint8_t
 docelowa = 200,
 aktualna = 0,
 stala_czasu = 10;

  Serial.println();
  Serial.print("Start: ");Serial.println(aktualna);

 for (uint8_t n=1; n<40; n++)
 {
   if (docelowa != aktualna)
   {
     uint8_t
       predkosc = (docelowa - aktualna) / stala_czasu;
     aktualna += predkosc;
   }
   Serial.print(n);Serial.print(": ");Serial.println(aktualna);
 }
 while(1);
}

Ten prosty program wypisuje kolejne jasności naszej wirtualnej diody zapalanej od stanu 0 do 200. Każdy może wypróbować różne wartości początkowe, końcowe i wielkość stałej_czasu i np. nanieść liczby na jakiś wykres - wyglądają naprawdę nieźle. Na początku jasność rośnie szybko, bo wyznaczana prędkość jest duża. Z każdym krokiem jasność rośnie a prędkość zmian maleje. Niestety proces załamuje się, gdy różnica robi się mniejsza niż ustawiona skala_czasu, bo wtedy prędkość wynikająca z dzielenia robi się zerowa - to są cienie świata obliczeń na liczbach całkowitych. Moglibyśmy rozszerzyć precyzję do operacji 16-bitowej lub wprowadzić zmienny przecinek, ale to nie apteka (i nie chcemy biednego procesorka obciążać ciężką matematyką w tak błahej sprawie). Żadne oko nie zauważy niewielkiej zmiany charakteru końcowego odcinka krzywej jasności, gdy trochę oszukamy:

void setup()
{
 Serial.begin(9600);
}

void loop()
{
uint8_t
 docelowa = 200,
 aktualna = 0,
 stala_czasu = 10;

 Serial.println();
 Serial.print("Start: ");Serial.println(aktualna);

 for (uint8_t n=1; n<45; n++)
 {
   if (docelowa != aktualna)
   {
     uint8_t
       predkosc = (docelowa - aktualna) / stala_czasu;
     if (predkosc == 0)
       predkosc++;
     aktualna += predkosc;
   }
   Serial.print(n);Serial.print(": ");Serial.println(aktualna);
 }
 while(1);
}

Wygląda na to, że nasza diodo-żarówka pokonuje zmianę jasności od 0 do 200 w 44 obroty pętli. Gdybyśmy te obliczenia wywoływali co 10ms, byłoby to jakieś 0.44s. Jak żarówka? 🙂

A jak to wkleić do programu obsługi diodek RGB? Do tej pory posługiwałem się kolorem diodki "skompresowanym" do liczby 32-bitowej nie wnikając (może oprócz generowania stałych color_jakiś) do wielkości i położenia składowych RGB. Teraz trzeba będzie zająć się osobno każdą składową, bo gdy diodka będzie zmieniała kolor z jakiegoś dziwnego, np. (100,200,30) czyli 100 czerwonego, 200 zielonego i 30 niebieskiego na czarny lub odwrotnie, każda składowa powinna mniej więcej słabnąć/rosnąć wg podobnej krzywej "kondensatorowej" a nie np. każda o tyle samo. Wrzucę na razie kod, a jutro - jeśli będą pytania - opowiem coś więcej:

#include <avr/pgmspace.h>
#include <Adafruit_NeoPixel.h>

#define LICZBA_DIODEK  5
#define PIN_RGB        6
#define LICZBA_STANOW   4
#define OKRES_MRUGANIA  100
#define STALA_CZASU      10

#define JASN_MAX       60
#define JASN_POL       (JASN_MAX/2)
#define JASN_TRZY      (JASN_MAX/3)

Adafruit_NeoPixel
 pasek = Adafruit_NeoPixel(LICZBA_DIODEK, PIN_RGB, NEO_GRB + NEO_KHZ800);

typedef struct
{
 uint8_t
   red, green, blue; 
} rgb_type;

const rgb_type
 color_black   = {0, 0, 0},
 color_red     = {JASN_MAX, 0, 0},
 color_green   = {0, JASN_MAX, 0},
 color_blue    = {0, 0, JASN_MAX},
 color_yellow  = {JASN_POL, JASN_POL, 0},
 color_cyan    = {0, JASN_POL, JASN_POL},
 color_magenta = {JASN_POL, 0, JASN_POL},
 color_white   = {JASN_TRZY, JASN_TRZY, JASN_TRZY};

boolean
 wzorzec_mrugania[LICZBA_STANOW][LICZBA_DIODEK];

rgb_type
 wzorzec_swiatel[LICZBA_STANOW][LICZBA_DIODEK];

uint8_t
 klik,
 stan = 0;

// ============================= SETUP ================================
void setup()
{
 wzorzec_swiatel[0][0] = color_red;
 wzorzec_swiatel[0][1] = color_black;
 wzorzec_swiatel[0][2] = color_black;
 wzorzec_swiatel[0][3] = color_black;
 wzorzec_swiatel[0][4] = color_green;

 wzorzec_swiatel[1][0] = color_red;
 wzorzec_swiatel[1][1] = color_yellow;
 wzorzec_swiatel[1][2] = color_black;
 wzorzec_swiatel[1][3] = color_black;
 wzorzec_swiatel[1][4] = color_green;

 wzorzec_swiatel[2][0] = color_black;
 wzorzec_swiatel[2][1] = color_black;
 wzorzec_swiatel[2][2] = color_green;
 wzorzec_swiatel[2][3] = color_red;
 wzorzec_swiatel[2][4] = color_black;

 wzorzec_swiatel[3][0] = color_black;
 wzorzec_swiatel[3][1] = color_yellow;
 wzorzec_swiatel[3][2] = color_black;
 wzorzec_swiatel[3][3] = color_red;
 wzorzec_swiatel[3][4] = color_black;

 wzorzec_mrugania[1][4] = true;
 wzorzec_mrugania[3][3] = true;

 pasek.begin();
 Serial.begin(9600);
}
// =========================== MAIN LOOP ==============================
void loop()
{
 klik = sprawdz_klik();
 analizuj_klik(klik);
 ustaw_swiatla(stan);
 wypisz_stan(stan);
 pasek.show();
 delay(10);
}
// --------------------------------------------------------------------
uint8_t sprawdz_klik(void)
{
 uint8_t ch = 0;
 if (Serial.available())
   ch = Serial.read();
 return ch;
}
// --------------------------------------------------------------------
void analizuj_klik(uint8_t klik_code)
{
 switch (klik_code)
 {
   case 'N':
     if (++stan >= LICZBA_STANOW)
       stan = 0;
     break;
   case 'P':
     if (stan-- == 0)
       stan = LICZBA_STANOW-1;
     break;
 }
}
// --------------------------------------------------------------------
// Funkcja wyznacza aktualna jasnosc swiecenia wszystkich lamp w semaforze
void ustaw_swiatla(uint8_t numer_stanu)
{
 static uint8_t
   licznik_mrugania;
 rgb_type
   stan_ledow_docelowy[LICZBA_DIODEK];
 static rgb_type
   stan_ledow_aktualny[LICZBA_DIODEK];
 uint8_t
   led;

 if (++licznik_mrugania >= OKRES_MRUGANIA)
   licznik_mrugania = 0;

// Tutaj pobieram wzorzec swiecenia jednoczesnie modyfikujac go wg wzorca mrugania.
// Te lampy ktore maja mrugac i akurat licznik jest w pierwszej polowie cyklu, dostaja kolor czarny:
 for(led = 0; led < LICZBA_DIODEK; led++)
 {
   if (wzorzec_mrugania[numer_stanu][led] && (licznik_mrugania < OKRES_MRUGANIA/2))
     stan_ledow_docelowy[led] = color_black;
   else
     stan_ledow_docelowy[led] = wzorzec_swiatel[numer_stanu][led];
 }

// Tutaj przejezdzam w petli przez wszystkie lampy semafora, dla kazdej obliczajac nowe skladowe RGB:
 for(led = 0; led < LICZBA_DIODEK; led++)
 {
   zapal_zgas(&stan_ledow_aktualny[led].red,   stan_ledow_docelowy[led].red);
   zapal_zgas(&stan_ledow_aktualny[led].green, stan_ledow_docelowy[led].green);
   zapal_zgas(&stan_ledow_aktualny[led].blue,  stan_ledow_docelowy[led].blue);
 }

// Uaktualnienie stanu wszystkich nowo policzonych skladowych kolorow wszystkich diodek:
 for(led = 0; led < LICZBA_DIODEK; led++)
   pasek.setPixelColor(led, pasek.Color(stan_ledow_aktualny[led].red,stan_ledow_aktualny[led].green,stan_ledow_aktualny[led].blue));
}
// --------------------------------------------------------------------
// Funkcja modyfikuje jasnosc aktualna starajac sie zdazac do wartosci docelowej wg stalej czasowej prawdziwej zarowki :)
void zapal_zgas(uint8_t* aktualny, uint8_t docelowy)
{
 uint8_t
   delta;
 if (*aktualny > docelowy)
 {
   delta = (*aktualny - docelowy) / STALA_CZASU;
   if (delta == 0)
     delta++;
   *aktualny -= delta;
 }
 else
 {
   if (*aktualny < docelowy)
   {
     delta = (docelowy - *aktualny) / STALA_CZASU;
     if (delta == 0)
       delta++;
     *aktualny += delta;
   }
 }
}
// --------------------------------------------------------------------
void wypisz_stan(uint8_t stan_aktualny)
{
 static uint8_t
   stan_poprzedni = 255;

 char
   nazwa_stanu[LICZBA_STANOW][60] =
   {
     " Samochody stoja, piesi przechodza",
     " Samochody pala opony, piesi uciekaja z jezdni",
     " Samochody jada, piesi stoja",
     " Samochody przeskakuja na zoltym, piesi szykuja sie"
   };

 if (stan_poprzedni != stan_aktualny)
 {
   Serial.print("Stan: "); Serial.print(stan_aktualny); Serial.println(nazwa_stanu[stan_aktualny]);
   stan_poprzedni = stan_aktualny;
 }
}

Najważniejsza zmiana to zdefiniowanie nowego typu danych: struktury rgb_type zawierającej trzy zmienne jednobajtowe o nazwach red, green i blue. Dzięki temu każdy kolor zajmuje 3 bajty (a nie 4 jak dotychczas) oraz mam wygodny dostęp do każdej składowej osobno. Tego właśnie potrzebuje nowa funkcja zapal_zgas wyznaczająca kierunek zmian oraz modyfikująca jasność aktualną każdej składowej w zależności od odległości do wartości docelowej odczytanej z wzorca stanu semafora. Proste, prawda? 🙂

BTW: Dodałem funkcję wypisująca nazwy stanów. Jeśli potraktować 3 pierwsze diody w pasku jako semafor dla samochodów a dwie pozostałe jako światła dla pieszych na przejściu, moje opisy są prawie poprawne.

Link to post
Share on other sites

A czy mógłbyś zamieścić przykładowy kod "żarówkowy" dla zwykłej jednokolorowej diody LED (może być tylko jedna)? W takim przypadku jasnością diody będziemy sterować chyba zmieniając wypełnienie sygnału PWM...

Link to post
Share on other sites

Maże spróbuj samemu? Pokazałem funkcję liczącą odpowiedź diody podobną do żarówki. Zrób prostą pętlę 10ms i wstaw tam:

- obsługę statycznego licznika robiącego cykl np. 1 czy 2s (czyli liczącego modulo 100 lub 200),
- funkcję wyznaczającą "docelowy" (pożądany) stan diody/żarówki (np. niech wynosi 0 dla licznika mniejszego niż połowa jego okresu liczenia i 255 gdy jest > od połowy okresu,
- funkcje wyliczającą "aktualny" stan diody podążający za "docelowym" zgodnie z opisem, który dostałeś na talerzu: zapal_zgas(), możesz tylko delikatnie zmienić jej argumenty i oddawaną wartość jeśli boisz się wskaźników,
- użyj wbudowanej w Arduino funkcji analogWrite() by wygenerować PWM na odpowiednim pinie wg "aktualnego" stanu diody,
- możesz na innym pinie włączać LED (także przez PWM i analogWrite) zgodnie ze stanem "docelowym" - będzie fajnie widać działanie algorytmu dociągania jednej jasności do drugiej.

To nie wydaje się poza zasięgiem, prawda? Czekam na komunikat o sukcesie 🙂 Film?

Link to post
Share on other sites

Sorry za kłopot.

Oczywiście spróbuję sam.

Czekam na komunikat o sukcesie 🙂 Film?

Komunikat pewnie się pojawi. O ile nastąpi sukces.

Na film raczej nie ma co liczyć. Nie dlatego, że nie chcę.

Tylko dlatego, że nie mam kamery. Ani nawet... smartfona.

A mój cyfrowy aparat foto (kompletnie "przedpotopowy") oraz aparat foto w moim telefonie komórkowym (też kompletnie "przedpotopowym" :->) robią fotki w takiej jakości, że kilka dni później - sam muszę się domyślać co to takiego "sfotografowałem" 🙂

W każdym razie, niezależnie od mojego sukcesu lub porażki - wciąż pozostaje "druga alternatywa": dorobienie funkcji umożliwiającej kontrolę diodek przez port szeregowy (ustawianie kolorów, jasności, mrugania itp) poprzez wpisywanie ciekawszych komend klawiaturowych zamiast trywialnych "N" i "P".

Link to post
Share on other sites

To żaden kłopot, przykładowy programik mogę wrzucić za 5 minut, ale właśnie takie konkretne problemy są fajnymi zadaniami na początek. Skoro już jako-tako oswoiłeś się z koncepcją czasowego podziału zadań w jednej wspólnej pętli, to każdy pomysł na ćwiczenie jest dobry. Nawet gdy nie masz kupionego czy zmontowanego semafora RGB, to właśnie taki zwykły LED doczepiony przez rezystor do któregoś pinu Uno (nie na wszystkich możesz dostać PWM!) będzie wystarczającym wyjściem Twojej nowej Funkcji Żarówkowej. Próbuj. Zacznij od napisania prostego programu zawierającego już funkcję docelowego obliczania jasności i wywołuj ją w pętli tak jak ja gdzieś tam wcześniej, wypisując wyniki na porcie szeregowym. Moja była tam przykładem jazdy tylko w górę, ale możesz skopiować pomysł z zapal_zgas() bazujący na liczbach jednobajtowych bez znaku (co wymusiło wstępne porównywanie wielkości) albo wymyślić swój własny, np. na typie int16_t (co być może da ładniejszy program, bo prędkość może być wtedy dodatnia lub ujemna i zawsze ją dodajesz). Spróbuj czy działa w górę i w dół i w ile obrotów pętli/wywołań dojeżdża z jasnością od zera do maxa lub z powrotem. Jak to będzie OK, napisanie pętli głównej z wymuszaniem jasności za pomocą samoczynnego licznika lub na podstawie zdarzeń klawiaturowych nie powinno być problemem.

Link to post
Share on other sites

witka

czy mi się wydaje ?

analizuj klik dało by się wtrącić do wyrażenia sprawdź klik?

chyba by było o jedno opuszczenie pętli mniej jeśli warunek zostanie spełniony.

2 zdarzenia przy czym 2 zależny od 1

a zatem więcej kwantu czasu?

a z tym światełkiem to XL +R =Z ?? (indukcyjny drut oporowy)

Link to post
Share on other sites
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.