{{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|]].