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
Následující program demonstruje použití knihovny HAL (Hardware Abstraction Layer) pro jednobytovou komunikaci a ovládání blikání LED zasláním znaků '1' až '5'. Pointa spočívá v tom, že jednotlivé periferie (piny, USB, ADC, …) jsou ve skutečnosti propojené s paměťovou sběrnicí a mají tedy v rámci adresního prostoru svou adresu. Knihovna HAL obsahuje přesné definice adres pro jednotlivé periferie, implementované do jazyka C jako ukazatele na struktury na konkrétních místech v adresním prostoru. Zápisem hodnoty do adresy se fyzicky nastaví napětí na daném obvodu.
Největší část programu níže tvoří inicializace jednotlivých periferií a nastavení hardware časovače. Přímým přístupem k registrům periferií 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.
#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 } }
Křížová kompilace znamená kompilování programu pro procesor s jinou architekturou, než má procesor, který kompilaci provádí. Prakticky to znamená použít vhodný kompilátor, který umí sestavit strojový kód dané architektury. Při programování MCU je pak také nutné správným způsobem slinkovat dílčí kompilační jednotky. Při linkování je nutné správně nastavit rozsahy jednotlivých typů pamětí (.data, .text, …).
.data
.text