12 - Propojení STM32F446RE sériovou linkou

  • 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í.

(Pro vyučující)

Jednobytová komunikace na straně Nucleo

  • Pracujeme v Keil 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í 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 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 návodné stránce.

  • 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 <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;
}

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í

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.)
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í.
courses/b3b36prg/labs/lab12.txt · Last modified: 2025/02/19 07:05 by faiglj