{{indexmenu_n>8}} ====== 8 - Propojení STM32F446RE sériovou linkou ====== * pro vyučující: [[courses:b3b36prg:internal:tutorialinstruction:08|]] ==== Procvičovaná témata ==== ^ Výchozí soubory | {{:courses:b3b36prg:labs:lab08.zip}} včetně binárních obrazů aplikací pro STM32F446RE | ^ Podpora | [[courses:b3b36prg:resources:nucleo|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 [[courses:b3b36prg:tutorials:serial|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 #include #include #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 {{:courses:b3b36prg:labs:lab08_cross_compile.zip|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 [[courses:b3b36prg:hw:hw08|]].