Skocz do zawartości

Sterowanie diodami WS2812B przez Arduino z telefonu


Pomocna odpowiedź

Hej,

Kilka dni temu zacząłem bawić się bluetooth i mam problem.

Staram się włączyć diody ws2812b prze telefon i już brakuje mi pomysłu. Oczywiście chcę załączać gotowy program, mianowicie klasyczną tęczę i wygląda to tak że diody słuchają komendy i zapalają się ale stoją w miejscu, nie ma przejścia kolorów. Ma ktoś pomysł jak to ugryźć? :(

 

#include <PololuLedStrip.h>
PololuLedStrip<3> ledStrip;
#define LED_COUNT 4
rgb_color colors[LED_COUNT];


void setup()


{
  Serial.begin(9600);
}

rgb_color hsvToRgb(uint16_t h, uint8_t s, uint8_t v)
{
    uint8_t f = (h % 60) * 255 / 60;
    uint8_t p = (255 - s) * (uint16_t)v / 255;
    uint8_t q = (255 - f * (uint16_t)s / 255) * (uint16_t)v / 255;
    uint8_t t = (255 - (255 - f) * (uint16_t)s / 255) * (uint16_t)v / 255;
    uint8_t r = 0, g = 0, b = 0;
    switch((h / 60) % 6){
        case 0: r = v; g = t; b = p; break;
        case 1: r = q; g = v; b = p; break;
        case 2: r = p; g = v; b = t; break;
        case 3: r = p; g = q; b = v; break;
        case 4: r = t; g = p; b = v; break;
        case 5: r = v; g = p; b = q; break;
    }
    return rgb_color(r, g, b);
}


 
char cmd[100];
byte cmdIndex;
 
void exeCmd() {
 






    if (cmd[0] == 'o') {
    digitalWrite(9, LOW);
  }
  }


 
void loop(){

  
  if (Serial.available() > 0)
  {
    char c = (char)Serial.read();
    if (c == '\n') {
      cmd[cmdIndex] = 0;
      exeCmd();
      cmdIndex = 0;
    } else {
      cmd[cmdIndex] = c;
      if (cmdIndex < 99) cmdIndex++;
    }
  }


   if (cmd[0] == 'l') {

 uint16_t time = millis() >> 2;
  for(uint16_t i = 0; i < LED_COUNT; i++)
  {
    byte x = (time >> 2) - (i << 3);
    colors[i] = hsvToRgb((uint32_t)x * 359 / 256, 255, 255);
  }   
      
      ledStrip.write(colors, LED_COUNT);
      delay(10);
      
  }
}

 

 

Link to post
Share on other sites

@Klos w komentarzach do kursów  staramy się rozmawiać tylko o sprzęcie i przykładach związanych bezpośrednio z kursem, dlatego post wydzieliłem do osobnego tematu.

Ciężko analizować Twój program, bo nie ma tam żadnych komentarzy i właściwie tylko Ty wiesz co tam się dzieje. Czy problem występuje tylko, gdy używasz BT, czy podczas sterowania przez UART jest tak samo? Mogę się tylko domyślać, że po prostu efekt poprawnie startuje, ale później nic się nie dzieje, bo zwyczajnie nie wywołujesz funkcji, która miałaby ten efekt animować. Może Twój program zatrzymuje się i czeka na kolejne sygnały z BT, a może zwyczajnie zapomniałeś, że po starcie funkcja efektu musi wykonywać się cały czas, a nie tylko przez jeden "obrót" pętli? Opisz jak krok, po kroku działa ten kod, bo ciężko go analizować.

Link to post
Share on other sites

O! Lubię takie zagadki.

Po pierwsze - nie daj sobie wmówić, że kodu bez komentarzy nie da się zrozumieć.

Dygresja: Współcześnie w projektach odchodzi się od komentowania kodu w ogóle. Powodów ku temu jest sporo.

Przede wszystkim: kompilator nie sprawdza komentarzy. Może tam być napisane dowolne coś, może to coś już dawno być nieaktualne czy nieprawdziwe, a może tylko pokazuje intencję autora albo wręcz maskować błąd.

Jakieś przykłady?

int a1; // zmienna a

ja tu widzę zmienną o identyfikatorze "a1".

for (i=0; i<10; ++i) { // pętla for

Masło maślane, całe szczęście, że nie "while"

for (i=0; i<=371; ++i) { // 371 obrotów pętli

a nie, bo 372.

Takich kwiatków w kodzie na forum i nie tylko można znaleźć mnóstwo.


Generalnie zakłada się, że kod ma być czytelny. Piszemy kod dla człowieka - autora, współpracownika, czytelników forum, nie dla komputera. Bo się ten kod czyta/pisze wiele godzin, a kompiluje raz na te wiele godzin.

Używamy fajnych, długich nazw dla zmiennych. Jak LED_COUNT, ale nigdy, nigdy żadnych tam p, q, t, f - tego nikt nie rozczyta. Nawet autor za tydzień nie pojmie co miał na myśli.

Najlepiej czasownikRzeczownik. Najlepiej po angielsku. I nigdy żadnych skrótów. exeCmd??? Co to jest, co to ma robić. Już nawet hsvToRgb - mogę się domyślić, ale exeCmd? W szczególności, że treść tego exeCmd niewiele mówi. Miało być executeCommand?
 

void exeCmd() {
    if (cmd[0] == 'o') {
        digitalWrite(9, LOW);
    }
}

I żadnych magicznych liczb. Czym tu jest "9"? Coś ma wybuchnąć? Ta dziewiątka nie pojawia się nigdy więcej. Czytelnik nie zgadnie. Żeby chociaż było tak:

const int kaboomPin = 9;
...
digitalWrite(kaboomPin, LOW);

Czym tu są liczby 6, 60, 359 (po dłuższym czasie mi wyszło, że one są związane ze sobą).

Czym jest znak 'o' w exeCmd?

A już 99 to i jej związek z rozmiarem tablicy cmd[100] - 20 wierszy wyżej, prawie cała strona - zrób za karę 20 przysiadów.

 

Raz mi się zdarzyło użyć w kodzie coś takiego:

constexpr auto MAGIC_NUMBER = 3;

I dla wszystkich było jasne, że jest to liczba prawdziwie magiczna.

 

I weź trochę kod sformatuj przed proszeniem innych, żeby to przeczytali.

Reasumując:

  1. Komentarze są złe
  2. Jeśli autor musi napisać komentarz, to znaczy, że tak zamotał, że sam sobie musi wytłumaczyć, co napisał. Taka żółta kaczuszka, ale w kodzie.

To było trochę poza wątkiem, a zaraz napiszę co widzę w kodzie.

  • Nie zgadzam się! 1
Link to post
Share on other sites
39 minut temu, jbanaszczyk napisał:

Przede wszystkim: kompilator nie sprawdza komentarzy. Może tam być napisane dowolne coś, może to coś już dawno być nieaktualne czy nieprawdziwe, a może tylko pokazuje intencję autora albo wręcz maskować błąd.

Ale to właśnie intencje autora chcielibyśmy tu poznać, bo z kodu nie wynikają. Właśnie rozbieżność komentarzy z kodem wskazuje gdzie leży problem i co należy człowiekowi wytłumaczyć. Bo przecież nie o to chodzi, żeby napisać za niego, tylko żeby się czegoś nauczył.

Brak komentarzy w takim wypadku może sugerować, że autor przekleił kod bez jakiegokolwiek zrozumienia czy nawet cienia wysiłku i oczekuje teraz cudów.

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

Popatrz na funkcję hsvToRgb() - tak zamotana, że naprawdę trudno dać jakikolwiek komentarz. A wystarczyło zmienne nazwać po ludzku

Nie chcę się wypowiadać za autora - ten kod nie jest przeklejony. Za dużo błędów 🙂

On powstał ze sporym wysiłkiem i po wielu eksperymentach. I co więcej - autor naprawdę nie oczekuje od nas cudów - on naprawdę powinien działać.

I żaden komentarz tu nie pomoże. Trzeba przeczytać.

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

Ło Panie, Kto Ci to tak zamotał?

Na początek - ten operator >> czy << do mnożenia przez stałą. Ani jednego bajtu kodu nie oszczędzisz. Kompilator g++ 5.4.0. z opcjami używanymi przez Arduino wykonuje jakieś 81 rodzajów optymalizacji kodu (ta liczba 81 to nie jest przenośnia, a optymalizacja -Os jest opisana tu: https://gcc.gnu.org/onlinedocs/gcc-5.4.0/gcc/Optimize-Options.html), kompilator znajdzie, że chciałeś podzielić przez stałą 4 albo pomnożyć przez stałą 8 i zrobi to dobrze.

I nie męcz kompilatora dziesiątkami konwersji między typami całkowitymi. On to raczej zrobi dobrze bez podpowiadania, niewiele zaoszczędzisz, a co się napiszesz to Twoje. W szczególności: czytelność radykalnie maleje.

uint16_t time = millis() >> 2;
auto time = millis() / 4; // to samo
char c = (char)Serial.read();
char c = Serial.read(); // to samo


 

for (uint16_t i = 0; i < LED_COUNT; i++)
{
    byte x = (time >> 2) - (i << 3);

zmienna sterująca spokojnie mogła być nawet short int. Albo int dla czytelności. I tak w wyliczeniu byte x najpierw wynik mnożenia i * 8 zostanie podniesiony do typu decltype(time), by potem go zrzucić do byte.

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

Weźmy hsvToRgb()

Trochę zabawię się w kompilator.

Jest wołana tylko raz: gdzieś tam w funkcji loop().
No to kompilator ją tam ochoczo przeniesie.

Parametry s oraz v są const i mają wartość 255? Super. Zaraz się pooptymalizuje:

uint8_t p = (255 - s) * (uint16_t)v / 255;
s = 255
v = 255

wychodzi

uint8_t p = 0;

 

uint8_t q = (255 - f * (uint16_t)s / 255) * (uint16_t)v / 255;
s = 255
v = 255

wychodzi

uint8_t q = 255 - f;

 

uint8_t t = (255 - (255 - f) * (uint16_t)s / 255) * (uint16_t)v / 255;

Jak nigdzie się nie machnąłem, to będzie

uint8_t t = f;

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

Kod funkcji by wyglądał jakoś tak:

rgb_color hsvToRgb(uint16_t h) {
    uint8_t f = (h % 60) * 255 / 60;
    uint8_t q = 255 - f;
    uint8_t r = 0, g = 0, b = 0;
    switch ((h / 60) % 6) {
      case 0: r = 255; g = f;   b = 0;   break;
      case 1: r = q;   g = 255; b = 0;   break;
      case 2: r = 0;   g = 255; b = f;   break;
      case 3: r = 0;   g = q;   b = 255; break;
      case 4: r = f;   g = 0;   b = 255; break;
      case 5: r = 255; g = 0;   b = q;   break;
    }
    return rgb_color(r, g, b);
}
uint8_t f = (h % 60) * 255 / 60;

h jest 16 bitowe, no to liczymy na 16 bitach.
(h % 60) - coś z zakresu 0..59
(h % 60) * 255 - coś zakresu 0..59*255, ze skokiem 255
(h % 60) * 255 / 60 - coś zakresu 0..250. Gdyby mnożyć przez 256, to by f było 0..255, ale nie bądźmy drobiazgowi 😛
Jakby narysować wykres f(h), to by wyszła piła od 0 do 250, z okresem 60

a jak popatrzeć na wartość argumentu h (gdzie x jest bajtem 0..255):
x * 359 / 256
to f jako funkcja x - też jest piłą 0..250, z okresem mniej więcej 42 (jakby zamiast 359 było 360, to by było dobrze)
a q jest piła odwróconą 255..5, z tym samym okresem

No i wyrażenie w switchu:
funkcja schodkowa, od 0 do 5, ze skokami co mniej więcej 42. Najpierw 42 zera, potem 42 jedynki itd.

Czyli najpierw (dla x z zakresu o do 41) (case 0):
r = 255 (bo v jest stałe)
g = rośnie 0..250 (tak jak f)
b = 0

potem (dla x z zakresu 42 do 83) (case 1):
r maleje od 255 do 5
g = 255
b = 0

potem (dla x z zakresu 84 do 123) (case 2):
r = 0 (tu będzie skokowy spadek, bo ostatnio skończylo się na 5)
g = 255
b rośnie do 250

potem (dla x z zakresu 124 do 167) (case 3):
r = 0
g maleje od 255 do 5
b = 255

potem (dla x z zakresu 168 do 209) (case 4):
r rośnie do 250
g = 0
b = 255

potem (dla x z zakresu 210 do 251) (case 5):
r = 255
g = 0
b maleje od 255 do 5

ten zakres x jest w rzeczywistości 0..255, ale przez dzielenie przez niekrągłe 359 wyszło w opisie jak wyszło

Mieni mi się w oczach. A weź to opisz w komentarzu 😛

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

Pętla loop().

Po co Ci colors[] jako zmienna globalna?

liczby 100 i 99 są w pewnym związku?.

To jeszcze dalekie od ideału, ale mniej więcej tak można to zapisać, bez zmiennych globalnych w ogóle...

void loop() {

    static byte cmdIndex = 0;
    const auto cmdSize = 100;
    static char cmd[cmdSize];

    if (Serial.available()) {
        char c = (char)Serial.read();
        if (c == '\n') {
            cmd[cmdIndex] = 0;
            if (cmd[0] == 'o') {
                digitalWrite(9, LOW);
            }
            cmdIndex = 0;
        } else {
            cmd[cmdIndex] = c;
            if (cmdIndex < cmdSize) ++cmdIndex;
        }
    }

    if (cmd[0] == 'l') {

        auto time = millis() / 4;

        const auto LED_COUNT = 4;
        rgb_color colors[LED_COUNT];

        for (int i = 0; i < LED_COUNT; ++i)
        {
            byte x = (time / 4) - (i * 8);
            colors[i] = hsvToRgb(((uint32_t)x * 360) / 256, 255, 255);
        }

        ledStrip.write(colors, LED_COUNT);
        delay(16);
    }
}

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

W tym opisie jest używany cmd[0] czyli pierwszy odebrany znak z Seriala po '\n'. I on się nie zmienia!

Najpierw upewnię się:

To mruganie diodami jest gdy cmd[0]=='l': 'l' jak 'leon'
cmdExe reaguje na na cmd[0]=='o': 'o' jak 'olga'
millis() najpierw dzielisz przez 4 przy podstawieniu pod time, potem jeszcze raz dzielisz przez 4 przy wyznaczeniu x (to nie ma znaczenia). Aż się prosi delay(16) zamiast delay(10). To też nie ma znaczenia.

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

Nie chce mi się pamiętać:
Masz w kodzie

byte cmdIndex;

Na pewno tam jest zero?
Bo jak nie, to pierwsze z brzegu

cmd[cmdIndex] = 0;

 

cmd[cmdIndex] = c;

zamaluje jakiś bajt w pamięci. Niekoniecznie w buforze. Generalnie - nie zostawiamy zmiennych nie zainicjowanych.

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

Ta druga pętla (LED_COUNT) trzyma się kupy: kolejne LEDy dostają zbliżone kolory, a całość się zmienia w rytm millis()/16

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

No to teraz pierwsza pętla w loop():
Kolekcjonujesz tam znaki z seriala. Pracowicie zapisujesz do bufora (o ile się mieszczą).
I tego bufora nigdzie nie używasz.

Jak przyjdzie '\n' to:

  • zakańczasz bufor zerem.
  • a jeśli na początku jest 'o' jak 'olga' to wtedy ustawiasz pin 9 na LOW (i nigdzie indziej nie ustawiasz go na HIGH) nie mam pojęcia co ma się zdarzyć
  • i zaczynasz kolekcjonować nikomu niepotrzebny bufor od nowa

A jeśli pierwszym znakiem bufora jest 'l' jak 'leon' to mrugasz diodami, czekasz 10 milisekund, i jeśli z seriala nie przyszło '\n' to znowu zmieniasz kolory

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

I jeszcze: jak przyjdzie '\n', to niby zerujesz indeks bufora, ale nie zmieniasz pierwszego znaku w buforze i nadal sprawdzasz, czy on nie jest == 'l'

 

 

Edytowano przez jbanaszczyk
  • Lubię! 1
Link to post
Share on other sites
6 godzin temu, jbanaszczyk napisał:

Nie chce mi się pamiętać:

Masz w kodzie


byte cmdIndex;

Na pewno tam jest zero?
Bo jak nie, to pierwsze z brzegu


cmd[cmdIndex] = 0;

 


cmd[cmdIndex] = c;

zamaluje jakiś bajt w pamięci. Niekoniecznie w buforze. Generalnie - nie zostawiamy zmiennych nie zainicjowanych.

 

Mi dla odmiany nie chciało się czytać - ale na pewno w cmdIndex będzie zero. Polecam wrócić do standardu języka C zanim się zacznie pouczać innych.

Link to post
Share on other sites
4 godziny temu, Elvis napisał:

Mi dla odmiany nie chciało się czytać - ale na pewno w cmdIndex będzie zero. Polecam wrócić do standardu języka C zanim się zacznie pouczać innych.

To ja może dla odmiany polecam poczytać o różnicach pomiędzy C i C++ i o tym którego z nich używa Arduino.

W C będzie tam niezdefiniowana wartość i kompilator ma prawo zrobić cokolwiek, włącznie z wypączkowaniem z płytki nóżek i odbiegnięciem w siną dal. W C++ (oraz Arduino) będzie zero.

Link to post
Share on other sites
29 minut temu, Elvis napisał:

@deshipu to zmienna globalna, więc wcale nie będzie miała wartości niezdefiniowanej... niezależnie czy to C, czy C++

Rzeczywiście. Umknął mi ten szczegół. Widzisz, ciężko prowadzić jakąś rozsądną dyskusję kiedy wszystko opiera się na niedopowiedzeniach i wyzwiskach.

Może umówmy się, że jak wyzywamy innych ludzi od idiotów i nieuków, to może jednak róbmy to z większą klasą i podawajmy źródła? Bo to trochę za bardzo przypomina małpki w cyrku obrzucające się lepiej nie wiedzieć czym. I tak, doskonale sobie zdaję sprawę, że sam też jestem winien takiego zachowania, ale już mi się to znudziło. Jak nie chce ci się szukać odnośnika czy cytować, to też spoko — po prostu nie pisz, jestem pewien, że wówczas napisze ktoś, komu się będzie chciało.

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

Nie wiem gdzie pojawiły się wyzwiska, może mi coś umknęło, ale ja nic takiego nie widziałem, ani nie pisałem.

Chodziło mi tylko o to, że naukę najlepiej zacząć od siebie. A wcześniejszy wpis zawierał strasznie dużo mądrości, które nie najlepiej świadczyły o samym piszącym. Natomiast zmienna cmdIndex była tylko przykładem - oczywiście przypisanie do niej zera nic nie zepsuje, może nawet lepiej wyglądać.

Ostatnio niestety to forum coraz częściej zmienia się w miejsce odreagowywania kompleksów przez różne osoby, więc może ustalmy tylko - zmienne globalne zarówno w C, jak i w C++ są inicjalizowane, mówi o tym standard języka. Odnośników nie chce mi się szukać, ale to nie jest wielki wysiłek.

Inna sprawa to faktycznie kod jest w C++, a nie jak napisałem w C - mój błąd, nikt nie jest nieomylny 🙂

  • Lubię! 1
  • Pomogłeś! 1
Link to post
Share on other sites

Cześć,

Dzięki wszystkim za odpowiedź. @Treker dzięki za przeniesienie, bo faktycznie inny dział, może i nawet jeszcze inny bo przyznam się że jestem w tych sprawach początkujący i też od razu powiem że powyższy kod jest zlepkiem dwóch gotowych programów. Pierwszy to po prostu tęcza na ws2812b, pewnie wiele ma wersji ale założenie jest proste i normalnie działa na Arduino. Drugi to po prostu sterowanie bluetooth poprzez RX i TX. Napisałem coś prostego do włączania zwykłych diod, albo RGB-każdego koloru osobno. Teraz pomyślałem że odpalę pętlę tęczy komendą z bluetooth. Więc skleiłem dwa kody.

Chciałem zapytać czy jest jakaś opcja na załączanie, powiedzmy konkretnej pętli. Bo cała tęcza to kod zamieniający to hsv i rgb, a to że "ona" się "porusza" jest zawarte w pętli, bynajmniej tak to widzę.

 

@jbanaszczyk Dzięki za lekcję 🙂  Zazdroszczę wiedzy. Kod odnośnie tablicy jest od chyba każdemu znanego Elektroprzewodnika, w sumie od niego wiem o tym forum. Ciekawe jest to jak każdy mimo iż się zna, twierdzi że coś jest źle. No i co masz na myśli mówiąc "sformatuj" kod? Jest nie czytelny?

Pozdrawiam!

Link to post
Share on other sites
21 godzin temu, Klos napisał:

No i co masz na myśli mówiąc "sformatuj" kod? Jest nie czytelny?

Spójrz chociażby na poniższy fragment. Brak wcięć przy nawiasach, niepotrzebne przerwy itd. Tak nie powinno być 😉

void exeCmd() {
 






    if (cmd[0] == 'o') {
    digitalWrite(9, LOW);
  }
  }


 
void loop(){

  

 

Link to post
Share on other sites

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.