{{indexmenu_n>10}} ======== HW 10 (Nucleo) - Interaktivní aplikace s komunikací s nadřazeným počítačem ======== ^ **[[courses:b3b36prg:hw:start#Úlohy HW08, HW09 a HW10 | Časný termín]]** | **[[courses:b3b36prg:hw:start#Úlohy HW08, HW09 a HW10 | 27.04.2019 23:59 PDT]]** | ^ Termín odevzdání | **04.05.2019 23:59 PDT** | ^ **[[courses:b3b36prg:hw:start#Úlohy HW08, HW09 a HW10 | Finální termín]]** | **[[courses:b3b36prg:hw:start#Úlohy HW08, HW09 a HW10 | 8.05.2019 23:59 PDT]]** | ^ Povinné zadání | 2b | ^ Volitelné zadání | není | ^ Bonusové zadání | není | ^ Počet uploadů | bez omezení | Cílem úlohy je rozšířit si znalosti komunikace s Nucleo aplikací sériovou komunikací s vícebajtovými zprávy a zpracováním dalšího zdroje asynchronních událostí, tj. reakce na stisk tlačítka na Nucleo desce. Program vychází z řešení HW 09 a rozšiřuje Nucleo program o obsluhu tlačítka a vícebajtovou komunikaci. Podobně rozšiřuje ovládací aplikaci o vícebajtovou komunikaci. Dále je doporučeno rozšířit model vícevláknové aplikace o synchronizaci mezi vlákny zasíláním zpráv hlavnímu řídícímu vláknu. Výsledný program představuje dílčí základ pro implementaci semestrální práce. Binární obrazy aplikací (Nucleo i ovládací počítač) pro testování jsou součástí balíku {{:courses:b3b36prg:hw:prg-hw10.zip|}}. Pro korektní ukončení programu (po stisku klávesy 'q') je nutné ukončit načítání znaku ze seriového portu, proto realizujte načítání v tzv. //non-bloking// režimu s využitím ''poll'' funkce, implementace můžete založit na modulu ''prg_serial_nonblocking'', např. viz [[courses:b3b36prg:tutorials:serial_nonblock|]]. ===== Povinné zadaní ===== Realizujte programy pro desku Nucleo STM32F446RE a program pro ovládací počítač. Komunikaci mezi Nucleo deskou a ovládací aplikací realizujte seriovou komunikací (115200 bps, 8 bitů, bez parity). Úloha se skládá ze dvou částí: 1) programu pro Nucleo, který přímo odpovídá řešení úlohy na cvičení [[courses:b3b36prg:labs:lab10|]] a 2) programu pro ovládací počítač. Přestože oba programy sdílejí stejnou definici zpráv, v případě ''mbed.org'' je doporučeno pro jednoduchost držet celý program v jediném souboru. V případě křížové kompilace je naopak výhodnější sdílet zdrojové soubory s definicí a parsování dílčích zpráv. ==== Nucleo aplikace ==== * Po startu program 5x krátce zabliká LED pošle zprávu ''MSG_STARTUP'' s obsahem '''**PRG-HW 10**''' (packetová komunikace s definovanou délkou zprávy, tj. není to textový řetěz zakončeny '\0'. * Dále čeká na příkazy ze sériového portu, případně reaguje na stisk uživatelského tlačítka * Na zprávu ''MSG_GET_VERSION'' zasílá ''MSG_VERSION'' s příslušnou verzí programu. Zprávu lze zaslat i v průběhu výpočtu a Nucleo aplikace na ni v průběhu výpočtu také odpoví. * Na zprávu ''MSG_COMPUTE'' odpovídá ''MSG_ERROR'' při chybě nebo ''MSG_OK'' a zahajuje výpočet, při kterém bliká LED. Po dokončení dílčího výpočtu zasílá zprávu ''MSG_COMPUTE_DATA'' a po dokončení celé dávky výpočtu zasílá ''MSG_DONE'' a ukončuje blikání LED. * Běžící výpočet je možné přerušit zprávou ''MSG_ABORT'', na kterou Nucleo odpovídá ''MSG_OK'' nebo ''MSG_ERROR'' * Výpočet je také možné přerušit stisknutím tlačítka, které nejen ukončí výpočet a blikání LED, ale také zašle zprávu ''MSG_ABORT'', tj. výpočet končí buď zasláním ''MSG_ABORT'' nebo ''MSG_DONE''. ==== Ovládací aplikace ==== Ovládací aplikace čte klávesy bez nutnosti stisku enter (tj. terminálový vstup je v //raw// režimu), na které reaguje zasláním odpovídající zprávy Nucleo programu. Program reaguje na následující znaky * 'g' - get version, zasílá zprávu ''MSG_GET_VERSION'' * '1', '2', '3', '4', '5' - zasílá zprávu ''MSG_COMPUTE'' s počtem úloh (''nbr_tasks'') rovným desetinásobku stisknutého znaku, tj. 10 * (c - '0'), kde 'c' je stisknutý znak. V případě běžící výpočtu, není tento výpočet přerušen ani není zadán nový, ale aplikace vypíše hlášku na standardní chybový výstup. Každá další zadaná úloha zvyšuje ''chunk_id'' * 'a' - pokud běží výpočet posílá zprávu ''MSG_ABORT'' Nucleo desce * 'r' - nastavuje aktuální čítač ''chunk_id'' na 0, ale pouze pokud výpočet neběží, jinak vypisuje hlášku na stderr. * 'q' - korektním způsobem ukončuje aplikaci, tj. v případě běžícího výpočtu zasílá ''MSG_ABORT'', ukončuje čtení klávesnice a hlavní vlákno čeká na ukončení všech ostatních vláken a následně ukončuje program. === Komunikace s Nucleo deskou a definice zpráv a pomocných funkcí === Komunikační zprávy odpovídají zprávám z úlohy na cvičení [[courses:b3b36prg:labs:lab10|]]. V případě ovládací aplikace lze využít definice datových struktur pro zprávy v souboru ''messages.h'' /* * File name: messages.h */ #ifndef __MESSAGES_H__ #define __MESSAGES_H__ #include #include // Definition of the communication messages typedef enum { MSG_OK, // ack of the received message MSG_ERROR, // report error on the previously received command MSG_ABORT, // abort - from user button or from serial port MSG_DONE, // report the requested work has been done MSG_GET_VERSION, // request version of the firmware MSG_VERSION, // send version of the firmware as major,minor, patch level, e.g., 1.0p1 MSG_STARTUP, // init of the message (id, up to 8 bytes long string, cksum MSG_COMPUTE, // request computation of a batch of tasks (chunk_id, nbr_tasks) MSG_COMPUTE_DATA, // computed result (chunk_id, result) MSG_NBR } message_type; #define STARTUP_MSG_LEN 9 typedef struct { uint8_t major; uint8_t minor; uint8_t patch; } msg_version; typedef struct { uint8_t message[STARTUP_MSG_LEN]; } msg_startup; typedef struct { uint16_t chunk_id; uint16_t nbr_tasks; } msg_compute; typedef struct { uint16_t chunk_id; uint16_t task_id; uint8_t result; } msg_compute_data; typedef struct { uint8_t type; // message type union { msg_version version; msg_startup startup; msg_compute compute; msg_compute_data compute_data; } data; uint8_t cksum; // message command } message; #endif /* end of messages.h */ kde jsou dále deklarovány hlavičky pomocných funkcí délky zprávy, //marshalling// a //unmarshalling// zpráv. // return the size of the message in bytes bool get_message_size(uint8_t msg_type, int *size); // fill the given buf by the message msg (marhaling); bool fill_message_buf(const message *msg, uint8_t *buf, int size, int *len); // parse the message from buf to msg (unmarshaling) bool parse_message_buf(const uint8_t *buf, int size, message *msg); Načtení zprávy ze sériového portu může být podobné tomu na Nucleo desce, ale v případě dedikovaného vlákna stačí načítat data do bufferu a následně vytvořit novou zprávu (např. ''malloc(sizeof(message))'' ) voláním ''parse_message_buf()''. Takto vytvořenou zprávu je pak možné v architektuře aplikace boss/worker předat do fronty událostí, kde po vyčtení z fronty bude zpráva zpracována hlavním vláknem a následně bude paměť uvolněna. Samotné načtení je možné realizovat [[courses:b3b36prg:tutorials:serial_nonblock|neblokovaným]] čtením, např. pro jednoduchost lze použít funkci ''serial_getc_timeout()'' z {{:courses:b3b36prg:tutorials:prg_serial_nonblock.zip|prg_serial_nonblock}}. V případě vytvoření zprávy pak stačí připravenou zprávu typu ''message'' převést na posloupnost bytů (buffer) funkcí ''fill_message_buf()'' a příslušný buffer zapsat na seriový port např. funkcí ''write()''. === Fronta zpráv === Možností implementace ovládací aplikace je několik. Jednou z nich je dedikovat jedno vlákno na zpracování vstupu z klávesnice a druhé vlákno dedikovat na čtení ze sériového portu. Tato vlákna představují zdroje událostí, které mohou být přeposílány do hlavního vlákna prostřednictvím fronty zpráv. Komunikace směrem od hlavního vlákna je pouze při ukončení aplikace, tj. ukončení vláken. V našem případě aplikaci ukončujeme po stisknutí klávesy 'q', proto je postačující blokované čtení klávesnice v samostatném vlákně, které zašle zprávu s žádostí o ukončení aplikace hlavnímu vláknu a následně ukončí vyčítání kláves. Frontu zpráv realizujeme jako kruhový buffer o definované kapacitě zpráv a synchronizačními primitivy zajistíme čekání vlákna při vložení zprávy do plné fronty, neboť předpokládáme, že dříve nebo později dojde k vyčtení zprávy hlavním vláknem. Zprávy mohou být opět definovány různě, např. můžeme rozlišit zdroj zprávy (klávesnice, seriový port). Příklad zpráv je uveden v adresáři //inspiration// v podkladech pro domácí úkol. Dále je možná ukázka použití uvedena v komentářích přiloženého souboru ''hw10-main.c''. ==== Výstup aplikace ==== V ovládací aplikaci vypisujte dílčí stavy a události na standardní výstup. Rozlište mezi úrovni výpisu ''DEBUG'', ''ERROR'', ''INFO'' a ''WARN''. Např. fprintf(stderr, "ERROR: Cannot open serial port %s\n", serial); fprintf(stderr, "INFO: Create thread '%s' %s\r\n", threads_names[i], ( r == 0 ? "OK" : "FAIL") ); fprintf(stderr, "INFO: Get version requested\r\n"); fprintf(stderr, "INFO: New computation chunk id: %d no. of tasks: %d\n\r", msg.data.compute.chunk_id, msg.data.compute.nbr_tasks); fprintf(stderr, "WARN: New computation requested but it is discarded due on ongoing computation\n\r"); fprintf(stderr, "INFO: Chunk reset request\n\r"); fprintf(stderr, "WARN: Chunk reset request discarded, it is currently computing\n\r"); fprintf(stderr, "WARN: Abort requested but it is not computing\n\r"); fprintf(stderr, "ERROR: send_message() does not send all bytes of the message!\n\r"); fprintf(stderr, "INFO: Nucleo restarted - '%s'\r\n", str); fprintf(stderr, "INFO: Receive ok from Nucleo\r\n"); fprintf(stderr, "INFO: Nucleo firmware ver. %d.%d-p%d\r\n", msg->data.version.major, msg->data.version.minor, msg->data.version.patch); fprintf(stderr, "INFO: Nucleo firmware ver. %d.%d\r\n", msg->data.version.major, msg->data.version.minor); fprintf(stderr, "WARN: Receive error from Nucleo\r\n"); fprintf(stderr, "INFO: Abort from Nucleo\r\n"); fprintf(stderr, "INFO: New data chunk id: %d, task id: %d - results %d\r\n", msg->data.compute_data.chunk_id, msg->data.compute_data.task_id, msg->data.compute_data.result); fprintf(stderr, "WARN: Nucleo sends new data without computing \r\n"); fprintf(stderr, "INFO: Nucleo reports the computation is done computing: %d\r\n", computation.computing); fprintf(stderr, "INFO: Call join to the thread %s\r\n", threads_names[i]); fprintf(stderr, "INFO: Joining the thread %s has been %s\r\n", threads_names[i], (r == 0 ? "OK" : "FAIL")); fprintf(stderr, "INFO: Exit input thead %p\r\n", pthread_self()); fprintf(stderr, "DEBUG: computing: %d\r\n", computation.computing); fprintf(stderr, "ERROR: Unknown message type has been received 0x%x\n - '%c'\r", c, c); fprintf(stderr, "ERROR: Cannot parse message type %d\n\r", msg_buf[0]); fprintf(stderr, "WARN: the packet has not been received discard what has been read\n\r"); fprintf(stderr, "ERROR: Cannot receive data from the serial port\r\n"); fprintf(stderr, "INFO: Exit serial_rx_thread %p\r\n", pthread_self()); fprintf(stderr, "DEBUG: Write message: "); ==== Příklad výstupu aplikace ==== INFO: Create thread 'Input' OK INFO: Create thread 'Serial In' OK INFO: Nucleo restarted - 'PRG-HW 10' INFO: Get version requested INFO: Nucleo firmware ver. 0.9 INFO: New computation chunk id: 0 no. of tasks: 10 INFO: Receive ok from Nucleo INFO: New data chunk id: 0, task id: 0 - results 238 INFO: New data chunk id: 0, task id: 1 - results 222 INFO: New data chunk id: 0, task id: 2 - results 184 INFO: New data chunk id: 0, task id: 3 - results 95 INFO: New data chunk id: 0, task id: 4 - results 243 INFO: New data chunk id: 0, task id: 5 - results 101 INFO: New data chunk id: 0, task id: 6 - results 60 INFO: New data chunk id: 0, task id: 7 - results 12 INFO: New data chunk id: 0, task id: 8 - results 132 INFO: New data chunk id: 0, task id: 9 - results 184 INFO: Nucleo reports the computation is done computing: 1 INFO: New computation chunk id: 1 no. of tasks: 20 INFO: Receive ok from Nucleo INFO: New data chunk id: 1, task id: 0 - results 213 INFO: New data chunk id: 1, task id: 1 - results 22 INFO: New data chunk id: 1, task id: 2 - results 9 INFO: New data chunk id: 1, task id: 3 - results 3 INFO: New data chunk id: 1, task id: 4 - results 237 WARN: Nucleo sends new data without computing INFO: Receive ok from Nucleo INFO: New computation chunk id: 2 no. of tasks: 30 INFO: Receive ok from Nucleo INFO: New data chunk id: 2, task id: 0 - results 191 INFO: New data chunk id: 2, task id: 1 - results 251 INFO: New data chunk id: 2, task id: 2 - results 144 INFO: New data chunk id: 2, task id: 3 - results 178 INFO: Abort from Nucleo INFO: Chunk reset request INFO: New computation chunk id: 0 no. of tasks: 10 INFO: Receive ok from Nucleo INFO: New data chunk id: 0, task id: 0 - results 30 INFO: New data chunk id: 0, task id: 1 - results 187 INFO: New data chunk id: 0, task id: 2 - results 250 INFO: New data chunk id: 0, task id: 3 - results 200 INFO: New data chunk id: 0, task id: 4 - results 44 INFO: New data chunk id: 0, task id: 5 - results 249 INFO: New data chunk id: 0, task id: 6 - results 167 INFO: New data chunk id: 0, task id: 7 - results 207 INFO: New data chunk id: 0, task id: 8 - results 43 INFO: New data chunk id: 0, task id: 9 - results 202 INFO: Nucleo reports the computation is done computing: 1 INFO: New computation chunk id: 1 no. of tasks: 20 INFO: Receive ok from Nucleo INFO: New data chunk id: 1, task id: 0 - results 250 INFO: New data chunk id: 1, task id: 1 - results 3 INFO: New data chunk id: 1, task id: 2 - results 26 INFO: Get version requested INFO: New data chunk id: 1, task id: 3 - results 216 INFO: Nucleo firmware ver. 0.9 INFO: New data chunk id: 1, task id: 4 - results 208 INFO: New data chunk id: 1, task id: 5 - results 74 INFO: New data chunk id: 1, task id: 6 - results 46 INFO: New data chunk id: 1, task id: 7 - results 174 INFO: Abort from Nucleo INFO: Exit input thead 0x801016500 INFO: Exit serial_rx_thread 0x801016a00 INFO: Call join to the thread Input INFO: Joining the thread Input has been OK INFO: Call join to the thread Serial In INFO: Joining the thread Serial In has been OK ====== Odevzdání a hodnocení ====== Program pokud možno realizujte na cvičení, nahrajte do odevzdávacího systému. Funkčnost programu ověří učitel na cvičení nebo na dalším cvičení. Program můžete implementovat jak v prostředí ''mbed'' tak přímo s křížovou kompilací. V případě ''mbed'' pojmenujte soubor ''hw10-mbed.cpp'' a v případě křížové kompilace pak ''hw10-cross.c''. V případě ovládacího programu pojmenujte hlavní soubor s ''main()'' funkcí ''hw10-main.c'' ^ ^ Povinné zadání ^hw10-main.c ^ Název v BRUTE | HW10 | ^ Odevzdávané soubory | ''hw10-main.c'' případně další potřebné soubory (včetně ''Makefile'') a ''hw10-mbed.cpp'' nebo ''hw10-cross.c'' | ^ Argumenty při spuštění | žádné | ^ Procvičované oblasti | programování pro Nucleo a vícevláknové programování |