Kurs STM32 LL cz. 8. Komunikacja USART w trybie przerwań

Komunikacja USART często wymaga przesyłania sporej ilości danych. W celu uzyskania niezawodności transmisji istotne jest odebranie wszystkich przychodzących danych. Pominięcie jednego z bajtów najczęściej będzie skutkowało tym, że ramka będzie wadliwa i będziemy musieli ją retransmitować. Odbieranie danych w pętli głównej może prowadzić bardzo często do tego typu zdarzeń. Nie zawsze będziemy w stanie na czas odczytać dane z bufora, ponieważ może wykonywać się inna część programu. Dlatego do tego typu zastosowań bardzo dobrym rozwiązaniem są przerwania. Pozwolą nam przerwać główny program i szybko oczytać dane z bufora RX, aby ich nie utracić. Jak do tego celu skonfigurować USART? O tym w dzisiejszej lekcji.

Przerwania w USART

Czym są przerwania i w jaki sposób realizowana jest ich obsługa za pomocą kontrolera NVIC dowiedzieliśmy się już w rozdziale o GPIO. Teraz czas na poznanie możliwości związanych z przerwaniami w przypadku komunikacji USART.

Zajmiemy się podstawowymi przerwaniami używanymi w przypadku komunikacji przez interfejs szeregowy USART, czyli przerwania od odbioru i wysyłania danych. Dodatkowo omówimy przerwanie IDLE, które określa, że linia RX jest wolna.

W mikrokontrolerze STM32G071 mamy do dyspozycji ponad 23 flagi przerwań USART, które informują o różnego rodzaju zdarzeniach. Umieszczone zostały w rejestrze USART_ISR. Dotyczą one obsługi sprzętowych kolejek FIFO, błędów transmisji oraz specyficznych zastosowań USART np. jako SPI lub LIN. Podstawowe z nich, którymi zajmiemy się dzisiaj to:

  • Flaga TXE – informuje, że rejestr danych wysyłanych TDR jest pusty. Pozwala nam określić, kiedy wrzucić nowe dane do rejestru bez obawy, że poprzednie zostaną nadpisane przed ich wysłaniem.
  • Flaga TC – informuje, że dane fizycznie zostały wysłane. Generowany jest po znaku STOP w ramce USART. W większości przypadków może być stosowany zamiennie z TXE, jednak czasami przydaje się ich rozróżnienie np. w przypadku, gdy chcemy po fizycznym wysłaniu danych przejść w inny tryb pracy na danym pinie.
  • Flaga RXNE – informuje, że w rejestrze RDR znajdują się dane. Dzięki temu wiemy, że ramka została odebrana i możemy odczytać je z rejestru odbiorczego.
  • Flaga IDLE – informuje, żę zakończyła się transmisja danych na linii RX. Bardzo przydatne przerwanie w przypadku odbierania danych, kiedy nie mamy określonej długości ramki, a chcemy wiedzieć, kiedy dane przestały przychodzić do magistrali USART.

Wiemy już jakie są podstawowe flagi przerwań. Jak jednak wygląda ich obsługa? Najważniejszą informacją jest to, że w momencie wystąpienia jakiegokolwiek z tych przerwań wywoływana jest ta sama funkcja, czyli w naszym przypadku USART2_IRQHandler(). To naszym zadaniem jest odczytanie, która flaga została ustawiona i jakiego typu zdarzenie spowodowało wywołanie przerwania.

Następnie, w zależności od obsługiwanego zdarzenia, musimy odpowiednio wyczyścić flagę. Sposób czyszczenia zależy od tego, jaka flaga została wywołana. W przypadku flag opisywanych w tej lekcji, wygląda to następująco:

  • Flaga TXE – flaga kasuje się automatycznie po wpisaniu danych do rejestru TDR
  • Flaga TC – flaga kasuje się automatycznie po wpisaniu danych do rejestru TDR
  • Flaga RXNE – flaga kasuje się automatycznie po odczytaniu danych z rejestru RDR
  • Flaga IDLE – flaga kasuje się po wpisaniu “1” na bicie IDLECF w rejestrze USART_ICR (USART Interrupt Clear Register)

Są to podstawowe metody kasowania omawianych flag przerwań. Więcej informacji można znaleźć w tabeli 186. [USART Interrupts requests] na stronie 1049 dokumentacji “Reference Manual”.

Dowiedzieliśmy się już, jak obsługiwać flagi przerwań. Na koniec została nam jeszcze jedna rzecz, którą tak właściwie będziemy wykonywali na samym początku 🙂 Musimy przerwania włączyć. Oprócz włączenia przerwań w kontrolerze NVIC, o czym już wiemy po rozdziale o GPIO, musimy jeszcze uruchomić przerwanie w rejestrach USART. Służą do tego bity TXEIE, TCIE, RXNEIE oraz IDLEIE w rejestrze USART_CR1.

Wszystkie projekty z kursu dostępne są w moim repozytorium GitHub.

[PROGRAM] Wysyłanie danych w trybie przerwań

Omówiliśmy podstawy działania przerwań interfejsu USART. Przejdźmy zatem do wysyłania danych w trybie IT.

Konfiguracja rejestrów USART dotycząca podstawowych parametrów ramki oraz prędkości komunikacji jest taka sama, jak w przypadku komunikacji w trybie polling.

LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
LL_USART_ConfigCharacter(USART2, LL_USART_DATAWIDTH_8B, LL_USART_PARITY_NONE, LL_USART_STOPBITS_1);
LL_USART_SetBaudRate(USART2, SystemCoreClock, LL_USART_PRESCALER_DIV1, LL_USART_OVERSAMPLING_16, 115200);
LL_USART_ConfigAsyncMode(USART2);

Aby korzystać z przerwań, musimy włączyć obsługę odpowiedniego wektora w kontrolerze NVIC oraz nadać priorytet obsługi przerwania.

NVIC_SetPriority(USART2_IRQn, 0);
NVIC_EnableIRQ(USART2_IRQn);

Na koniec włączamy blok wysyłania danych oraz uruchamiamy cały interfejs USART.

LL_USART_EnableDirectionTx(USART2);
LL_USART_Enable(USART2);

Teraz przejdziemy do części obsługi wysyłania danych. Tworzymy bufor oraz zmienną, w której będziemy przechowywali informację o tym, ile danych z bufora zostało już wysłanych. Ze względu na to, że zmienną txBuffer_send_data modyfikujemy w przerwaniu (może ona być w takim razie zmieniona spoza programu głównego), powinna ona mieć modyfikator volatile.

const uint8_t tx_buffer[6] = {"TEST\n\r"};
volatile uint32_t tx_buffer_count = 0;

Dodajemy teraz obsługę USART2_IRQHandler. W funkcji sprawdzamy, czy przerwania od flagi TXE są włączone oraz czy aktywna jest flaga pustego bufora. Jeżeli bufor nadawczy jest pusty, przechodzimy do obsługi wysyłania danych. Najpierw warto sprawdzić, czy nie wysłaliśmy już wszystkich danych z bufora. Jeżeli wysłane zostały wszystkie dane, zerujemy licznik wysłanych bajtów i wyłączamy przerwania. Jeżeli pozostały jeszcze jakieś dane do wysłania, wysyłamy kolejny bajt, zwiększając licznik wysłanych danych.

if(LL_USART_IsEnabledIT_TXE(USART2) && LL_USART_IsActiveFlag_TXE(USART2))
{
	if(tx_buffer_count == sizeof(tx_buffer))
	{
		tx_buffer_count = 0;
		LL_USART_DisableIT_TXE(USART2);

		return;
	}

	LL_USART_TransmitData8(USART2, tx_buffer[tx_buffer_count]);
	tx_buffer_count++;
}

Teraz, aby rozpocząć transmisję danych przez przerwania, wystarczy w programie głównym włączyć przerwania.

LL_USART_EnableIT_TXE(USART2);

Po zakończeniu transmisji danych przerwania zostaną wyłączone. Ponowne włączenie przerwań spowoduje rozpoczęcie transmisji danych z bufora od nowa.

[PROGRAM] Odbieranie danych w trybie przerwań

Nauczyliśmy się już wysyłać dane w trybie przerwań, zatem czas na ich odbiór.

Konfiguracja rejestrów USART dotycząca podstawowych parametrów ramki oraz prędkości komunikacji jest taka sama, jak w przypadku komunikacji w trybie polling.

LL_GPIO_SetPinPull(USART2_RX_GPIO_Port, USART2_RX_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(USART2_RX_GPIO_Port, USART2_RX_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetAFPin_0_7(USART2_RX_GPIO_Port, USART2_RX_Pin, LL_GPIO_AF_1);
LL_GPIO_SetPinMode(USART2_RX_GPIO_Port, USART2_RX_Pin, LL_GPIO_MODE_ALTERNATE);

Podobnie jak w przypadku wysyłania danych, aby korzystać z przerwań, musimy włączyć obsługę odpowiedniego wektora w kontrolerze NVIC oraz nadać priorytet obsługi przerwania.

NVIC_SetPriority(USART2_IRQn, 0);
NVIC_EnableIRQ(USART2_IRQn);

Na koniec włączamy blok wysyłania danych oraz uruchamiamy cały interfejs USART.

LL_USART_EnableDirectionRx(USART2);
LL_USART_Enable(USART2);

W przypadku wysyłania danych, przerwania od pustego rejestru nadawczego włączaliśmy na początku transmisji i wyłączaliśmy, gdy wszystkie dane zostały wysłane. Przy odbiorze danych zazwyczaj chcemy, aby interfejs był w trybie nasłuchiwania cały czas, aby wywołać obsługę przerwania, gdy tylko dane pojawią się w rejestrze odbiorczym. Możemy w takim razie włączyć przerwania od RXNE na początku programu i nie wyłączać ich wcale.

LL_USART_EnableIT_RXNE(USART2);

Teraz pozostaje obsługa przerwania i zapisywanie przychodzących danych do tablicy.

void USART2_IRQHandler(void)
{
	if(LL_USART_IsEnabledIT_RXNE(USART2) && LL_USART_IsActiveFlag_RXNE(USART2))
	{
		rx_buffer.data[rx_buffer.count] = LL_USART_ReceiveData8(USART2);
	        rx_buffer.count++;
	}
}

Gdzie bufor odbiorczy ma postać struktury buffer_t.


#define BUFFER_SIZE	128

typedef struct
{
	uint8_t data[BUFFER_SIZE];
	uint32_t count;
}buffer_t;

W ten sposób każda przychodząca dana zostaje zapisana do bufora bez obciążania CPU odpytywaniem o flagę w pętli głównej.

[PROGRAM] Odbieranie danych w trybie przerwań – IDLE

W trakcie komunikacji przez interfejs USART odbieramy dane znak po znaku. Jeżeli długość ramki jest znana, czekamy na odpowiednią ilość znaków i analizujemy odebrane dane. Często jednak nie znamy długości ramki danych. Wtedy bardzo przydatne staje się przerwanie od trybu bezczynności na linii RX, czyli IDLE.

Przerwanie IDLE generowane jest w momencie, gdy na linii RX po odebraniu bitów stopu pojawi się stan bezczynności. Nie przychodzą już żadne dane, czyli transmisja się zakończyła. Możemy w ten sposób zareagować na odebrane dane zapisane już do bufora, wiedząc, że fragment transmisji się zakończył i być może mamy już kompletne dane.

Jeżeli mamy uruchomione odbieranie danych na linii RX i przerwania od RXNE, wystarczy, że do konfiguracji dodamy uruchomienie przerwania IDLE. Przed jego włączeniem warto wyczyścić flagę przerwania, która może być ustawiona po włączeniu interfejsu USART.

LL_USART_ClearFlag_IDLE(USART2);
LL_USART_EnableIT_IDLE(USART2);

W mikrokontrolerach STM32G0 występuje drobny błąd w implementacji, który powoduje, że flaga IDLE jest ustawiana dopiero po chwili od uruchomienia magistrali USART. Jeżeli włączymy przerwanie IDLE zaraz po USART_Enable(), przerwanie może się wywołać jeden raz pomimo, że żadne dane jeszcze nie przyszły. Możemy to rozwiązać m.in. poprzez wprowadzenie małego opóźnienia po włączeniu USART lub poprzez właściwą obsługę w przerwaniu (ignorowanie przerwania, gdy licznik danych jest 0).

W obsłudze przerwań USART dorzucamy sprawdzenie flagi IDLE. Jeżeli przerwanie wystąpi, musimy ręcznie wyczyścić flagę a następnie wykonać np. analizę danych.

if(LL_USART_IsEnabledIT_IDLE(USART2) && LL_USART_IsActiveFlag_IDLE(USART2))
{
	LL_USART_ClearFlag_IDLE(USART2);

	//obsługa zdarzenia po odebraniu całej ramki
}

Przerwanie IDLE pozwala nam zaoszczędzić czas pracy mikrokontrolera. Analizę danych możemy wykonać dopiero, jak przyjdą wszystkie dane, a nie po każdym bajcie. Jest to bardzo fajne i pożyteczne usprawnienie zwłaszcza, gdy odbieramy spore ilości danych.

Chciałbyś otrzymywać na bieżąco informacje o nowych artykułach z kursu? Zapisz się do newslettera!

TO NIE TYLKO MAIL Z INFORMACJĄ O NOWEJ LEKCJI, ALE TAKŻE DODATKOWE MATERIAŁY. NIE PRZEGAP NOWEJ TREŚCI I DODATKOWYCH BONUSÓW. PRZEJDŹ DO STRONY KURSU I PODAJ SWÓJ ADRES E-MAIL. NIE ZAPOMNIJ POTWIERDZIĆ CHĘCI DOŁĄCZENIA W PIERWSZEJ WIADOMOŚCI!
Repozytorium GitHub

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *