HW 10 (Nucleo) - Interaktivní aplikace s komunikací s nadřazeným počítačem

Termín odevzdání 05.05.2018 23:59 PDT
Povinné zadání 2b
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.

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 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 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í 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í 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().

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í
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í
courses/b3b36prg/hw/hw10.txt · Last modified: 2018/05/10 18:40 by faiglj