STM32F051 má dvě rozhraní USART. Jedno obyčejné (USART2)) a jedno šíleně nadupané (USART1). I ten obyčejný USART má ale dost široké možnosti použití. Oba USARTy zvládají přenos rychlostmi až 6Mb/s a obsahují hardwarové řízení toku. To je totiž při těchto rychlostech nezbytné. Přenos může být obsluhován pomocí DMA, což výrazně zjednoduší vysílání a přijímání objemných dat. Má zabudovanou podporu Multiprocesorové komunikace i schopnost komunikovat "po jednom drátě". Jako bonus je možné každý kanál invertovat a využít RTS pin jako DE (Device Enable) pokud využíváme externí driver (třeba pro RS485). Občas najde využití i automatická detekce datové rychlosti. Nadupaný USART má potom podporu LIN, Smartcard, Modbus, práce v režimu spánku a kdo ví čeho ještě. Pochybuji že se kdy k těmto funkcím dostanu, ale i ty běžné postačí na slušně dlouhý tutoriál. A to je také důvod proč ho jíst po pořádných soustech.
Protože je USART vybaven řízením toku, uděláme si malou rekapitulaci jeho signálních linek.
Seznam příkladů:
Pro první ukázku jsem si zvolil jednoduché odesílání počtu stisknutí tlačítka. Ideální sousto. Protože chci pouze vysílat, stačí mi nakonfigurovat pin PA9 a připojit ho spolu s GND k převodníku UART->USB. Používám modulek FT234XF přilepený tavnou pistolí na zadní stranu Discovery boardu (viz foto). Příklad je ale přirozeně nezávislý na typu převodníku. Takže si klidně vystačíte s čínským CH340 nebo PL2303, případně CP2102. Jen dávejte pozor aby některý z nich nepoužíval 5V logiku. Pokud budete chtít testovat i vyšší rychlosti, určitě na to zvolte modulek vybevený handshakingem. Vraťme se ale k příkadu. Pin PA9 je potřeba nakonfigurovat jako Alternative function s funkcí AF1 (USART1). Není od věci přidělit mu maximální rychlost, protože implicitně je zvolena nejnižší a ta by při vyšších datových tocích nestačila. Konfigurace USARTu spočívá jako obvykle ve vyplnění struktury a zavolání funkce LL_USART_Init(). Funkce počítá obsah registrů podle hodnoty buadrate a podle předpokládané frekvence USARTu. Program běžící s clockem z HSE tedy potřebuje znát jeho frekvenci. Jinak řečeno musíte mít korektně nastavenou konstantu HSE_VALUE (více zde). Nesmíme přirozeně zapomenout zapnout USARTu clock. Tady udělám malou zastávku. Je to specialita tohoto čipu, že lze volit pro USART1 různé zdroje clocku. Díky tomu může USART pracovat i v režimu spánku a vzbudit čip po přijmu dat. Po startu je automaticky zvolena jako zdroj clocku sběrnice APB1, ale já jse mdo zdrojového kódu pro jistotu přidal funkci, která tento zdroj clocku zvolí. U jiných čipů se tím nebudete muset zabývat. Nastavení clocku provádím ve funkci init_clock() a jádro i sběrnice taktuji na 48MHz. Po konfiguraci už USART pouze povolím funkcí LL_USART_Enable().
Tlačítko, kterým zahajujeme vysílání je připojené na PA0 a ve stisknutém stavu je na něm log.1. Abych ošetřil zákmity tlačítka využvám přerušení od Systick (více o něm zde), v němž 100x za vteřinu skenuji jeho stav. Jakmile je stisknuto inkrementuji počítadlo pocet_stisku a skrze proměnnou posli dávám vědět hlavní smyčce aby zahájila odeslání zprávy. V hlavní smyčce se pomocí funkce snprintf() nejprve připraví řetězec a poté se předá funkci usart1_puts(). Ta jej prochází a odesílá jednotlivé znaky tak dlouho než narazí na ukončovací znak řetězce. Odesílání je prováděno jednoduchým "pollingem". Funkce čeká dokud je vlajka TXE (Transmit data register empty) nulová. Ta značí že je vysílací buffer plný a není kam vložit další data. Jakmile se s odesláím předchozích dat buffer uvolní, vlajka se vynuluje a funkce do bufferu uloží další znak. No a to je vlastně všecho :)
Zdrojový kód ke stažení// 4A - USART1 jednoduché vysílání #include "stm32f0xx.h" #include "stm32f0xx_ll_bus.h" #include "stm32f0xx_ll_gpio.h" #include "stm32f0xx_ll_rcc.h" #include "stm32f0xx_ll_usart.h" #include "stm32f0xx_ll_utils.h" // kvůli konfiguraci clocku #include "stdio.h" // kvůli fci snprintf() void init_clock(void); void init_usart1(void); void usart1_putchar(uint8_t data); void usart1_puts(char *Buffer); LL_RCC_ClocksTypeDef clocks; volatile uint8_t posli=0; volatile uint16_t pocet_stisku=0; char text[31]; int main(void){ init_clock(); // 48MHz z HSE init_usart1(); SysTick_Config(480000); // přerušení 100x za vteřinu while (1){ if(posli){ // jakmile máme co odesílat posli=0; // už jsme to poslali snprintf(text,30,"Stisknuto %u krat\n\r",pocet_stisku); // připraví řetězec usart1_puts(text);// odešle řetězec } } } // Rutina přerušení Systick - pravidelná kontrola stavu tlačítka void SysTick_Handler(void){ static uint8_t button_status=1; if(!LL_GPIO_IsInputPinSet(GPIOA,LL_GPIO_PIN_0) && button_status==0){ button_status=1; // tlačítko stisknuto pocet_stisku++; // inkrementuj počítadlo stisků posli=1; // dej vědět hlavní smyčce ať pošle zprávu } if(LL_GPIO_IsInputPinSet(GPIOA,LL_GPIO_PIN_0)){ button_status=0; // tlačítko uvolněno } } void usart1_puts(char *Buffer){ while(*Buffer){ // než narazíš na konec řetězce (znak /0) while(!LL_USART_IsActiveFlag_TXE(USART1)){}; // čekej než bude volno v Tx Bufferu LL_USART_TransmitData8(USART1,*Buffer++); // předej znak k odeslání } } void init_usart1(void){ LL_USART_InitTypeDef usart; LL_GPIO_InitTypeDef gp; // konfigurace pinů - PA9 jako TX LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); // clock pro GPIOA LL_GPIO_StructInit(&gp); gp.Pin = LL_GPIO_PIN_9; gp.Mode = LL_GPIO_MODE_ALTERNATE; gp.Speed = LL_GPIO_SPEED_HIGH; gp.Alternate = LL_GPIO_AF_1; gp.Pull = LL_GPIO_PULL_NO; LL_GPIO_Init(GPIOA,&gp); // konfigurace USART LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_USART1); // clock pro USART1 z APB1 // USART1 si může vybírat z vícero zdrojů clocku (aby mohl běžet v režimu spánku) LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK1); // clock z APB1 // konfigurace USARTu usart.BaudRate = 9600; // 9600 b/s usart.DataWidth = LL_USART_DATAWIDTH_8B; usart.HardwareFlowControl = LL_USART_HWCONTROL_NONE; usart.OverSampling = LL_USART_OVERSAMPLING_16; // pokud není nouze o rychlost raději 16x usart.Parity = LL_USART_PARITY_NONE; usart.StopBits = LL_USART_STOPBITS_1; usart.TransferDirection = LL_USART_DIRECTION_TX; // pouze vysíláme LL_USART_Init(USART1,&usart); // můžete hlídat návratovou hodnotu ... LL_USART_Enable(USART1); } // 48MHz z externího 8MHz signálu void init_clock(void){ LL_UTILS_PLLInitTypeDef pll; LL_UTILS_ClkInitTypeDef clk; pll.Prediv = LL_RCC_PREDIV_DIV_2; // 8MHz / 2 = 4MHz pll.PLLMul = LL_RCC_PLL_MUL_12; // 4MHz * 12 = 48MHz clk.AHBCLKDivider = LL_RCC_SYSCLK_DIV_1; // APB i AHB bez předděličky clk.APB1CLKDivider = LL_RCC_APB1_DIV_1; // voláme konfiguraci clocku LL_PLL_ConfigSystemClock_HSE(8000000,LL_UTILS_HSEBYPASS_ON,&pll,&clk); // aktualizuj proměnnou SystemCoreClock SystemCoreClockUpdate(); }
Slíbil jsem vám, že si budeme v tutoriálu ukusovat větší sousta a s touto ukázkou jsme si ukousli jedno zvlášť výživné. Příjem je totiž citelně obtížnější než odesílání. Když odesíláte, máte spoustu času si vše připravit a nemusíte si dělat starosti s tím kdy data odesíláte. Příjem je naopak závod s časem. Příjem znaku je nanedálá událost na kterou musíte včas zareagovat a vyřídit ji rychle - dřív než dorazí další znak. Abychom to dokázali využijeme přerušení. Náš program bude mít za úkol přijmout příkaz (jehož konec rozpozná podle ukončovacího znaku \r nebo \n - stisk klávesy Enter v terminálu) a zareagovat na něj. Formát příkazu bude "c=12345" + stisk Enter. Hrubé rysy programu budou následující. Nakonfigurujeme USART1 tak aby vysílal i přijímal a aby s přijatým znakem vyvolal přerušení. V rutině přerušení budeme ukládat příchozí zprávu do dočasného pole až do okamžiku ky přijde ukončovací znak. Pak zprávu překopírujeme do jiného pole (abychom si tak uvolnili dočasné pole k přijmu dalšího příkazu) a dáme zbytku programu vědět, že je tu nový příkaz. Hlavní program tento nový příkaz zpracuje, pokusí se z něj vytáhnout užitečná data a odpoví nám.
Přerušení, pokud jste se s ním ještě na STM32 nesetkali, zprostředkovává NVIC (Nested Vector Interrupt Controller), který je součástí jádra. Funkce k jeho ovládání najdete v knihovně CMSIS v souboru core_cm0.h. KAždé přerušení lze povolit nebo zakázat a je možné mu nastavit prioritu. Priorita se nastavuje číslem 0 až 3 a čím je toto číslo nižší tím je priorita vyšší. Pokud běží rutina přerušení s nízkou prioritou může ji přerušení s vyšší prioritou přerušit (nehezká formulace). Seznam všech možných přerušení najdete v hlavičkovém souboru (v našem případě stm32f051x8.h). My budeme využívat přerušení USART1_IRQn. Všechny rutiny přerušení mají název odvozený od názvu přerušení, takže naše rutina se musí jmenovat USART1_IRQHandler(). USART může vyvolat přerušení jako reakci na mnoh různých událostí. Například na přijatý znak, na prázdný odesílací buffer, na ukončení přenosu, na různé chyby přenosu atd. Všechny tyto události skončí v jedné jediné rutině přerušení a je na vašem programu aby dokázal rozpoznat která z událostí přerušení vyvolala. Naše situace ale bude vcelku jednoduchá, my USARTu povolíme jako zdroj přerušení pouze událost RXNE (Receive buffer not empty - přišel znak). Ze slušnosti si ale v rutině přerušení ověříme jestli ji vyvolala opravdu tato událost. Nesmíme také zapomenout dát USARTu nějak vědět že jsme na jeho přerušení zareagovali, jinak by nadále trvalo a náš program by rutinu přerušení vykonával stále dokola. Žádost o přerušení se startuje nastavením tvz. vlajky (v našem případě RXNE) a způsob jejího vynulování je různý, podle toho o jakou vlajku jde. V případě RXNE stačí abychom přečetli přijatá data a dojde k automatickému smazání. V našem případě je tedy nezbytně nutné data přečíst ! Pokud budete využívat přerušení i od jiných událostí, musíte si v datasheetu zjistit jak jejich vlajky smazat.
Pro jistotu zrekapituluji to důležité. USART v reakci na různé události nastavuje vlajky. Některé vlajky mohou volat přerušení a lze individuálně povolit které to jsou. Rutina přerušení je jen jedna, takže je v ní potřeba zjistit jaká událost ji vyvolala (podle stavu vlajek). Než rutina přerušení skončí je potřeba odstranit podnět který ji vyvolal (vynulovat příslušnou vlajku) - což se dělá rozličnými způsoby. Tento postup bude shodný u přerušení od většiny dalších periferií.
Do teď jsme se zabývali přerušením, tedy schopností programu reagovat na nenadálou událost. V úvodu jsme ale diskutovali ještě jedno úskalí, nutnost zareagovat rychle, dřív než přijde další znak. Jak je na tom tedy naše funkce s reakčním časem ? Shrneme si to v přehledné tabulce níže.Při příjmu jediného znaku se v rutině přerušení nic moc neděje a program se v ní dlouho nezdržuje a je během 3us připraven přijímat další znak. Zajímavé to začne být v situaci kdy přijde poslední znak zprávy, v takovém případě musí program v rutině přerušení zkopírovat přijatou zprávu. To mu v případě průměrně dlouhé zprávy trvá přibližně 6.4us. Zpracování řetězce o maximální délce (30 znaků), tedy nejhorší možná situace trvá skro 16us. To znamená že pokud by znaky přicházely s časovým odstupem menším jak 16us, hrozilo by riziko že některý z nich nestihneme přijmout. V našem konkrétním případě ale nic takového nehrozí. Při datové rychlosti 9600b/s trvá přenos jednoho znaku 1/9600*10=1.04ms. I při datové rychlosti 115200b/s, kde přenos jednoho znaku trvá 86us, má funkce ještě rezervy.
Událost | Rakční čas rutiny přerušení | Reakční čas funkce sscanf() |
příjem jednoho znaku | 3 us | - |
dokončení příjmu zprávy "c=12345" | 6.4 us | 86.2us |
dokončení příjmu zprávy "aaaaa...30x" | 15.8 us | 9.2us |
// 4B - USART1 příjem řetězců s přerušením #include "stm32f0xx.h" #include "stm32f0xx_ll_bus.h" #include "stm32f0xx_ll_gpio.h" #include "stm32f0xx_ll_rcc.h" #include "stm32f0xx_ll_usart.h" #include "stm32f0xx_ll_utils.h" // konfigurace clocku #include "stdio.h" // kvůli fci snprintf() #include "string.h" #define MAX_STRLEN 32 // maximální počet znaků v přijímaném řetězci void init_clock(void); void init_usart1(void); void usart1_putchar(uint8_t data); void usart1_puts(char *Buffer); LL_RCC_ClocksTypeDef clocks; char command[MAX_STRLEN]; // přijatý řetězec (zpráva) char answer[MAX_STRLEN]; // řetězec pro zpracování odpovědi uint8_t num_commands=0; // informuje o příchodu nové zprávy uint32_t test=0; // něco zajímavého ve zprávě :) uint8_t scanned=0; // pomocná proměnná int main(void){ init_clock(); // 48MHz z HSE init_usart1(); while (1){ // pokud přišla nová zpráva if(num_commands){ scanned=sscanf(command,"c = %lu",&test); // pokus se ji přečíst if(scanned==0){usart1_puts("Error\n\r");} // pokud to nešlo else{ // pokud je v pořádku, připrav odpověď... snprintf(answer,MAX_STRLEN,"c = %lu, 2*c = %lu \n\r",test,test*2); usart1_puts(answer); // ...a pošli ji } num_commands=0; // dokončili jsme zpracování zprávy } } } void USART1_IRQHandler(void){ static char received_string[MAX_STRLEN]; // zde se bude ukládat přijímaná zpráva static uint16_t cnt = 0; // počítadlo přijatých znaků char t; // pokud je zdrojem přerušení přijatý znak if(LL_USART_IsActiveFlag_RXNE(USART1)){ t = LL_USART_ReceiveData8(USART1); // přečti přijatý znak (a maže vlajku RXNE - zdroj přerušení) // pokud příchozí znak není konec řádku a ještě máme místo v přijímací paměti if((t!='\r') && (t!='\n') && (cnt < (MAX_STRLEN-1))){ received_string[cnt] = t; // ulož příchozí znak do dočasného pole cnt++; // inkrementuj počítadlo znaků } else if(cnt>0){ // je to konec příchozí zprávy (pokud není prázdná) strncpy (command,received_string, cnt); // zkopíruj příkaz do pole command command[cnt]= '\0'; // přiřaď na konec řetězce ukončovací znak cnt = 0; // vynuluj počítadlo znaků num_commands++; // oznam hlavní smyčce že má zprávu ke zpracování } } } void usart1_puts(char *Buffer){ while(*Buffer){ // než narazíš na konec řetězce (znak /0) while(!LL_USART_IsActiveFlag_TXE(USART1)){}; // čekej než bude volno v Tx Bufferu LL_USART_TransmitData8(USART1,*Buffer++); // předej znak k odeslání } } void init_usart1(void){ LL_USART_InitTypeDef usart; LL_GPIO_InitTypeDef gp; // PA9 jako TX, PA10 jako RX LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); // clock pro GPIOA LL_GPIO_StructInit(&gp); gp.Pin = LL_GPIO_PIN_9 | LL_GPIO_PIN_10; gp.Mode = LL_GPIO_MODE_ALTERNATE; gp.Speed = LL_GPIO_SPEED_HIGH; gp.Alternate = LL_GPIO_AF_1; gp.Pull = LL_GPIO_PULL_NO; LL_GPIO_Init(GPIOA,&gp); // konfigurace USART LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_USART1); // clock pro USART1 z APB1 // USART1 si může vybírat z vícero zdrojů clocku (aby mohl běžet v režimu spánku) LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK1); // clock z APB1 // konfigurace USARTu usart.BaudRate = 9600; // 9600 bd/s usart.DataWidth = LL_USART_DATAWIDTH_8B; usart.HardwareFlowControl = LL_USART_HWCONTROL_NONE; usart.OverSampling = LL_USART_OVERSAMPLING_16; // pokud není nouze o rychlost raději 16x usart.Parity = LL_USART_PARITY_NONE; usart.StopBits = LL_USART_STOPBITS_1; usart.TransferDirection = LL_USART_DIRECTION_TX_RX; // vysíláme i přijímáme LL_USART_Init(USART1,&usart); // můžete hlídat návratovou hodnotu ... LL_USART_Enable(USART1); // konfigurace přerušení NVIC_SetPriority(USART1_IRQn,2); // nízká priorita (vysoké číslo) NVIC_EnableIRQ(USART1_IRQn); // povolit přerušení od USART1 // v USART1 povolit přerušení od RXNE (Receive buffer not empty) LL_USART_EnableIT_RXNE(USART1); } // 48MHz z externího 8MHz signálu void init_clock(void){ LL_UTILS_PLLInitTypeDef pll; LL_UTILS_ClkInitTypeDef clk; pll.Prediv = LL_RCC_PREDIV_DIV_2; // 8MHz / 2 = 4MHz pll.PLLMul = LL_RCC_PLL_MUL_12; // 4MHz * 12 = 48MHz clk.AHBCLKDivider = LL_RCC_SYSCLK_DIV_1; // APB i AHB bez předděličky clk.APB1CLKDivider = LL_RCC_APB1_DIV_1; // voláme konfiguraci clocku LL_PLL_ConfigSystemClock_HSE(8000000,LL_UTILS_HSEBYPASS_ON,&pll,&clk); // aktualizuj proměnnou SystemCoreClock SystemCoreClockUpdate(); }
Pokud jste se ještě neseznámili s DMA na STM32, doporučil bych vám odskočit si na tutoriál o DMA a prostudovat jej před tím než se pustíte do následujícího příkladu. Použití DMA při vysílání přináší nesporné výhody. První z nich je schopnost dosáhnout datové rychlosti až 6Mb/s, což nejspíš příliš často neoceníte. Tou druhou výhodou, je schopnost DMA zařídit celé vysílání za vás. A právě to si předvedeme. Celý proces bude vlastně jednoduchý. Vyčleníme si v RAM pole v němž budeme skladovat data k odeslání (v našem případě řetězec). USART nastavíme jako vysílač a povolíme mu aby generoval requesty pro DMA. USART umí generovat dva requesty. Request RXNE (Receive buffer not empty) a TXE (Transmitt buffer empty). My budeme využívat TXE, protože ten se generuje v okamžiku kdy je USART hladový (má prostor převzít další byte dat). DMA nastavíme tak aby s každým requestem přeneslo jeden byte z našeho pole (řetězce) do USART1->TDR registru. Jinak řečeno aby udělalo přesně to co děláme ručně pokud odesíláme "blokujicí" metodou jako v předchozích příkladech.
Před odesláním musíme DMA specifikovat kolik dat má přenést. V našem případě to bude odpovídat počtu znaků v řetězci. Pak už jen DMA spustíme. Hned po startu bude na DMA čekat request od hladového USARTu, který se s prázdným bufferem dožaduje svých dat. Takže DMA okamžitě přenese jede znak zprávy a nakrmený USART se dá ihned do vysílání. To povede k tomu že se buffer USARTu vyprázdní, vyvolá request a DMA přesune další znak. Takto celý proces poběží do té doby než DMA přesune přednastavený počet znaků. Potom se DMA zastaví (s výjimkou situace kdy používáte "circular" mód). Během toho se budou postupně nastavovat dvě vlajky - DMAx_FLAG_HTx (Half Transfer) a DMAx_FLAG_TCx (Transfer Complete). Jejich názvy napovídají co signalizují, HT indikuje, že byla přenesena polovina dat a TC značí že byl dokončen celý přenos. Za zmínku ještě stojí vlajka DMAx_FLAG_TEx (Transfer Error), která signalizuje že se něco pokazilo, ale o tom až jindy. Od zmínených událostí (HT,TC,TE) může DMA vyvolat přerušení a my toho využijeme. Necháme volat přerušení od Transfer Complete a skrze proměnnou uart_dma_transfer dáme hlavní smyčce na vědomí, že je přenos dokončen a že může zahájit další vysílání. Přirozeně můžete vlajku hlídat i "pollingem". Tím ale jádro připravíte o možnost dělat během přenosu něco jiného a celé použití DMA ztratí smysl. Tuto jinou činnost bude v našem příkladě představovat instrukce nudim_se++ .
Závěrem jen krátce připomenu, že pro náš přenos používáme DMA1 (F031 jich stejně víc nemá) a kanál 2. Volba kanálu je vázaná periferií od které chcete přijímat requesty, což je ale problematika se kterou jste se jistě už seznámili v tutoriálu o DMA. Více se dozvíte z komentářů ve zdrojovém kódu. Upozorňuji vás, že příklad je psán s pomocí SPL (Standard Peripheral Library) namísto LL_API, jež jsem použil v předchozích příkladech. SPL mám mnohem raději a budu v něm připravovat asi všechny další příklady. Naštěstí není principiálně příliš odlišné od toho co jste viděli v LL_API v rámci předchozích příkladů. Očekávám tedy, že vás přechod nebude tolik bolet :D
#include "stm32f0xx.h" #include "stdio.h" // kvůli snprintf() void USART1_Configuration(void); // nastaví GPIO + USART + DMA #define MAX_LEN 64 // maximální délka odesílaného řetězce uint8_t uart_buff[MAX_LEN]; // prostor pro odesílaná data uint16_t uart_buff_len; // délka odesílaných dat volatile uint8_t uart_dma_transfer=0; // informace o probíhajícím přenosu uint32_t nudim_se=0; // počítáme jak moc se program při přenosu nudí :D int main(void){ SystemCoreClockUpdate(); USART1_Configuration(); // nastaví GPIO + USART + DMA while(1){ // počkáme až přenos skončí nudim_se++; // jádro se nudí if(uart_dma_transfer==0){ // připravíme řetězec k odeslání a zjistíme si jeho délku uart_buff_len=snprintf(uart_buff,MAX_LEN-1,"x=%u\n\r",nudim_se); // nastavíme délku přenosu DMA_SetCurrDataCounter(DMA1_Channel2,uart_buff_len); // a spustíme to (request od USART_TXE už čeká) DMA_Cmd(DMA1_Channel2, ENABLE); uart_dma_transfer=1; // přenos probíhá nudim_se=0; // jádro se začíná nudit znovu } } } void DMA1_Channel2_3_IRQHandler(void){ // máme sice jen jeden zdroj pro toto přerušení, ale ze slušnosti zjišťujeme zda je to on if(DMA_GetFlagStatus(DMA1_FLAG_TC2)){ DMA_ClearFlag(DMA1_FLAG_TC2); // mažeme vlajku Transfer Complete DMA_Cmd(DMA1_Channel2, DISABLE); // vypínáme DMA - aby ho bylo možné rekonfigurovat uart_dma_transfer=0; // oznámíme zbytku programu, že přenos je dokončen } } void USART1_Configuration(void){ USART_InitTypeDef usart_is; GPIO_InitTypeDef gp; DMA_InitTypeDef dma; NVIC_InitTypeDef nvic; // init GPIO RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); gp.GPIO_Pin = GPIO_Pin_2; //PA2 Tx gp.GPIO_Mode = GPIO_Mode_AF; gp.GPIO_OType = GPIO_OType_PP; gp.GPIO_PuPd = GPIO_PuPd_NOPULL; gp.GPIO_Speed = GPIO_Speed_Level_3; GPIO_Init(GPIOA, &gp); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1); // AF1 => USART1 // init USART RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); usart_is.USART_BaudRate = 115200; usart_is.USART_WordLength = USART_WordLength_8b; usart_is.USART_StopBits = USART_StopBits_1; usart_is.USART_Parity = USART_Parity_No ; usart_is.USART_HardwareFlowControl = USART_HardwareFlowControl_None; usart_is.USART_Mode = USART_Mode_Tx; // pouze vysíláme USART_Init(USART1, &usart_is); // povolíme TXE události volat DMA request USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // spustíme USART USART_Cmd(USART1, ENABLE); // init DMA RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); dma.DMA_BufferSize = MAX_LEN; // před odesláním přepíšeme jinou hodnotou dma.DMA_DIR = DMA_DIR_PeripheralDST; // cíl přenosu - periferie dma.DMA_M2M = DMA_M2M_Disable; // režim M2M nechceme dma.DMA_MemoryBaseAddr = (uint32_t)(uart_buff); // adresa pole s daty k odeslání dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // data jsou 8bit dma.DMA_MemoryInc = DMA_MemoryInc_Enable; // zdrojovou adresu inkrementovat dma.DMA_Mode = DMA_Mode_Normal; // neppoužíváme "kruhový" režim dma.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->TDR); // cíl přenosu dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // data jsou 8bit dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // vše posílat na jednu adresu dma.DMA_Priority = DMA_Priority_Low; // priorita nízká (nízký baudrate, času je dost) DMA_Init(DMA1_Channel2, &dma); // aplikovat nastavení na channel2 if(DMA_GetFlagStatus(DMA1_FLAG_TC2)){ DMA_ClearFlag(DMA1_FLAG_TC2);} // kdyby náhodou DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE); // povolit přerušení od dokončení přenosu // konfigurace přerušení nvic.NVIC_IRQChannel = DMA1_Channel2_3_IRQn; nvic.NVIC_IRQChannelCmd = ENABLE; nvic.NVIC_IRQChannelPriority = 2; NVIC_Init(&nvic); }
Home
V1.03 23.7.2017 (edit. 02/2024)
By Michal Dudka (m.dudka@seznam.cz)