Termín odevzdání | 06.05.2017 23:59 PDT |
---|---|
Povinné zadání | 3b |
Volitelné zadání | není |
Bonusové zadání | není |
Počet uploadů | 5 |
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.
Binární obrazy aplikací (Nucleo i ovládací počítač) pro testování jsou součástí balíku 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 serial_nonblock.
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í lab10 a 2) programu pro ovládací počítač.
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.
MSG_STARTUP
s obsahem 'PRG-HW 10
' (packetová komunikace s definovanou délkou zprávy, tj. není to textový řetěz zakončeny '\0'.
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í.
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.
MSG_ABORT
, na kterou Nucleo odpovídá MSG_OK
nebo MSG_ERROR
MSG_ABORT
, tj. výpočet končí buď zasláním MSG_ABORT
nebo MSG_DONE
.
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
MSG_GET_VERSION
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
MSG_ABORT
Nucleo desce
chunk_id
na 0, ale pouze pokud výpočet neběží, jinak vypisuje hlášku na stderr.
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.
Komunikační zprávy odpovídají zprávám z úlohy na cvičení 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 <stdint.h> #include <stdbool.h> // 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 neblokovaným čtením, např. pro jednoduchost lze použít funkci serial_getc_timeout()
z 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()
.
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 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: ");
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
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í.
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í | |
---|---|
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í |