Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

8 - Propojení STM32F446RE sériovou linkou

* pro vyučující: 08

Procvičovaná témata

Výchozí soubory lab08.zip včetně binárních obrazů aplikací pro STM32F446RE
Podpora Video tutoriál k mbed.org a Nucleo (97 min)
  • Seriová jednobytová komunikace v prostředí mbed.org
  • Seriová komunikace v OS
  • Kombinace seriové komunikace, blikání LED a reakce na tlačítko

Úkoly na cvičení

Jednobytová komunikace v mbed.org

Použijte objekt Serial a nastavte rychlost na 115200 baudů. Realizujte aktivní vyčítání s detekcí připravenosti bajtu na vstupu. Program bude reagovat na přijetí znaků

  • 's' - rozsvícení LED a pošle na seriový port znak 'a' (ack);
  • 'e' - zhasnutí LED a pošle na seriový port znak 'a' (ack);

v případě přijmutí jiného znaku pošle na seriový port znak 'f' (fail).

Po restartu Nuclea program krátce (5x s periodou 50 ms) zabliká a pošle na seriový port inicializační byte 'i' a zhasne LED.

#include "mbed.h"
 
Serial serial(SERIAL_TX, SERIAL_RX);
 
DigitalOut myled(LED1);
 
int main()
{
    serial.baud(115200);
    for (int i = 0; i < 5*2; ++i) { // 5x zablikání LED s periodou 50 ms
        myled = !myled;
        wait(0.05);
    }
    serial.putc('i');
    myled = 0;
    while (1) {
        while(!serial.readable()) {
            //aktivní čekání na přijetí bytu
        }
        int c = serial.getc();
        int ok = 1;
        switch(c) {
            case 's':
                myled = 1;
                break;
            case 'e':
                myled = 0;
                break;
            default:
                ok = 0;
        }
        while (!serial.writeable()) {}
        serial.putc(ok ? 'a' : 'f');
    }
}

Program otestujte terminálem připojeným k seriovému portu, např. GTKterm, cutecom nebo jerm.

Nastavení a čtení/zápis z/do sériového portu z OS

Rozhraní sériového portu je zpravidla přístupné jako soubor blokového zařízení v adresáři /dev, např. /dev/ttyUSB0, /dev/cuaU0 nebo /dev/ttyACM0, v závislosti na operačním systému a příslušném ovladači. Možností otevření a konfigurace sériového rozhraní je několik. Pro jednoduchost lze využít funkce

  • 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

modulu prg_serial.

S využítím funkce z “prg_serial.h” realizujte základní aplikaci pro komunikaci s Nucleo boardem, například s popisem jednotlivé akcí vypisované na terminal:

#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/cuaU0"; // cuaU0 nahraďte příslušným rozhraním sériového portu
   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");
	       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;
	    default: //nothing to do
	       break;
	 } // end switch c
	 if (write_read_response) {
	    int r = serial_putc(fd, c);
	    if (r != -1) {
	       fprintf(stderr, "DEBUG: Received response '%c'\n", r);
	    } else {
	       fprintf(stderr, "ERROR: Error in received responses\n");
	    }
	 }
      } // end while()
      serial_close(fd);
   } else {
      fprintf(stderr, "ERROR: Cannot open device %s\n", serial);
   }
   return ret;
}

Aplikace pro ovládání LED na Nucleo desce

Načítání stisku klávesy vyžaduje z důvodu bufferování standardního vstupu potvrzení koncem řádku, 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á 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

int ret = 0;
char c;
const char *serial = argc > 1 ? argv[1] : "/dev/cuaU0";
int fd = serial_open(serial);
if (fd != -1) { // read from serial port
   set_raw(true); // set the raw mode
   _Bool quit = false;
   while (!quit) {
      if ((c = getchar()) == 's' || c == 'e') {
         if (serial_putc(fd, c) == -1) {
            fprintf(stderr, "ERROR: Error in received responses\n");
            quit = true;
         }
      }
      quit = c == 'q';
   } // end while()
   serial_close(fd);
   set_raw(false);
} else {
   fprintf(stderr, "ERROR: Cannot open device %s\n", serial);
}

Program vyzkoušejte a také otestujte chování při náhlem odpojení Nucleo desky při běhu programu nebo resetu Nuclea desky.

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

a pošle znak 'a' jako indikátor potvrzení přijetí příkazu pro nastavení periody.

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 komunikace 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. V tomto jednoduchém případě můžeme v obsluze přerušení přímo rozsvít/zhasnout LED nebo alternativně nastavovat proměnnou a řídit periférie v hlavním cyklu.

V případě mbed.org lze využít objekt Ticker a metodu attached, která nastaví obsluhu přerušení na zadanou callback.

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

Křížová kompilace programu

Následující program demonstruje plnou funkčnost programu pro jednobytovou komunikaci a ovládání blikání LED zasláním znaků '1' až '5'. Největší část programu tvoří inicializace jednotlivých periferií a nastavení hardware časovače. Na druhou stranu, přímým přístupem k registrům mikroprocesoru lze dosáhnout pokročilejšího chování. Samotná hlavní smyčka v metodě main se omezuje na čtení/zápis do několika řídících registrů a neliší se tak výrazně od programu sestaveného v prostředí mbed. Zdrojové soubory projektu určené ke cross-kompilaci jsou k dispozici jako lab08_cross_compile.

#include "stm32f4xx_hal.h"
#include "stm32f4xx_hal_dma.h"
#include "stm32f4xx_hal_uart.h"
#include "stm32f4xx_hal_usart.h"
#include "system_stm32f4xx.h"
 
// volatile variable to enable/disable LED blinking
volatile int blink;
 
/* method to initilaize the LED on PA5 GPIO pin*/
void InitializeLEDs(void)
{
    RCC->AHB1ENR   |= (RCC_AHB1ENR_GPIOAEN); // enable clocks
    GPIOA->MODER   |= (0x1 << 10); // set port A pin 5 output
    GPIOA->OSPEEDR |= (0x1 << 11); // set port A pin 5 output speed fast
    GPIOA->PUPDR   |= (0x1 << 10); // set port A pin 5 pull-up
}
 
/* method to initialize the serial port interface */
void InitializeSerial(int baudrate){
    /* Initialize USART pins */
    RCC->AHB1ENR |= (RCC_AHB1ENR_GPIOAEN);  // enable clocks 
 
    GPIOA->MODER  = (GPIOA->MODER  & 0xffffff0f) | 0xA0;     //set PA2 and PA3 to alternate function '10'
    GPIOA->OTYPER = (GPIOA->OTYPER & 0xfffffff3);	     // set push-pull register
    GPIOA->PUPDR  = (GPIOA->PUPDR  & 0xffffff0f);	     // set no pull-up
    GPIOA->AFR[0] = (GPIOA->AFR[0] & 0xffff77ff) | 0x7700;   // set alternate function to USART
 
    /* Initialize USART core */
    RCC->APB1ENR |= (RCC_APB1ENR_USART2EN); // enable clocks
 
    USART2->CR1 &= ~USART_CR1_UE; // temporarily disable the core
    USART2->CR1 = (uint32_t)(USART_WORDLENGTH_8B | USART_PARITY_NONE | USART_MODE_TX_RX); // set parameters
 
    USART2->CR2 = (uint32_t)(USART_CLOCK_ENABLED | UART_STOPBITS_1);
    USART2->CR2 &= ~(USART_CR2_LINEN | USART_CR2_CLKEN);
 
    USART2->CR3 &= ~(USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN);
    USART2->BRR = __USART_BRR(HAL_RCC_GetPCLK1Freq(), baudrate);  // set baudrate
    USART2->CR1 |=  USART_CR1_UE;  	// enable the core
}
 
/* method to initilaize the timer2 */
void InitializeTimer()
{
    RCC->APB1ENR |= (RCC_APB1ENR_TIM2EN); // enable clocks
 
    TIM2->CR1 |= 0x1;   // Counter enable bit to start the timer
    TIM2->PSC  = 40000; // Prescale register
    TIM2->ARR  = 400;   // auto-reload register - the reset value of the counter
 
}
 
/* method to enable interrupts for timer 2 */
void EnableTimerInterrupt() {
    TIM2->DIER |= (0x1);         //enable interrupt
    NVIC_EnableIRQ(TIM2_IRQn);	 //enable interrupts from timer2
}
 
/* method to disable interrupts for timer 2 */
void DisableTimerInterrupt() {
    TIM2->DIER &= ~(0x1);        //enable interrupt
    NVIC_DisableIRQ(TIM2_IRQn);	 //enable interrupts from timer2
}
 
/* Interrupt handler routine for timer 2 */ 
extern void TIM2_IRQHandler()
{
    if (((TIM2->SR) & 0x01) == 1){    //read timer status register
    	TIM2->SR   &= ~(0x1);	      //clear timer status register
 
    	if(blink){
	    GPIOA->ODR ^= (0x1 << 5); 	//blink the led
    	}
    }
}
 
/* method to delay by busy loop */
void ms_delay(int ms) {
    while (ms-- > 0) {
        volatile int x = 5971;
        while (x-- > 0)
            __asm("nop"); // no operation
    }
}
 
/* main method */ 
int main()
{
    /* update frequency of the STM board to 160 MHz */
    SystemClock_Config();
    SystemCoreClockUpdate();
 
    /* Initialize PA5 LED GPIO pin */
    InitializeLEDs();
    /* Initialize serial port */
    InitializeSerial(115200);
    /* Initialize timer for LED blinking and enable interrupts */
    InitializeTimer();
    EnableTimerInterrupt();
 
    /* blink the LED 5 times */
    GPIOA->ODR |= (1 << 5);	//LED_on
    int i;
    for (i = 0; i < 5*2; ++i) { 
        ms_delay(100);
    	GPIOA->ODR ^= (1 << 5); //toggle LED
    }
 
    /* Send character i */
    USART2->DR |= 'i';
 
    int ok = 1;
    uint16_t periods[5] = { 200, 400, 800, 2000, 4000 };
    blink = 0;
 
    /* loop to receive from UART and blink the LED accordingly */
    while(1){
        while((USART2->SR & (0x01 << 5)) == 0){} // active wait for received byte
        ok = 1;
        char c = USART2->DR; // reading received byte
 
        switch(c){
            case 's':
                blink = 0;
                GPIOA->ODR |= (1 << 5);	//LED - on
	        break;
 	    case 'e':
	        blink = 0;
	        GPIOA->ODR &= ~(1 << 5);	//LED - off
	        break;	 			
	    default: 
	        if (c >= '1' && c <= '5') {
	      	    blink = 1;
	 	    TIM2->ARR = periods[c - '1'];	//update period counter
	            TIM2->EGR = 0x1;	        //reset timer
        	} else {
                    ok = 0;
        	}		
	        break;
            }
 	    while((USART2->SR & (0x01 << 7)) == 0){}    // active wait to allow sending
 	    USART2->DR =(ok ? 'a' : 'f');               // send response
	}
}

Další úkoly na cvičení

V další části cvičení pokračujte implementací domácího úkolu hw08.

courses/b3b36prg/labs/lab08.txt · Last modified: 2020/05/09 23:01 by faiglj