Toto je starší verze dokumentu!
Pomocí mikroprocesoru LPC407x zprostředkujte komunikaci mezi PC, proudovým zdrojem a integrovaným obvodem pixel light controller. Dále implementujte ukázkové funkce pro pixel light controller (wiping blinker, dark zone, …). Využijte plánovač (RTOS).
Tento projekt vychází z aktuálně řešené diplomové práce na téma Řízení maticového světlometu s LED diodami. Cílem této práce je úprava programu v mikroprocesoru tak, aby bylo dosaženo větší univerzálnosti, lepšího časování a přidání některých dalších funkcí. Jedná se o úpravu již funkčního programu, který pracuje jako velká nekonečná smyčka, do podoby pro využití operačního systému reálného času (RTOS).
Projekt využívá hardwarové prostředky, které byly vytvořeny v rámci diplomové práce. Konkrétně se jedná o takzvaný evaluační kit. Jeho blokové schéma je možné vidět na obrázku:
Výsledná realizace je zachycena na následující fotografii.
Práce stávajícího programu s periferiemi je řešena na úrovni registrů. Přímé použití registrů u procesoru LPC není příliš velký problém, protože příručka (User guide) a datasheet, dodávaný k těmto procesorům, jsou psány vcelku přehledně a srozumitelně, přesto v rámci úprav stávajícího programu bylo uvažováno o použití knihovny LPCOpen, která je oficiálně vydávaná výrobcem mikroprocesoru NXP. Balík LPCopen podporuje několik vývojových prostředí, v rámci toho také mnou použitý Keil uVision. Knihovna pro projekt se skládá ze dvou části – část pro mikroprocesor (lpc_chip) a část pro použitou desku (lpc_board). Každá z obou částí obsahuje vlastní projekt pro Keil uVision, kde jsou různé provázané zdrojové soubory, které obsahují všechny funkce, které knihovna obsahuje. Ty je možné upravit, a po sestavení projektu vznikne soubor *.lib. Oba soubory lpc_chip.lib a lpc_board.lib je třeba přidat do vlastního projektu. Knihovnu pro procesor je možné použít z výchozího stavu, knihovnu pro desku je třeba vždy upravit podle aktuálně použité desky. Větší problém tvoří horší dokumentace k těmto knihovnám a minimum ukázkových projektů, jeden příklad ke každé periferii, s nejistou funkčností. Vzhledem k těmto problémům bylo nakonec od použití knihovny LPCOpen upuštěno, zachováno řešení práce na úrovni registrů a věnování větší pozornosti operačnímu systému reálného času.
Z důvodu větší univerzálnosti bylo rozhodnuto o použití operačního systému reálného času. Byly uvažovány dva, konkrétně známý FreeRTOS a RTX, dodávaný spolu s vývojovým prostředím Keil uVision 5. Nakonec se jako největší problém ukázal vybraný mikrokontrolér, který není přímo podporovaný systémem FreeRTOS, proto byl vybrán druhý jmenovaný – RTX. Systém RTX je preemptivní operační systém, což znamená, že přepnutí tasků vyvolává samotný systém a nevyžaduje kooperaci s běžícími tasky, které by se musely vzdát běhu. RTX má na stránkách www.keil.com vcelku obsáhlou podporu, skládající se z obecného popisu, popisu všech funkcí a pár ukázkových příkladů. Systém RTX je třeba nastavit v samotném vývojovém prostředí a poté i ve zdrojovém souboru. Nastavení systému se nachází v souboru RTX_Conf_CM.c. Zde je možno například nastavit:
Nastavení systému se nachází v souboru RTX_Conf_CM.c. Zde je možno například nastavit:
Základní použité funkce systému RTX:
Program pro mikroprocesor je napsán v jazyce C ve vývojovém prostředí Keil uVision 5. Základem programu je operační systém reálného času RTX, který je pro zjednodušení provozován v konfiguraci Round – Robin, což znamená, že všechny tasky mají stejnou prioritu a k přepínání tasků dochází cyklicky. Perioda přepínání je nastavena na 1 ms.
Znázornění celkové funkce programu je znázorněna na následujícím obrázku.
Po startu programu programu ve funkci main dojde k inicializaci systému RTX, nastavení periferií a povolení přerušení od periferie UART0 při příjmu dat. Následně je spuštěn dočasný inicializační task, ve kterém jsou spuštěny všechny trvalé tasky, inicializovány semafory a mutexy. Po provedení těchto operací se inicializační task sám smaže. Kód tasku je pro ukázku uveden zde:
//------------------------- TASK: init -- (Begin) -------------------------------- // iniciation task, start other task __task void init (void) { // create tasks: t_blink = os_tsk_create(blink, 0); t_uart_tx = os_tsk_create(uart_tx, 0); t_interpreter = os_tsk_create(interpreter, 0); t_spi247 = os_tsk_create(spi247, 0); t_spi787 = os_tsk_create(spi787, 0); t_dc247 = os_tsk_create(dc247, 0); // init semaphores: os_sem_init (&Semspi247, 0); os_sem_init (&Semspi787, 0); os_sem_init (&Semdc247, 0); // init mutexes os_mut_init(&Mutspi247); os_mut_init(&Mutspi787); os_mut_init(&Mutuart_tx); //delete itself: os_tsk_delete_self (); } //------------------------- TASK: init -- (End) ----------------------------------
Při obvyklém běhu programu je spuštěno 6 tasků, z nichž dva běží neustále – task blink a interpreter, ostatní trvalé tasky čekají na semafor. Task blink pouze bliká jednou LED diodou na desce s periodou 1 s pro signalizaci běhu programu. Kód tasku je pro ukázku uveden zde:
//-------------------------- TASK: blink -- (Begin) --------------------------------- // Blinking with LED D5 __task void blink (void) { while(1) { LED_off(5); // turn off LED5 os_dly_wait (500); // wait for 500 ms LED_on(5); // turn on LED5 os_dly_wait (500); // wait for 500 ms } } //-------------------------- TASK: blink -- (End) ----------------------------------
Task interpreter přebírá přijatá data po lince UART z počítače a dále je třídí na základě požadované operace, případně danou operaci sám provede. Například pokud je požadována operace komunikace po SPI s obvodem zdroje, task interpreter pošle data do příslušného bufferu a zašle semafor tasku spi787, kterého tím odblokuje, a task spi787 vykoná příkaz, který mu byl předán příslušným bufferem. Po vykonání příkazu task spi787 odešle výsledná data do počítače přes UART linku tím, že přepošle data tasku uart_tx, který se postará o jejich korektní odeslání.
//------------------------- TASK: spi247 -- (Begin) -------------------------------- // SPI communication with pixel light __task void spi247 (void) { while(1) { os_sem_wait(&Semspi247, 0xFFFF); // wait for data (driven by semaphore) SPI247_ReadBuff = (SPI247_ReadBuff+1) & (BUFFERSPISIZE - 1); os_mut_wait(&Mutspi247, 0xFFFF); // wait for mutex SPI_SendFrame((SPI247_Buffer[SPI247_ReadBuff][0] & 0x07), 0, SPI247_Buffer[SPI247_ReadBuff][1], SPI247_Buffer[SPI247_ReadBuff][2], SPI247_Buffer[SPI247_ReadBuff][3], SPI247_Buffer[SPI247_ReadBuff][4]); // send SPI frame while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until first SPI frame is received SPI_RcvData = LPC_SSP2->DR; // received SPI frame SPI_RcvData = SPI_RcvData << 16; // shift first received SPI frame (higher) while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until second SPI frame is received SPI_RcvData = SPI_RcvData + (LPC_SSP2->DR & 0x0000FFFF); // add lower received SPI frame os_mut_wait(&Mutuart_tx, 0xFFFF); // wait for mutex UART_SendFrame (0x01); // send identification frame to PC UART_SendFrame (SPI247_Buffer[SPI247_ReadBuff][0]); // send target device UART_SendFrame (SPI_RcvData&0xFF); // send 1. byte (lowest) UART_SendFrame ((SPI_RcvData >> 8)&0xFF); // send 2. byte UART_SendFrame ((SPI_RcvData >> 16)&0xFF); // send 3. byte UART_SendFrame ((SPI_RcvData >> 24)&0xFF); // send 4. byte (highest) os_mut_release(&Mutuart_tx); // release mutex SPI_SendFrame((SPI247_Buffer[SPI247_ReadBuff][0] & 0x07), 0, (SPI247_Buffer[SPI247_ReadBuff][1] & 0xF0) + 0x0F, 0, 0, 0); while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until first SPI frame is received SPI_RcvData = LPC_SSP2->DR; // received SPI frame SPI_RcvData = SPI_RcvData << 16; // shift first received SPI frame (higher) while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until second SPI frame is received SPI_RcvData = SPI_RcvData + (LPC_SSP2->DR & 0x0000FFFF); // add lower received SPI frame if (((SPI247_Buffer[SPI247_ReadBuff][1]&0x0F) < 12)) { if(((SPI_RcvData&0xFF) == SPI247_Buffer[SPI247_ReadBuff][2]) && (((SPI_RcvData>>8)&0xFF) == SPI247_Buffer[SPI247_ReadBuff][3]) && (((SPI_RcvData>>16)&0xFF) == SPI247_Buffer[SPI247_ReadBuff][4])) temp++; } if((SPI247_Buffer[SPI247_ReadBuff][1] & 0x0F) == 0x0C) // write to control register? { if ((SPI247_Buffer[SPI247_ReadBuff][2] & 0x05) == 0x05) // write to MAPENA? { while ((SPI_RcvData&0x05) == 5) // wait for MAPENA { SPI_SendFrame((SPI247_Buffer[SPI247_ReadBuff][0] & 0x07), 0, SPI247_Buffer[SPI247_ReadBuff][1], SPI247_Buffer[SPI247_ReadBuff][2], SPI247_Buffer[SPI247_ReadBuff][3], SPI247_Buffer[SPI247_ReadBuff][4]); // send SPI frame while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until first SPI frame is received SPI_RcvData = LPC_SSP2->DR; // received SPI frame SPI_RcvData = SPI_RcvData << 16; // shift first received SPI frame (higher) while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until second SPI frame is received SPI_RcvData = SPI_RcvData + (LPC_SSP2->DR & 0x0000FFFF); // add lower received SPI frame } } } os_mut_release(&Mutspi247); // release mutex } // end of while(1) } //------------------------- TASK: spi247 -- (End) ----------------------------------
Dále software obsahuje tři dočasné tasky, které se vytváří a mažou dynamicky v případě, že je daný efekt potřeba. Například pokud přijde z počítače povel pro efekt Dark Zone, task interpreter vytvoří nový task dz, který se už sám stará o odesílání příslušných dat do SPI periferie. Task s efektem se maže opět příkazem z počítače. V jednu chvíli je možné mít spuštěných i více kopií stejného tasku, které obsluhují různé obvody integrovaného budiče.
//-------------------------- TASK: wb -- (Begin) ---------------------------------- // LED effect - Wiping blinker __task void wb (void) { uint8_t target = newtarget; // target device float wb_dt = 0; // time difference while(1) { wb_dt = (float)(wb_stop[target] - wb_start[target])/12.0; if (wb_counter[target] >= wb_per[target]) // counter overflow wb_counter[target] = 0; else wb_counter[target]++; // increment counter if (wb_counter[target] < wb_start[target]) // all LEDs OFF { for (i=0; i<12; i++) { DCs[i] = 1023; } j=0; } else if (wb_counter[target] < wb_stop[target]) //all LEDs dimmed { for (i=0; i<12; i++) { if (wb_counter[target] < (wb_start[target] + i*wb_dt)) DCs[i] = 1023; else if (wb_counter[target] < (wb_start[target] + (i+1)*wb_dt)) DCs[i] = (wb_start[target] + (i+1)*wb_dt - wb_counter[target])/wb_dt*1023; else DCs[i] = 0; } } else // all LEDs ON { for (i=0; i<12; i++) { DCs[i] = 0; } } dimming(DCs, LEDcount, ON, OFF, TR, lastLevel[target]); // ON, OFF, TR values computation using Dimming algorithm os_mut_wait(&Mutspi247, 0xFFFF); // wait for mutex SPI_SendFrame(target + 3, 1, 0xCF, 0, 0, 0); // send SPI frame to pixel light while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData = (LPC_SSP2->DR)<<16; // shift received data while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData += LPC_SSP2->DR; // sum received data SPI_SendFrame(target + 3, 1, 0xCC, ((SPI_RcvData)&(0xFB)), (SPI_RcvData>>8)&0xFF, (SPI_RcvData>>16)&0xFF); while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData = (LPC_SSP2->DR)<<16; // shift received data while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData += LPC_SSP2->DR; // sum received data for(i=0; i<12; i++) // send ON, OFF and TR values to pixel light { SPI_SendFrame(target + 3, 1, i + 0xC0, ((OFF[i]<<4)+TR[i])&0xFF, ((ON[i]<<6)+(OFF[i]>>4))&0xFF, (ON[i]>>2)&0xFF); while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData = (LPC_SSP2->DR)<<16; // shift received data while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData += LPC_SSP2->DR; // sum received data } SPI_SendFrame(target + 3, 1, 0xCC, ((SPI_RcvData)&0xFF)|(1<<2), (SPI_RcvData>>8)&0xFF, (SPI_RcvData>>16)&0xFF); //set MAPENA bit while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData = (LPC_SSP2->DR)<<16; // shift received data while ((LPC_SSP2->SR & 0x4)== 0x0){} // wait until frame is received SPI_RcvData += LPC_SSP2->DR; // sum received data os_mut_release(&Mutspi247); // release mutex os_dly_wait(20); } // end while(1) } //--------------------------- TASK: wb -- (End) -----------------------------------