{{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;
}
}
*/