Przedstawiam wstępny zarys libskika do komunikacji za pomocą komend kończących się określonym znakiem tylko za pomocą DMA.
Działanie odbioru już opisywałem - DMA jest skonfigurowane jednorazowo do pracy z buforem cyklicznym i pobieram z niego dane, kiedy wystąpi przerwanie np od powrotu karetki. Dane z cyklicznego bufora pobieram potem do bufora do przetworzenia.
Zalety względem programowej obsługi:
- nie wchodzimy w przerwanie od UARTu po każdym znaku, jedynie na końcu linii. W takim przerwaniu, trzeba było sprawdzić źródło wystąpienia, porównać indexy głowy i ogona węża, wpisać dane do bufora, obsłużyć porównanie znaku końca linii i zinkrementować głowę węża
Wady:
- kod do konfiguracji DMA, chociaż jest on wykonywany tylko jednorazowo
W przypadku wysyłania - tu jest więcej zabawy i inna koncepcja. DMA nie zrobi nam samo wysyłania cyklicznego - musimy podać ile chcemy zapisać. Pierwszy pomysł był taki - jeśli napis przekroczy index ostatniego elementu to rozbijamy to na 2 stringi - jeden od końcówki do końca i drugi od początku bufora do końca napisu, ale wydało mi się to jakoś nieoptymalne. Przyjąłem koncepcję kilku mniejszych buforów - musimy ich długość wyznaczyć na podstawie najdłuższej oczekiwanej wiadomości do przesłania. Działa to tak:
- ładujemy dane do pierwszego wolnego bufora i odpalamy DMA, jeśli wcześniej nie było uruchomione, jeśli juz jest uruchomione to przerwanie od zakonczenia wysyłania samo odpali kolejne wysyłanie. Jeśli wszystkie bufory są do wysłania - czekamy na zwolnienie. Można powiedzieć, że jest to "bufor cykliczny wskaźników na bufory"
Zalety:
- uproszczona funkcja wrzucająca dane do bufora - nie musimy już przeliczać tam indexów. Wrzucamy nasz string do danego bufora, jedynie licząc ilość elementów
- 0 wejsć w przerwanie UARTu od wysyłania przy każdym znaku, jedynie przy końcu transmisji w przerwanie od DMA
Wady:
- kod na obsługę tego wszystkiego za każdym razem przy odpaleniu nowego wysyłania
- koncepcja z kilkoma buforami może być trochę marnowaniem zasobów - powiedzmy, że nasze komendy mają zazwyczaj 10 znaków, ale mamy jeden przypadek z 50 znakami - wtedy minimalna długość pojedynczego bufora musi mieć te 50 bajtów
- w buforze cyklicznym, miejsce w pamięci zwalnia się natychmiastowo po przesłaniu bajtu - tutaj dopiero to przesłaniu całego pojedynczego bufora - to może być główną przeszkodą w użyciu
Kod napisany na STM32F030F4P6 na małym chińskim moduliku.
Kody z plików UARTu:
Kod: Zaznacz cały
/*
* uart.h
*
* Created on: 29 sie 2015
* Author: Przemek
*/
#ifndef __UART_H
#define __UART_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "../src/definicje.h"
#include "stm32f0xx.h"
#define UART_ZNAK_KONCA_LINII '\r'
#define UART_RX_BUF_SIZE 256
#define UART_TX_BUFFS_NUMBER 3
#define UART_TX_BUF_SIZE 50
// rejestry uartu jako makra:
#define UART_CR1(x) (x->CR1)
#define UART_CR2(x) (x->CR2)
#define UART_CR3(x) (x->CR3)
#define UART_BRR(x) (x->BRR)
#define UART_GTPR(x) (x->GTPR)
#define UART_RTOR(x) (x->RTOR)
#define UART_RQR(x) (x->RQR)
#define UART_ISR(x) (x->ISR)
#define UART_ICR(x) (x->ICR)
#define UART_RDR(x) (x->RDR)
#define UART_TDR(x) (x->TDR)
// numer naszego uartu:
#define UARTx USART1
#define UART_INTERRUPT_NUMBER USART1_IRQn
#define UART_RX_PORT GPIOA
#define UART_RX_PIN 10
#define UART_RX_ALTERNATE_FUNCTION_NUMBER 1
#define UART_TX_PORT GPIOA
#define UART_TX_PIN 9
#define UART_TX_ALTERNATE_FUNCTION_NUMBER 1
#define UART_RX_ALTERNATE _MODER(UART_RX_PORT) |= ( 2 << ( 2 * UART_RX_PIN ) )
#if UART_RX_PIN < 8
#define UART_RX_ALTERNATE_NR _AFRL(UART_RX_PORT) |= ( UART_RX_ALTERNATE_FUNCTION_NUMBER << ( 4 * UART_RX_PIN ) )
#else
#define UART_RX_ALTERNATE_NR _AFRH(UART_RX_PORT) |= ( UART_RX_ALTERNATE_FUNCTION_NUMBER << ( 4 * ( UART_RX_PIN - 8 ) ) )
#endif
#define UART_TX_ALTERNATE _MODER(UART_TX_PORT) |= ( 2 << ( 2 * UART_TX_PIN ) )
#if UART_TX_PIN < 8
#define UART_TX_ALTERNATE_NR _AFRL(UART_TX_PORT) |= ( UART_TX_ALTERNATE_FUNCTION_NUMBER << ( 4 * UART_TX_PIN ) )
#else
#define UART_TX_ALTERNATE_NR _AFRH(UART_TX_PORT) |= ( UART_TX_ALTERNATE_FUNCTION_NUMBER << ( 4 * ( UART_TX_PIN - 8 ) ) )
#endif
// inicjalizacja uartu
void init_uart(void);
// nasza funkcja przerwania
void USART1_IRQHandler(void);
void uart_wyslij_lancuch(char *s);
void uart_wyslij_liczbe(int16_t liczba);
// funkcja sprawdza, czy jakies dane znajduja sie w buforze odbiorczym
uint8_t uart_odbior_sprawdz();
// fukcja pobiera pojedynczy znak z bufora odbiorczego
uint8_t uart_pobierz_znak(void);
// funkcja sprawdza, czy odebrano linie tekstu
uint8_t uart_odebrano_linie( void );
// funkcja pobiera linie tekstu
void uart_pobierz_linie( uint8_t *buff );
void uruchom_wysylanie();
void DMA1_Channel2_3_IRQ();
#ifdef __cplusplus
}
#endif
#endif /* __UART_H */
Kod: Zaznacz cały
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "stdlib.h"
#include "uart.h"
#include "definicje.h"
#include "stm32f0xx.h"
#include "delay_systick.h"
// definiujemy nasz bufor UART_RxBuf
volatile char UART_RxBuf[UART_RX_BUF_SIZE];
volatile uint32_t rx_buff_pos = UART_RX_BUF_SIZE;
// definiujemy nasze bufory UART_RxBuf jako dwuwymiarowa tablice
volatile char UART_TxBuf[UART_TX_BUFFS_NUMBER][UART_TX_BUF_SIZE];
// tablica ze stanami naszych buforow uartu
// w tej zmiennej zawarte sa 3 rzeczy
// najmlodsze 8 bitow to ilosc znakow ktore DMA ma przeslac
// bit 8 zapalony oznacza, ze bufor wlanie jest przesylany
// bit 9 oznacza, ze bufor jest do przeslania
volatile uint16_t UART_TXBuf_status[UART_TX_BUFFS_NUMBER];
// numer kolejnego bufora w kolejce do wyslania
volatile uint8_t UART_TX_next_DMA_buff = 0;
// numer kolejnego bufora do wpisywania danych
volatile uint8_t UART_TX_next_RAM_buff = 0;
// zmienna oznaczajaca prace DMA
volatile uint8_t DMA_praca = 0;
// zmienna informujaca nas, ze czeka nowa linia do przetworzenia
volatile uint8_t otrzymane_linie = 0;
void USART1_IRQHandler(void)
{
// odbior znaku konca linii
if( ( UART_ISR(UARTx) & USART_ISR_CMF ) )
{
otrzymane_linie++;
// czyszczenie flagi przerwania
UART_ICR( UARTx ) = USART_ICR_CMCF;
}
}
uint8_t uart_odebrano_linie( void )
{
if( otrzymane_linie )
{
return 1;
}
return 0;
}
void uart_pobierz_linie( uint8_t *buf )
{
char c;
if(otrzymane_linie)
{
while( (c = uart_pobierz_znak() ) != UART_ZNAK_KONCA_LINII )
{
*buf++ = c;
}
*buf = 0;
otrzymane_linie--;
}
}
// funkcja uruchamia DMA do wyslania stringu
void uruchom_wysylanie()
{
// jesli kolejny bufor jest gotowy do wylania - rozpocznij wysylanie
if(UART_TXBuf_status[UART_TX_next_DMA_buff] & 0x0200 )
{
// zapalenie flagi dzialania DMA
DMA_praca = 1;
// wylaczenie kanalu - niezbedne, zeby zmienic parametry
DMA1_Channel2->CCR &= ~DMA_CCR_EN;
// wpisanie adresu startowego
DMA1_Channel2->CMAR = (uint32_t)( UART_TxBuf[UART_TX_next_DMA_buff] );
// wpisanie liczby znakow do wyslania
DMA1_Channel2->CNDTR = 0x00ff & UART_TXBuf_status[UART_TX_next_DMA_buff];
// wlaczenie DMA
DMA1_Channel2->CCR |= DMA_CCR_EN;
// zapalenie flagi przetwarzania - reszte danych mozemy utracic
UART_TXBuf_status[UART_TX_next_DMA_buff] = 0x0100;
}
// w przeciwnymprzypadku - nie ma nic do wysylania - wychodzimy bez niczego
}
void uart_wyslij_lancuch(char *s) // wysyła łańcuch z pamięci RAM na UART
{
// oczekiwanie na dostepnosc bufora uartu TODO jakis timeout itp
while( UART_TXBuf_status[UART_TX_next_RAM_buff] & 0x0100 )
{
}
// przesylanie naszego stringa TODO zapytac o to czy register dziala globalnie/lokalnie
register char c;
uint8_t licznik = 0;
uint8_t *wsk = UART_TxBuf[UART_TX_next_RAM_buff];
// przekopiowanie stringa do bufora
while ((c = *s++))
{
*wsk = c;
wsk++;
licznik++;
}
// wpisanie statusu dla bufora
UART_TXBuf_status[UART_TX_next_RAM_buff] = 0x0200 | licznik;
// inkrementacja indexu bufora
UART_TX_next_RAM_buff++;
if( UART_TX_next_RAM_buff > UART_TX_BUFFS_NUMBER )
{
UART_TX_next_RAM_buff = 0;
}
// sprawdzamy teraz, czy DMA jest uruchomione, jesli tak to ono samo zajmie sie wyslaniem naszego stringa
// jesli nie - musimy je uruchomic
if( !DMA_praca )
{
uruchom_wysylanie();
}
}
// definiujemy funkcję pobierającą jeden bajt z bufora cyklicznego
uint8_t uart_pobierz_znak(void)
{
// inkrementacja wskaznika na bufor i jego ograniczenie
rx_buff_pos++;
if( rx_buff_pos == UART_RX_BUF_SIZE )
{
rx_buff_pos = 0;
}
return UART_RxBuf[rx_buff_pos];
}
void uart_wyslij_liczbe(int16_t liczba)
{
char buf[17];
itoa(liczba, buf, 10);
uart_wyslij_lancuch(buf);
}
void init_uart(void)
{
rx_buff_pos = UART_RX_BUF_SIZE - 1;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// wlaczenie zegara dla UARTu
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
//wlaczenie zegara dla portu gpio:
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
// ustawienie piny jako alternate
UART_TX_ALTERNATE;
UART_RX_ALTERNATE;
// ustawiamy numer opcji alternatywnej
UART_TX_ALTERNATE_NR;
UART_RX_ALTERNATE_NR;
// dlugosc znaku (8bit) jest ustawione domyslnie
// tak samo jak 1 bit stopu
// ustawienie baudrate: zrobmy najpierw 9600:
UART_BRR(UARTx) = 48000000UL / 9600UL;
// uruchomienie DMA dla odbierania danych
// wlaczenie zegara dla DMA1
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
////////////////////////////////////////////////////////
// DMA dla RX
////////////////////////////////////////////////////////
// adres "z" - rejestr odbiorczy UARTU
DMA1_Channel3->CPAR = (uint32_t) ( &UART_RDR( UARTx ) );
// adres "do" - nasz bufor RX
DMA1_Channel3->CMAR = (uint32_t)( UART_RxBuf );
// licznik "przesylow" - rozmiar naszego bufora
DMA1_Channel3->CNDTR = UART_RX_BUF_SIZE;
// kierunek odczytu - z peryferium do pamieci jest domyslnym ustawieniem
// tryb cyrkularny - bedziemy w kolko krecic sie po naszym buforze
DMA1_Channel3->CCR |= DMA_CCR_CIRC;
// rozmiary danych wejsciowych i wyjsciowych domyslnie sa 8-bitowe
// inkrementacje wskaznikow tylko dla pamieci - dla peryferium staly adres
DMA1_Channel3->CCR |= DMA_CCR_MINC;
// wpisanie znaku konca linii ktory mamy wykrywac
UART_CR2(UARTx) &= 0x00ffffff;
UART_CR2(UARTx) |= UART_ZNAK_KONCA_LINII << USART_CR2_ADD_Pos;
// uruchomienie zezwolenia dla DMA od odbioru UARTU
UART_CR3(UARTx) |= USART_CR3_DMAR;
////////////////////////////////////////////////////////
// DMA dla TX
////////////////////////////////////////////////////////
// adres "x" - tego nie podajemy teraz, tylko w obsludze wysylania
//DMA1_Channel2->CMAR = (uint32_t) ( UART_TxBuf1 );
// adres "do" - rejestr wysylajacy dla UARTU
DMA1_Channel2->CPAR = (uint32_t)( &UART_TDR( UARTx ) );
// licznik "przesylow" - obecnie niepotrzebny - bedzie wpisywany w funkcji przesylajacej
//DMA1_Channel3->CNDTR = UART_RX_BUF_SIZE;
// kierunek odczytu - z pamieci do peryferium
DMA1_Channel2->CCR |= DMA_CCR_DIR;
// rozmiary danych wejsciowych i wyjsciowych domyslnie sa 8-bitowe
// inkrementacje wskaznikow tylko dla pamieci - dla peryferium staly adres
DMA1_Channel2->CCR |= DMA_CCR_MINC;
// uruchomienie zezwolenia dla DMA od wyslania danej UARTU
UART_CR3(UARTx) |= USART_CR3_DMAT;
// uruchomienie przerwania od zakoncznia transferu po DMA
DMA1_Channel2->CCR |= DMA_CCR_TCIE;
// uruchomienie przerwania nadawczego
// UART_CR1(UARTx) |= USART_CR1_TXEIE;
// // uruchomienie przerwania odbiorczego
// UART_CR1(UARTx) |= USART_CR1_RXNEIE;
// uruchomienie przerwania od odebranego znaku konca linii:
UART_CR1(UARTx) |= USART_CR1_CMIE;
// uruchomienie DMA dla RX
DMA1_Channel3->CCR |= DMA_CCR_EN;
// wlaczenie nadajnika:
UART_CR1(UARTx) |= (USART_CR1_TE);
// wlaczenie odbiornika:
UART_CR1(UARTx) |= (USART_CR1_RE);
// wlaczenie uartu:
UART_CR1(UARTx) |= USART_CR1_UE;
// wlaczenie przerwania w NVICu
NVIC_EnableIRQ( USART1_IRQn );
NVIC_EnableIRQ( DMA1_Channel2_3_IRQn );
}
void DMA1_Channel2_3_IRQHandler()
{
// jesli jest to przerwanie od naszego uartu
if(DMA1->ISR & DMA_ISR_TCIF2 )
{
// czyscimy flage wystapienia przerwania
DMA1->IFCR = DMA_IFCR_CTCIF2;
// obsluga naszego przerwania
// wstepnie gasimy flage naszej pracy
DMA_praca = 0;
// gasimy flage przesylania ostatniego bufora
UART_TXBuf_status[UART_TX_next_DMA_buff] = 0;
// inkrementujemy nasz wskaznik na bufor
UART_TX_next_DMA_buff++;
if( UART_TX_next_DMA_buff > UART_TX_BUFFS_NUMBER )
{
UART_TX_next_DMA_buff = 0;
}
// spradzamy teraz, czy jest cos do uruchomienia - jest tak - zaczynamy kolejna wysylke
// jesli nie - wychodzimy
uruchom_wysylanie();
}
}
#ifdef __cplusplus
}
#endif
Wszelkie uwagi mile widziane
