fbpx

Dotarliśmy do końca cyklu o adresowalnych diodach WS2812B. Przynajmniej na tą chwilę. W tej części omówię podział paska diod na w pełni konfigurowalne i niezależne segmenty. Oznacza to, że każdy z segmentów może działać w innym trybie, z inną prędkością oraz posiadać różną długość. Daje to olbrzymie możliwości dostosowania pod własne potrzeby. Jedziemy!

Ten artykuł jest częścią cyklu:

Idea podziału na segmenty

Jako, że paski LED mogą mieć teoretycznie nieskończoną długość, szkoda byłoby marnować ich potencjał na ustawienie jedynie jednego efektu. Można przecież taką taśmę prowadzić w różnych miejscach, zaginać ją i robić inne cuda. Na przykład oklejając szafkę pod TV niekoniecznie chcielibyśmy, aby każda krawędź świeciła w ten sam sposób. Dlatego zmodyfikowałem bibliotekę i dodałem do niej podział na segmenty. Biblioteka do Arduino na którą się inspirowałem również ma taką możliwość, lecz jest to tak wykonane w delikatnie inny sposób.

Jedym z moich założeń było dynamiczne tworzenie odpowiedniej tablicy segmentów. Dzięki temu chcąc tylko jeden segment, w pamięci tworzony jest tylko jeden “obiekt” segmentu. Ilość segmentów można zmieniać w locie. Kod dba o to, aby nie dochodziło do wycieku pamięci i tablica segmentów skaluje się na bieżąco. Jeden segment zajmuje w pamięci 56 bajtów i wygląda w ten sposób:

typedef struct ws2812bfx_s
{
	volatile uint32_t ModeDelay; // Segment SW timer counter

	uint16_t IdStart; // Start segment point
	uint16_t IdStop; // End segment point

	uint8_t Running : 1; // Is sector running
	uint8_t ActualMode; // Sector mode setting
	uint8_t Reverse : 1; // Is reverted mode
	uint32_t CounterModeCall; // Numbers of calls
	uint32_t CounterModeStep; // Call step

	uint16_t Speed; // Segment speed

	uint32_t ModeColor[NUM_COLORS]; // Mode color 32 bit representation
	ws2812b_color ModeColor_w[NUM_COLORS]; // Mode color struct representation

	uint8_t AuxParam; // Computing variable
	uint16_t AuxParam16b; // Computing variable
	uint8_t Cycle : 1; // Cycle variable

	void (*mModeCallback)(void); // Sector mode callback
} ws2812bfx_s;

Mam świadomość, że niektóre parametry są nadmiarowe dla niektórych trybów. Na szczęście nie jest to ogromny koszt. W miarę potrzeb będę to optymalizował w przyszłości .

Podstawa działania biblioteki

Do poprawnego działania biblioteka wymaga timer’ów programowych oraz callbacka w głównej pętli main.

void WS2812BFX_SysTickCallback(void);

Callback SysTicka umieszczam w predefiniowanym callbacku SysTicka mieszczącym się w bibliotekach HAL. Jako, że jest to funkcja weak umieszczam ją wygodnie w czwartej sekcji kodu użytkownika pliku main.c. Jest to podobne działanie jak w poprzedniej wersji biblioteki.

void WS2812BFX_Callback(void);

Funkcja callbackowa biblioteki FX podobnie jak w poprzedniej wersji pilnuje wywoływania kolejnych efektów. Musi być umieszczona w głównej pętli.

Tworzenie segmentów

W przykładzie umieściłem podział całego paska na 3 segmenty. Po zainicjalizowaniu sterowania diodami należy wywołać inizjalizację efektów za pomocą funkcji

FX_STATUS WS2812BFX_Init(uint8_t Segments);

Funkcja ta ma za zadanie postawić tablicę struktur odpowiedzialnych za segmenty. W argumencie przyjmuje ilość segmentów na którą podzielony zostanie pasek. Ta funkcja dzieli pasek na równe części. Funkcja potrafi także dołożyć kolejny segment pamiętając ustawienia wszystkich poprzednich. Odjęcie segmentu powoduje, że informacja o ostatnim jest bezpowrotnie tracona, natomiast każdy nowy segment powyżej poprzedniej ilości ma defaultowe ustawienia oraz jest wstrzymany – należy go wystartować. Do wygodnego dodawania i odejmowania segmentów można użyć przygotowanych funkcji.

FX_STATUS WS2812BFX_SegmentIncrease(void);
FX_STATUS WS2812BFX_SegmentDecrease(void);

Konfiguracja segmentów

No dobra, ale chciałbym mieć różne długości tych segmentów. Przewidziałem taką możliwość kilkoma wygodnymi funkcjami.

FX_STATUS WS2812BFX_SegmentIncreaseEnd(uint8_t Segment);
FX_STATUS WS2812BFX_SegmentDecreaseEnd(uint8_t Segment);
FX_STATUS WS2812BFX_SegmentIncreaseStart(uint8_t Segment);
FX_STATUS WS2812BFX_SegmentDecreaseStart(uint8_t Segment);

Jak same nazwy wskazują, możemy za ich pomocą przenieść pierwszy i ostatni punkt w segmencie. Operacje te są bezpieczne i nie pozwalają na nałożenie się dwóch segmentów sąsiednich. Dlatego przykładowo, aby zwiększyć segment nr 1 na końcu należy najpierw przesunąć początek segmentu nr 2.

Jest jeszcze jedna, mniej bezpieczna funkcja.

FX_STATUS WS2812BFX_SetSegmentSize(uint8_t Segment, uint16_t Start, uint16_t Stop);

Ona już nie pilnuje nakładania się segmentów. Nie zaimplementowałem pilnowania z prostego powodu. Każdy może inaczej wyobrażać sobie przesuwanie się sąsiedniego segmentu. Bo co zrobić w sytuacji, gdy sąsiedni segment ma już tylko jedną diodę? Usunąć go czy skracać inny? A może wszystkie segmenty skracać równomiernie w miarę rozszerzania? Zostawiam to już Tobie i dzięki tej funkcji możesz dowolnie konfigurować długości poszczególnych efektów.

Ustawianie kolorów

Każdy z trybów wymaga co najmniej jednego koloru do działania. Maksymalnie liczba kolorów wymaganych przez któryś z trybów to 3. Każdy z nich ma swój ID przez który można się do niego odnieść. Kolor ustawiony może być na cztery sposoby.

void WS2812BFX_SetColorStruct(uint8_t id, ws2812b_color c);
void WS2812BFX_SetColorRGB(uint8_t id, uint8_t r, uint8_t g, uint8_t b);
void WS2812BFX_SetColorHSV(uint8_t id, uint16_t h, uint8_t s, uint8_t v);
void WS2812BFX_SetColor(uint8_t id, uint32_t c);

Efekt wywołania każdej z powyższych funkcji jest taki sam. Uwaga  funkcje te przypisują kolor do zmiennych tymczasowych. Aby zaaplikować je do konkretnego segmentu, należy po nich wywołać funkcję startującą wybrany tryb pracy na danym segmencie.

Operacje na trybach

Zmiana trybu poszczególnych segmentów wydaje się najważniejszą funkcją w całym działaniu paska diod WS2812B.

FX_STATUS WS2812BFX_SetMode(uint8_t Segment, fx_mode Mode);
FX_STATUS WS2812BFX_NextMode(uint8_t Segment);
FX_STATUS WS2812BFX_PrevMode(uint8_t Segment);
FX_STATUS WS2812BFX_SetReverse(uint8_t Segment, uint8_t Reverse);

Nazwy jak zwykle same mówią za siebie. Do wyboru mamy ustawienie konkretnego trybu dla wybranego segmentu lub wybranie kolejnego i poprzedniego trybu. Funkcje nie pozwalają wyjść poza listę dostępnych trybów.

Dwie ostatnia funkcja dotyczy ustawienia kierunku przebiegu animacji dla niektórych trybów w jedną lub drugą stronę.

Oprócz zmian trybów biblioteka umożliwia kontrolę nad działaniem danego segmentu.

FX_STATUS WS2812BFX_Start(uint8_t Segment);
FX_STATUS WS2812BFX_Stop(uint8_t Segment);

Funkcje te startują lub stopują animację na wybranym segmencie. W momencie startu resetowane są timer’y trybu oraz przypisywane są kolory znajdujące się w zmiennych tymczasowych ustawień kolorów – segment będzie działał od początku, a nie kontynuował od momentu zatrzymania.

Prędkość animacji

Prędkość animacji dla każdego z segmentów ustawia się osobno za pomocą poniższych fukncji.

FX_STATUS WS2812BFX_SetSpeed(uint8_t Segment, uint16_t Speed);
FX_STATUS WS2812BFX_IncreaseSpeed(uint8_t Segment, uint16_t Speed);
FX_STATUS WS2812BFX_DecreaseSpeed(uint8_t Segment, uint16_t Speed);

Wartość Speed pierwszej fukncji wyrażona jest w milisekunkach pomiędzy każdym przejściem w trybie. Wyjątkiem są dwa tryby  – FX_MODE_WHITE_TO_COLOR i FX_MODE_BLACK_TO_COLOR – dla których wartość ta to czas potrzebny na przejście czerń/biel -> kolor -> czerń/biel również wyrażony w milisekundach.

Dwie ostatnie fukncje zwiększają nadaną już prędkość w segmencie o wartość z argumentu Speed.

Zbieranie informacji o segmentach

Na potrzeby komunikacji zewnętrznej np. przez UART czy USB zachodzi potrzeba zdalnego sprawdzania konfiguracji całego sterownika paska.

uint8_t WS2812BFX_GetSegmentsQuantity(void);
FX_STATUS WS2812BFX_IsRunning(uint8_t Segment, uint8_t *Running);
FX_STATUS WS2812BFX_GetMode(uint8_t Segment, fx_mode *Mode);
FX_STATUS WS2812BFX_GetReverse(uint8_t Segment, uint8_t *Reverse);
FX_STATUS WS2812BFX_GetSegmentSize(uint8_t Segment, uint16_t *Start, uint16_t *Stop);
FX_STATUS WS2812BFX_GetSpeed(uint8_t Segment, uint16_t *Speed);
FX_STATUS WS2812BFX_GetColorRGB(uint8_t id, uint8_t *r, uint8_t *g, uint8_t *b);

Wszystkie te funkcje zwracają odpowiednie parametry pracy paska i segmentów. Zwracają je pod wskaźnki podane w argumentach. Jak możesz zauważyć każda fukncja zwraca wartość FX_STATUS. Wiąże się to z powodzeniem wykonania funkcji. FX_STATUS przyjmuje dwa stany.

typedef enum {
  FX_OK = 0,
  FX_ERROR = 1
} FX_STATUS;

FX_ERROR jak nietrudno się domyślić oznacza błąd. Może to być np. błąd wynikający z przekroczenia zakresów. Taki błąd wystąpi chcąc wyciągnąć tryb segmentu nr 7, mając działających tylko 5.

Przykład działania

Nagrałem film, który prezentuje działanie segmentów oraz ich konfiguracji. Na tą potrzebę zaimplementowałem komunikację USB poprzez virtual COM port oraz proste parsowanie komunikatów. Nie zwracaj proszę uwagi na poprawność parsowania bo sposobów jest mnóstwo, a ten który zaimplementowałem tutaj jest dosyć prymitywny.

Podsumowanie

Jak wspomniałem w pierwszym akapicie, na ten moment to koniec cyklu o adresowalnych diodach WS2812B. W miarę możliwości i uwag od czytelników będę poprawiał ewentualne błędy oraz implementował ciekawe propozycje. Zachęcam do podsyłania mi swoich pomysłów.

Podział paska LEDów na segmenty daje niesamowite możliwości customizacji pod siebie. Sam prawdopodobnie postawię coś na oknie lub na choince podczas nadchodzących świąt. Zastosowanie segmentów widzę również w dekoracji wnętrz czy mebli. Ogranicza nas tylko wyobraźnia 😉

Dziękuję Ci za przeczytanie tego wpisu. Jeśli taka tematyka Ci odpowiada, daj mi znać w komentarzu. Będę też wdzięczny za propozycję tematów które chciałbyś abym poruszył.

Kod standardowo dostępny jest na moim GitHubie: link

Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podystkutować w tym temacie, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.

Ten artykuł jest częścią cyklu:

5/5 - (3 votes)

Podobne artykuły

.

10 komentarzy

Rezasurmar · 20/04/2021 o 11:16

Brak w projekcie obsługi prawidłowej CDC_Receive_FS 😉

    Mateusz Salamon · 17/05/2021 o 13:17

    Całkiem możliwe 😉 Stary projekt, który kiedyś trzeba poprawić.

      Rezasurmar · 28/06/2021 o 11:19

      Brakująca obsługa CDC_Receive_FS 😉

      static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
      {
      /* USER CODE BEGIN 6 */
      extern uint8_t USBReceivedDataFlag; // Flaga informujaca o odebraniu danych
      extern uint8_t USBDataRX[50]; // Tablica przechowujaca odebrane dane

      USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
      USBD_CDC_ReceivePacket(&hUsbDeviceFS);

      // Wyczyszczenie tablicy odebranych danych
      uint8_t iter;
      for(iter = 0; iter<50; ++iter){
      USBDataRX[iter] = 0;
      }

      strlcpy(USBDataRX, Buf, (*Len) + 1); // Przekopiowanie danych do naszej tablicy
      USBReceivedDataFlag = 1; // Ustawienie flagi odebrania danych

      return (USBD_OK);
      /* USER CODE END 6 */
      }

      Mateusz Salamon · 28/06/2021 o 12:07

      Dzięki! Zrobisz pull request na Githubie? https://github.com/lamik/WS2812B_STM32_HAL

Rezasurmar · 16/04/2021 o 11:40

Niestety, masz rację, pin odpowiedzialny za SPI, nie może być w trybie Open Drain, gdzie np. i2c można.

Jest to ograniczenie sprzętowe samego procesora/interfejsu :/

    Mateusz Salamon · 16/04/2021 o 11:47

    Ja wiem, czy niestety 😀 To wynika ze specyfikacji i standardów interfejsów, a nie jakies ograniczenia w STM32 🙂

    I2C jest wolnym interfejsem, więc OD mu nie przeszkadza. SPI może być niesamowicie szybki i z tego względu wymuszony jest tryb Push-Pull.

SaS · 27/03/2020 o 19:58

Skupiłeś się na stronie programowej całkowicie pomijając sprzęt. Najczęściej STM32 jest zasilany z 3,3V a diody WS2812 zasilane z 5V gwarantowany H na wejściu musi mieć 70% zasilania czyli 3,5V. Jak rozwiązać ten problem jednym rezystorem opisano w EdW i EP.

    Mateusz Salamon · 27/03/2020 o 20:15

    Racja. Jakoś nie zastanowiłem się nad tym skoro działało, ale to powinno być zmienione. Jednym rezystorem? Przez chwilę pomyślałem o Open Drain z podciągnięciem do 5V, ale SPI w STM32 jest chyba tylko Push-Pull. Masz pod ręką ten opis z EdW/EP?

      Rezasurmar · 15/04/2021 o 13:00

      Piny są Open Drain, wystarczy podpiąć rezystor pod +5V. Warto sprawdzić czy pin jest 5V tolerant. Ale z tego co pamiętam, 99% Pinów w STMach jest.

      Mateusz Salamon · 15/04/2021 o 13:38

      To ciekawe. Pokażesz mi w RM albo DS, że piny SPI jest w trybie OD?

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *