Search
(Pro vyučující)
BufferedSerial
i
s
a
e
f
!
*
GTKterm
cutecom
jerm
\dev\ttyACM0
#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); } }
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 návodné stránce.
/dev
/dev/ttyUSB0
/dev/cuaU0
/dev/ttyACM0
dmesg
prg_serial
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í).
prg_serial.h
#include <stdio.h> #include <stdbool.h> #include <stdlib.h> #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; }
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ř.
cfmakeraw()
stty raw -echo
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ř.
system()
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.
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:
kill
SIGINT
reset
void handler(int signal_code) { set_raw_1(0); exit(0); } int main(){ signal(SIGINT, intHandler); ... }
Oba programy rozšiřte pro ovládání blikání LED zasláním znaků
1
2
3
4
5
0
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:
main
serial.read
ThisThread::sleep_for(…)
Ticker
attach