{{indexmenu_n>12}} ====== 12 - Propojení STM32F446RE sériovou linkou ====== /* ^ Výchozí soubory | {{:courses:b3b36prg:labs:prg-lab11.zip}} včetně binárních obrazů aplikací pro STM32F446RE | */ * Seriová jednobytová komunikace na straně Nucleo. * Seriová jednobytová komunikace na straně počítače s OS. * Propojení Nuclea a počítače, blikání LED a reakce na tlačítko. * Diskuze kompilace, křížové kompilace a flashování. [[courses:b3b36prg:internal:tutorialinstruction:11|(Pro vyučující)]] ===== Jednobytová komunikace na straně Nucleo ===== Online prostředí Keil bývá nestabilní. Můžete využít experimentální offline prostředí. https://gitlab.fel.cvut.cz/valoudav/mbed-cli-setup-for-prg {{ courses:b3b36prg:labs:mbed-cli-setup-for-prg.zip |mbed-cli-setup-for-prg.zip}} * Pracujeme v [[https://studio.keil.arm.com/|Keil Studio]] nebo v [[https://os.mbed.com/studio/|Mbed Studio]]. Použijeme objekt ''BufferedSerial'' nad seriovým portem (realizovaným USB), s rychlostí nastavenou na 115200 baudů. * Nastavme chování objektu na "blokující". Tzn. MCU nepostoupí na další instrukci dokud nedorazí zpráva. * Po restartu Nuclea program krátce (5x s periodou 50ms) zabliká a pošle na seriový port inicializační byte ''i'' a zhasne LED. * Následně program bude reagovat na přijetí znaků * ''s'' - rozsvícení LED. Nucleo pošle zpět na seriový port znak ''a'' (ack). * ''e'' - zhasnutí LED. Nucleo pošle zpět na seriový port znak ''a'' (ack). * ''f'' - změní stav LED. Nucleo pošle zpět na seriový port znak ''a'' (ack). * Na jiný znak program odpoví chybovou zprávou ''!'' (fail). * V případě, že není zrovna k dispozici zpráva, Nucleo pošle znak ''*''. (Toto nastane v případě neblokovaného režimu.) * Program otestujte na počítači pomocí terminálu, např. ''GTKterm'', ''cutecom'' nebo ''jerm''. Terminál připojte k portu ''\dev\ttyACM0''. * Vyzkoušejte, jak se chování změní v neblokujícím režimu. Z pohledu hlubšího porozumění fungování vyčítání dat může být vhodnější variantou použití [[https://os.mbed.com/docs/mbed-os/v6.16/apis/unbufferedserial.html|UnbufferedSerial]] s definicí obsluhy přerušení pro přijem o odeslání jednoho bajtů s využitím vlastní kruhové fronty. Zejména se to hodí pro vícebajtové zprávy. Např. jako v [[https://cw.fel.cvut.cz/b212/courses/b3b36prg/labs/lab11|předchozích bězích předmětu]]. #include "mbed.h" DigitalOut myled(LED1); BufferedSerial serial (USBTX, USBRX, 115200); void blink(uint32_t i, Kernel::Clock::duration_u32 t = 50ms){ for(; i > 0; i--){ myled = true; ThisThread::sleep_for(t); myled = false; ThisThread::sleep_for(t); } } int main() { serial.set_blocking(true); const char init_msg = 'i'; serial.write(&init_msg, 1); blink(5); while(true){ char received_value; ssize_t received_in_fact = serial.read(&received_value, 1); char acknowledge_msg = 'a'; if(received_in_fact == 1){ switch(received_value){ case 's': myled = true; break; case 'e': myled = false; break; case 'f': myled = !myled; break; default: acknowledge_msg = '!'; } }else{ acknowledge_msg = '*'; } serial.write(&acknowledge_msg, 1); } } ===== Jednobytová komunikace na straně počítače s OS ===== V rámci operačního systému je zpravidla sériový port zpřístupňěn jako **soubor** (tzv. blokového zařízení) v adresáři ''/dev'', např. ''/dev/ttyUSB0'', ''/dev/cuaU0'' nebo ''/dev/ttyACM0'', v závislosti na konkrétním operačním systému a příslušném ovladači. (Po připojení Nuclea k počítači například spusťte příkaz ''dmesg'', který zobrazí jak bylo Nucleo rozpoznáno.) Možností otevření a konfigurace sériového rozhraní je několik. Pro jednoduchost lze využít funkce z modulu ''prg_serial'', který je dostupný na [[courses:b3b36prg:tutorials:serial|návodné stránce]]. {{:courses:b3b36prg:tutorials:prg_serial.zip|prg_serial}} * serial_open() - otevře daný soubor seriového portu a nastaví rychlost na 115200 bps * serial_close() - zavře daný file deskriptor * serial_putc() - zapíše daný znak do sériového portu * serial_getc() - přečte jeden znak ze sériového port S využítím funkcí z ''prg_serial.h'' nahraďte virtuální termínál použitý v minulé úloze (''GTKterm'' apod.) vlastní aplikaci pro komunikaci s Nucleo boardem. Aplikaci rozšiřte například popisem jednotlivých akcí výpisem na terminal. Uvědomte si, se kterými všemi soubory se pracuje (stdin, stdout, sériové rozhraní). #include #include #include #include "prg_serial.h" int main(int argc, char *argv[]){ int ret = 0; const char *serial = argc > 1 ? argv[1] : "/dev/ttyACM0"; fprintf(stderr, "INFO: open serial port at %s\n", serial); int fd = serial_open(serial); if (fd != -1) { // read from serial port _Bool quit = false; while (!quit) { int c = getchar(); _Bool write_read_response = false; switch (c) { case 'q': printf("Quit the program\n"); quit = true; break; case 's': printf("Send 's' - LED on\n"); write_read_response = true; break; case 'e': printf("Send 'e' - LED off\n"); write_read_response = true; break; case 'f': printf("Send 'f' - LED switch\n"); write_read_response = true; break; default: printf("Ignoring char '%d'\n", c); break; } if (write_read_response) { int r = serial_putc(fd, c); if (r != -1) { fprintf(stderr, "DEBUG: Received response '%d'\n", r); } else { fprintf(stderr, "ERROR: Error in received responses\n"); } } } serial_close(fd); } else { fprintf(stderr, "ERROR: Cannot open device %s\n", serial); } return ret; } ===== Nastavení režimu "raw" ===== Načítání stisku klávesy vyžaduje z důvodu bufferování standardního vstupu potvrzení koncem řádku (stisknutí klávesy enter), což není příliš šikovné. Proto nastate terminál do tzv. "raw" režimu např. prostřednictvím funkce ''cfmakeraw()'' nebo ''stty raw -echo'', viz 6. přednáška. Po skončení programu je vhodné terminál opět přepnout do běžného režimu, proto implementujte funkce pro přepínání do/z raw řežimu např. void set_raw(_Bool set) { static struct termios tio, tioOld; tcgetattr(STDIN_FILENO, &tio); if (set) { // put the terminal to raw tioOld = tio; //backup cfmakeraw(&tio); tio.c_lflag &= ~ECHO; // assure echo is disabled tio.c_oflag |= OPOST; // enable output postprocessing tcsetattr(STDIN_FILENO, TCSANOW, &tio); } else { // set the previous settingsreset tcsetattr(STDIN_FILENO, TCSANOW, &tioOld); } } Nebo využitím volání externího procesu funkcí ''system()'', jejímž argumentem je jméno programu spolu s příslušnými argumenty, např. void set_raw(_Bool set) { if (set) { system("stty raw -echo"); // enable raw, disable echo } else { system("stty -raw echo"); // disable raw, enable echo } } S využitím raw režimu terminálu zjednodušíme aplikaci pro ovládání LED na Nucleo desce ++++ Možné řešení | int ret = 0; char c; const char *serial = argc > 1 ? argv[1] : "/dev/ttyACM0"; int fd = serial_open(serial); if (fd != -1) { // read from serial port set_raw_1(1); // set the raw mode _Bool quit = 0; while (!quit) { if ((c = getchar()) == 's' || c == 'e' || c == 'f') { if (serial_putc(fd, c) == -1) { fprintf(stderr, "ERROR: Error in received responses\n"); quit = 1; } } quit = c == 'q'; } // end while() serial_close(fd); set_raw_1(0); } else { fprintf(stderr, "ERROR: Cannot open device %s\n", serial); } return ret; ++++ Program vyzkoušejte a také otestujte chování při náhlem odpojení Nucleo desky při běhu programu nebo resetu Nuclea desky. ** ** == Rozbitý terminál po ukončení programu v "raw" režimu == Pokud program nastaví terminál do režimu "raw" a skončí (například příkazem ''kill'', nebo-li signálem ''SIGINT''), terminál si může zachovat s původní nastavení. V takovém případě je možné zkusit příkaz ''reset'', nebo program vybavit handlerem signálu: void handler(int signal_code) { set_raw_1(0); exit(0); } int main(){ signal(SIGINT, intHandler); ... } ===== Aplikace pro ovládání blikání LED na Nucleo desce ===== Oba programy rozšiřte pro ovládání blikání LED zasláním znaků * ''1'' - nastaví periodu blikání LED na 50 ms * ''2'' - nastaví periodu blikání LED na 100 ms * ''3'' - nastaví periodu blikání LED na 200 ms * ''4'' - nastaví periodu blikání LED na 500 ms * ''5'' - nastaví periodu blikání LED na 1000 ms. * ''0'' - zastaví blikání, nechá LED ve stavu v jakém zrovna je. Nucleo pošle po spuštění znak ''i'' a každé přijetí příkazu pro nastavení periody potvrdí znakem ''a''. Aplikace na straně počítače bude fungovat v režimu "raw" a bude zobrazovat zprávy zaslané Nucleem. Blikání LED v programu pro Nucleo je možno realizovat třemi způsoby: * V hlavní smyčce funkce ''main'' v kombinaci s neblokující verzí ''serial.read'' za použití funkce ''ThisThread::sleep_for(...)''. * Objektem ''Ticker'' a metody ''attach'', která nastaví obsluhu přerušení podobně jako u tlačítka v minulém cvičení v kombinaci s blokující variantou sériového příjmu. * Program bude posílat s vhodnou frekvencí zprávy ''f''. Program pro Nucleo nebude nijak modifikovaný. (Tento způsob není ideální - pokud bychom měli pro Nucleo externí napájení, předchozí řešení by oproti tomuto zvládly blikat i po odpojení USB.) /* V aktuální verzi programu (viz výše) pro Nucleo je řízení LED realizováno v hlavním cyklu ve funkci ''main()'', která aktivně čeká na příjem znaku. Pro blikání LED nezávisle na komunikaci můžeme po dotazu na přítomnost znaku v bufferu sériového portu uvažovat čas po který LED svítí/nesvítí a realizovat tak blikání s požadovanou periodu. Výhodnější je v tomto případě použít časovač, který vyvolá přerušení (interupt) vždy po uběhnutí definované doby. */ Vypracováním a předvedením této úlohy cvičícímu na tomto nebo příštím cvičení lze získat až 6 bodů do hodnocení v rámci celého předmětu PRG. Kontrolovat se bude program na Nucleu a program na počítači nezávisle proti referenčnímu řešení. /* Ticker ticker; // callback function for LED blinking void tick() { myled = !myled; } // main function int main() { float periods[] = { 0.05, 0.1, 0.2, 0.5, 1.0 }; ... switch(c) { case 's': myled = 1; ticker.detach(); break; case 'e': ... default: if (c >= '1' && c <= '5') { ticker.attach(tick, periods[c-'1']); } else { ok = 0; } break; ... Dále rozšiřte ovládácí program pro posílání znaků '1' až '5' nastavující periodu blikání, např. if ( (c = getchar()) == 's' || c == 'e' || (c >= '1' && c <= '5') ) { if (serial_putc(fd, c) == -1) { fprintf(stderr, "ERROR: Error in received responses\n"); quit = true; } } */