LPC810 WS2812 (SPI + SCT)

Cyfrowe diody WS2812B już na dobre rozpowszechniły się wśród amatorów. Niestety, nie da się sterować nimi bezpośrednio, więc dla  osób, które nie mają pojęcia o elektronice i programowaniu mikrokontrolerów pozostaje tylko wzorowanie się na dostępnych w internecie rozwiązaniach zaproponowanych przez inne osoby. W tym artykule opiszę jak można sterować tymi diodami za pomocą mikrokontrolera LPC810 z wykorzystaniem takich peryferii jak SPI i SCT.

Schemat podłączenia procesora

WS2812B-Schemat

Do portu P0.4 podłączona jest bramka tranzystora T1 przez rezystor R1. Tranzystor T1 w połączeniu z rezystorem R2 spełnia rolę negatora oraz konwertera poziomów. Rezystor R3 zabezpiecza tranzystor przed stałym napięciem które mogłoby pojawić się w przypadku zwarcia lub uszkodzenia diody LED. W trakcie normalnej pracy rezystor R3 ogranicza poziom zakłóceń generowanych podczas transmisji danych.
Uwagi!
– Nie wolno podłączać procesora do napięcia 5V, gdyż grozi to jego uszkodzeniem!
– Należy zwrócić uwagę na wybór tranzystora w przypadku próby stosowania zamienników. Popularne tranzystory typu BC547 czy BC847 nie nadają się do tego zastosowania.

Połączenie SPI i SCT

SPI można połączyć z timerem SCT tylko poprzez SWM (switch matrix). Procesory z grupy LPC81x posiadają do 18 portów i SWM we wszystkich jest jednakowy. Mikrokontroler LPC810 posiada tylko 6 portów wyprowadzonych na zewnątrz, ale SWM nadal posiada 18 slotów, więc niewyprowadzone sloty można wykorzystać do połączenia SPI z SCT. Z SPI zostały wykorzystane tylko sygnały SCK i MOSI, które za pomocą slotów 6 i 7 w SWM są podpięte do wejść CTIN0 i CTIN1 timera SCT.

WS2812B-SWM

Wyjście CTOUT0 zostało podpięte poprzez SWM do portu P0.4. Sygnał danych na wyjściu procesora jest zanegowany. Dzięki zanegowaniu sygnału można wykonać prosty konwerter poziomów logicznych za pomocą kilku łatwo dostępnych elementów.


UWAGA! Korzystanie z niepodłączonych do wyjść slotów w SWM wiąże się z ryzykiem, że w przyszłych wersjach krzemu producent może tę funkcję usunąć. Jeśli mamy niewykorzystane porty to lepiej skorzystać z tych portów.


Listing kodu źródłowego konfigurującego SWM:

static void InitSWM( void )
{
  LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);    // Włączamy zegar SWM
  LPC_SWM->PINENABLE0 = 0b111111111;
  LPC_SWM->PINASSIGN0 = (255 << 0)|(255 << 8)|(255 << 16)|(255 << 24);    // U0_TXD_O;   U0_RXD_I;   U0_RTS_O;   U0_CTS_I;
  LPC_SWM->PINASSIGN1 = (255 << 0)|(255 << 8)|(255 << 16)|(255 << 24);    // U0_SCLK_IO;   U1_TXD_O;   U1_RXD_I;   U1_RTS_O;
  LPC_SWM->PINASSIGN2 = (255 << 0)|(255 << 8)|(255 << 16)|(255 << 24);    // U1_CTS_I;   U1_SCLK_IO;   U2_TXD_O;   U2_RXD_I;
  LPC_SWM->PINASSIGN3 = (255 << 0)|(255 << 8)|(255 << 16)|(  6 << 24);    // U2_RTS_O;   U2_CTS_I;   U2_SCLK_IO;   SPI0_SCK_IO;
  LPC_SWM->PINASSIGN4 = (  7 << 0)|(255 << 8)|(255 << 16)|(255 << 24);    // SPI0_MOSI_IO;   SPI0_MISO_IO;   SPI0_SSEL_IO;   SPI1_SCK_IO;
  LPC_SWM->PINASSIGN5 = (255 << 0)|(255 << 8)|(255 << 16)|(  6 << 24);    // PI1_MOSI_IO;   SPI1_MISO_IO;   SPI1_SSEL_IO;   CTIN_0_I;
  LPC_SWM->PINASSIGN6 = (  7 << 0)|(255 << 8)|(255 << 16)|(  4 << 24);    // CTIN_1_I;   CTIN_2_I;   CTIN_3_I;   CTOUT_0_O;
  LPC_SWM->PINASSIGN7 = (255 << 0)|(255 << 8)|(255 << 16)|(255 << 24);    // CTOUT_1_O;   CTOUT_2_O;   CTOUT_3_O;   I2C_SDA_IO;
  LPC_SWM->PINASSIGN8 = (255 << 0)|(255 << 8)|(255 << 16)|(255 << 24);    // I2C_SCL_IO;    ACMP_O_O;   CLKOUT_O;   GPIO_INT_BMAT_O
  LPC_SYSCON->SYSAHBCLKCTRL &= ~(1<<7);    // Wyłączamy zegar SWM
}

Konfiguracja SPI

SPI pracuje w trybie 8 bitów, dane zatrzaskiwane są na narastającym zboczu sygnału zegarowego, a uaktualniane przy opadającym (SPI MODE 0). Prędkość transmisji została skonfigurowana na około 810kb/s.

Listing kodu źródłowego konfigurującego SPI:

static void SPIInit( void )
{
  LPC_SYSCON->SYSAHBCLKCTRL |= (1<<11);                // SPI0
  LPC_SPI0->CFG = (1 << 0) | (1 << 2);
  LPC_SPI0->DIV = 36;                                  // [36] 811k@PCLK == 30MHz
  NVIC_EnableIRQ(SPI0_IRQn);
}

Konfiguracja SCT

Do transkodowania sygnału SPI na WS2812B został użyty jeden licznik 16 bitowy, 2 rejestry porównujące, 5 zdarzeń, 2 stany, 2 wejścia i 1 wyjście. Do wejścia CTIN0 podawany jest sygnał SCK z SPI, a do wejścia CTIN1 sygnał MOSI. Zanegowany transkodowany sygnał zostaje wygenerowany na wyjściu CTOUT0. Licznik odlicza od 0 do 400 lub 800ns z krokiem 33,3ns. Czasy ustawione są w rejestrach MATCH_L[0]  i MATCH_L[1]  odpowiednio 12 i 24 cykle (12 x 33,3ns = 400ns, 24 x 33,3ns = 800ns). Narastające zbocze sygnału zegarowego SPI powoduje wygenerowanie zdarzenia EVENT2, a te z kolei uruchamia licznik oraz ustawia wyjście CTOUT0 w stan niski. Narastające i opadające zbocze na linii danych SPI powoduje wygenerowanie zdarzeń odpowiednio EVENT3 i EVENT4. Zdarzenia te służą do przełączenia stanu. Zdarzenie EVENT3 przełącza na stan STATE1, a zdarzenie EVENT4 przełącza na stan STATE0. W zależności od aktywnego stanu, aktywne są zdarzenia EVENT0 i EVENT1. Zdarzenia generowane są w momencie osiągnięcia przez licznik wartości zapisanych w rejestrach MATCH_L[ ]. Obydwa zdarzenia zatrzymują i resetują licznik oraz ustawiają wyjście CTOUT0 w stan wysoki.

Listing kodu źródłowego konfigurującego SCT:

static void SCTInit( void )
{
 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8);                //SCT
 LPC_SCT->CONFIG = (1 << 7);                            // 2x16b; bez przeładowania
 LPC_SCT->MATCH_L[0] = 12;                            // [12]400ns@30MHz
 LPC_SCT->MATCH_L[1] = 24;                            // [24]800ns@30MHz
 LPC_SCT->START_L = (1 << 2);                        // Event 2 uruchamia licznik
 LPC_SCT->STOP_L = (1 << 0) | (1 << 1);                // Event 0 i 1 zatrzymuje licznik
 LPC_SCT->LIMIT_L = (1 << 0) | (1 << 1);                // Event 0 i 1 zeruje licznik
 LPC_SCT->EVENT[0].CTRL = (1 << 12);                    // Event 0; wyzwalany przez MATCH_L[0];
 LPC_SCT->EVENT[0].STATE = (1 << 0);                    // Event 0; aktywny tylko gdy STATE0;
 LPC_SCT->EVENT[1].CTRL = (1 << 0) | (1 << 12);        // Event 1; wyzwalany przez MATCH_L[1];
 LPC_SCT->EVENT[1].STATE = (1 << 0) | (1 << 1);        // Event 1; aktywny gdy STATE0 i STATE1;
 LPC_SCT->EVENT[2].CTRL = (1 << 10) | (2 << 12);        // Event 2; wyzwalany przez narastający sygnał na IN0 ( SCK );
 LPC_SCT->EVENT[2].STATE = (1 << 0) | (1 << 1);        // Event 2; aktywny gdy STATE0 i STATE1;
 LPC_SCT->EVENT[3].CTRL = (1 << 6) | (1 << 10) | (2 << 12) | (1 << 14) | (1 << 15);        // Event 3; wyzwalany przez narastające zbocze na IN1 ( MOSI ); ustawia STATE1
 LPC_SCT->EVENT[3].STATE = (1 << 0) | (1 << 1);        // Event 3; aktywny gdy STATE0 i STATE1;
 LPC_SCT->EVENT[4].CTRL = (1 << 6) | (2 << 10) | (2 << 12) | (1 << 14);        // Event 4; wyzwalany przez opadające zbocze na IN1 ( MOSI ); ustawia STATE0
 LPC_SCT->EVENT[4].STATE = (1 << 0) | (1 << 1);        // Event 4; aktywny gdy STATE0 i STATE1;
 LPC_SCT->OUT[0].CLR = (1 << 2);                        // Wyjście 0 przełączane na stan niski przez event 2
 LPC_SCT->OUT[0].SET = (1 << 0) | (1 << 1);            // Wyjście 0 przełączane na stan wysoki przez eventy 0 i 1
 LPC_SCT->OUTPUT = 0x1;
 LPC_SCT->CTRL_L = (1 << 1) | (1 << 3);
}

Jeśli w projekcie wymagany jest sygnał niezanegowany, wystarczy przekonfigurować dwa rejestry sterujące wyjściem CTOUT0, oraz usunąć wstępną inicjalizację wyjścia.

LPC_SCT->OUT[0].SET = (1 << 2);                       // Wyjście 0 przełączane na stan wysoki przez event 2
LPC_SCT->OUT[0].CLR = (1 << 0) | (1 << 1);            // Wyjście 0 przełączane na stan niski przez eventy 0 i 1
LPC_SCT->OUTPUT = 0x1;

Wysyłanie danych przez SPI

Funkcja WS_Update() służy do inicjalizacji i odblokowania przerwania. Wysyłaniem danych zajmuje się funkcja obsługi przerwania SPI0_IRQHandler(). Po wysłaniu wszystkich danych  przerwanie od SPI jest blokowane. Program użytkownika powinien zadbać o to, aby przez następne 50us żadne dane nie zastałe wysłane w celu resetu interfejsu komunikacyjnego w diodach. Czas wykonywania funkcji obsługi przerwania od SPI to około 2,7us. W trakcie wysyłania danych przerwanie obciąża procesor w niecałych 30%.

Listing kodu źródłowego obsługi SPI:

void WS_Update( void  )
{
  SpiTxData = (uint8_t*)WsData;
  SpiTxSize = sizeof(WsData);
  LPC_SPI0->TXCTRL = (1 << 22) | (0x7 << 24);
  LPC_SPI0->INTENCLR = 0x3F;
  LPC_SPI0->INTENSET = (1 << 1);
}
void SPI0_IRQHandler(void)
{
   if( LPC_SPI0->STAT & (1 << 1))
   {
     if( SpiTxSize )
     {
       if(!(--SpiTxSize))
       {
         LPC_SPI0->TXCTRL |= (1 << 20);            // EOT
       }
       LPC_SPI0->TXDAT = *SpiTxData++;
     }
     else
     {
       LPC_SPI0->INTENCLR = 0x3F;
     }
   }
}

CDN…

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google

Komentujesz korzystając z konta Google. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s