Kurs STM32 LL cz. 15. Konwersja ADC Single Channel i Multi Channel w trybie przerwań

Konwerter ADC oferuje możliwość wywołania dość sporej ilości przerwań, które odnoszą się nie tylko do zakończenia konwersji i obsługi błędów, ale także etapów konfiguracji. W dzisiejszej lekcji pokaże, w jaki sposób wykorzystać przerwania do obsługi odczytu danych z przetwornika.

Źródła przerwań są bezpośrednio powiązane z flagami dostępnymi w rejestrze ADC_ISR.

Wśród dostępnych źródeł przerwań możemy znaleźć:

  • Zakończenie kalibracji (EOCAL)
  • Uruchomienie ADC (ADRDY)
  • Zakończenie konwersji (EOC)
  • Zakończenie sekwencji konwersji (EOS)
  • Zdarzenie od Analog Watchdog (AWD1, AWD2, AWD3)
  • Zakończenie konfiguracji kanałów ADC (CCRDY)
  • Zakończenie fazy próbkowania (EOSMP)
  • Wystąpienie nadpisania danych (OVR)

Nas na tym etapie nauki będą interesowały dwa przerwania: zakończenie konwersji (EOC) oraz zakończenie sekwencji konwersji (EOS), które będą informowały o zakończeniu przetwarzania danych przez ADC kanału lub całej grupy kanałów.

Do obsługi tych przerwań będziemy potrzebowali dwóch rejestrów: ADC_ISR do odczytu flagi, które zdarzenie wystąpiło oraz ADC_IER, czyli rejestr włączający generowanie wybranych przerwań.

Rejestr ADC_ISR – rejestr statusu flag ADC

Bity EOC i EOS – koniec konwersji i koniec sekwencji konwersji

Rejestr ADC_IER – rejestr włączający przerwania ADC

Bity EOCIE i EOSIE – włączenie przerwania od końca konwersji i końca sekwencji konwersji

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

[PROGRAM] Konwersja w trybie przerwań (Single Channel)

Przykład wykorzystujący przerwania będzie analogiczny, jak w przypadku trybu polling.  Wykorzystamy potencjometr podłączony do kanału 4 ADC, czyli do pinu PA4.

Konfigurujemy pin PA4 w trybie analogowym.

LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
LL_GPIO_SetPinPull(ADC_Input_GPIO_Port, ADC_Input_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(ADC_Input_GPIO_Port, ADC_Input_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(ADC_Input_GPIO_Port, ADC_Input_Pin, LL_GPIO_MODE_ANALOG);

Teraz przystępujemy do konfiguracji przetwornika ADC. Na początku włączamy taktowanie ADC.

LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);

Wybieramy rozdzielczość oraz sposób wyrównania danych.

LL_ADC_SetResolution(ADC1, LL_ADC_RESOLUTION_12B);
LL_ADC_SetDataAlignment(ADC1, LL_ADC_DATA_ALIGN_RIGHT);

Następnie ustawiamy zegar dla ADC. Wybierzemy taktowanie z sygnału APB z dzielnikiem 4. ADC możemy być taktowany maksymalnie z częstotliwością 35 MHz, dlatego ustawimy mniejszą wartość, czyli 16 MHz.

LL_ADC_SetClock(ADC1, LL_ADC_CLOCK_SYNC_PCLK_DIV4);

Teraz możemy ustawić tryb konwersji. Do pomiarów wykorzystamy tryb pojedynczego pomiaru.

LL_ADC_REG_SetContinuousMode(ADC1, LL_ADC_REG_CONV_SINGLE);

Sposób konfiguracji kanałów ustawiamy jako stały. Czekamy na wykonanie konfiguracji i czyścimy flagę.

if(LL_ADC_REG_GetSequencerConfigurable(ADC1) != LL_ADC_REG_SEQ_FIXED)
{
	LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_FIXED);
	while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
		;
	LL_ADC_ClearFlag_CCRDY(ADC1);
}

Czas próbkowania ustawiamy najpierw w pierwszym rejestrze SMP1, a następnie przypisujemy go do wybranego kanału.

LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_39CYCLES_5);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_4, LL_ADC_SAMPLINGTIME_COMMON_1);

Aby korzystać z kanału 4, włączamy go w rejestrze sekwencera CHSELR. Czekamy, aż konfiguracja kanałów się wykona i czyścimy flagę.

LL_ADC_REG_SetSequencerChannels(ADC1, LL_ADC_CHANNEL_4);

while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
	;
LL_ADC_ClearFlag_CCRDY(ADC1);

Teraz możemy skonfigurować kontroler NVIC.

NVIC_SetPriority(ADC1_COMP_IRQn, 1);
NVIC_EnableIRQ(ADC1_COMP_IRQn);

Pora na uruchomienie ADC. Najpierw włączamy wewnętrzny stabilizator i czekamy wymagane minimum 20 us (ja wykorzystałem delay o czasie 1 ms).

LL_ADC_EnableInternalRegulator(ADC1);
LL_mDelay(1);

Teraz możemy włączyć przetwornik i poczekać, aż się uruchomi.

LL_ADC_ClearFlag_ADRDY(ADC1);
LL_ADC_Enable(ADC1);
while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
	;

Pozostało tylko uruchomić przerwanie od końca konwersji.

LL_ADC_EnableIT_EOC(ADC1);

Konwersję będziemy wykonywali co 100 ms. Do tego celu wykorzystamy timer programowy oparty na SysTick. Konstrukcję znamy już z poprzednich rozdziałów.

W pętli co 100 ms startujemy konwersję.

LL_ADC_REG_StartConversion(ADC1);

Dane odczytujemy w przerwaniu od EOC.

void ADC_COMP_IRQHandler(void)
{
	if(LL_ADC_IsActiveFlag_EOC(ADC1) != 0)
	{
		adc_data = LL_ADC_REG_ReadConversionData12(ADC1);
                voltage_mv = CONVERT_ADC_TO_MV(adc_data);
		LL_ADC_ClearFlag_EOC(ADC1);
	}
}

Teraz możemy uruchomić debugger Run -> Debug (F11) i wpisać w pole Live expression zmienne adc_data i voltage_mv. Jeżeli zmienimy położenie potencjometru, wartość odczytana z przetwornika również będzie się zmieniała.

[PROGRAM] Konwersja w trybie przerwań (Multi Channel)

Czas na przykład z przerwaniami przy pomiarze kilku kanałów. Do wejść analogowych ADC_IN0 oraz ADC_IN1 (odpowiednio na pinach PA0 i PA1) podłączamy jeszcze dwa potencjometry. Dodajemy też definicje pinów, aby łatwiej było operować na nazwach.

#define ADC_Pot3_Pin LL_GPIO_PIN_4
#define ADC_Pot3_GPIO_Port GPIOA
#define ADC_Pot2_Pin LL_GPIO_PIN_1
#define ADC_Pot2_GPIO_Port GPIOA
#define ADC_Pot1_Pin LL_GPIO_PIN_0
#define ADC_Pot1_GPIO_Port GPIOA

Po ustawieniu zegarów konfigurujemy PA0, PA1 i PA4 (dostępne na Nucleo na złączu Arduino pod nazwami A0, A1 i A2) jako wejścia analogowe.

LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
 
LL_GPIO_SetPinPull(ADC_Pot1_GPIO_Port, ADC_Pot1_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(ADC_Pot1_GPIO_Port, ADC_Pot1_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(ADC_Pot1_GPIO_Port, ADC_Pot1_Pin, LL_GPIO_MODE_ANALOG);
 
LL_GPIO_SetPinPull(ADC_Pot2_GPIO_Port, ADC_Pot2_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(ADC_Pot2_GPIO_Port, ADC_Pot2_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(ADC_Pot2_GPIO_Port, ADC_Pot2_Pin, LL_GPIO_MODE_ANALOG);
 
LL_GPIO_SetPinPull(ADC_Pot3_GPIO_Port, ADC_Pot3_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(ADC_Pot3_GPIO_Port, ADC_Pot3_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(ADC_Pot3_GPIO_Port, ADC_Pot3_Pin, LL_GPIO_MODE_ANALOG);

Teraz przystępujemy do konfiguracji przetwornika ADC. Na początku włączamy taktowanie ADC.

LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);

Wybieramy rozdzielczość oraz sposób wyrównania danych.

LL_ADC_SetResolution(ADC1, LL_ADC_RESOLUTION_12B);
LL_ADC_SetDataAlignment(ADC1, LL_ADC_DATA_ALIGN_RIGHT);

Następnie ustawiamy zegar dla ADC. Wybierzemy taktowanie z sygnału APB z dzielnikiem 4. ADC możemy być taktowany maksymalnie z częstotliwością 35 MHz, dlatego ustawimy mniejszą wartość, czyli 16 MHz.

LL_ADC_SetClock(ADC1, LL_ADC_CLOCK_SYNC_PCLK_DIV4);

Teraz możemy ustawić tryb konwersji. Do pomiarów wykorzystamy tryb pojedynczego pomiaru.

LL_ADC_REG_SetContinuousMode(ADC1, LL_ADC_REG_CONV_SINGLE);

Sposób konfiguracji kanałów ustawiamy jako stały. Czekamy na wykonanie konfiguracji i czyścimy flagę.

if(LL_ADC_REG_GetSequencerConfigurable(ADC1) != LL_ADC_REG_SEQ_FIXED)
{
	LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_FIXED);
	while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
		;
	LL_ADC_ClearFlag_CCRDY(ADC1);
}

Czas próbkowania ustawiamy najpierw w pierwszym rejestrze SMP1, a następnie przypisujemy go do wybranych kanałów.

LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_39CYCLES_5);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_0, LL_ADC_SAMPLINGTIME_COMMON_1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SAMPLINGTIME_COMMON_1);
LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_4, LL_ADC_SAMPLINGTIME_COMMON_1);

Aby korzystać z kanału 0, 1 i 4, włączamy je w rejestrze sekwencera CHSELR. Czekamy, aż konfiguracja kanałów się wykona i czyścimy flagę.

LL_ADC_REG_SetSequencerChannels(ADC1, LL_ADC_CHANNEL_0 | LL_ADC_CHANNEL_1 | LL_ADC_CHANNEL_4);
while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
	;
LL_ADC_ClearFlag_CCRDY(ADC1);

Konfigurujemy kontroler przerwań NVIC.

NVIC_SetPriority(ADC1_COMP_IRQn, 1);
NVIC_EnableIRQ(ADC1_COMP_IRQn);

Pora na uruchomienie ADC. Najpierw włączamy wewnętrzny stabilizator i czekamy wymagane minimum 20 us (ja wykorzystałem delay o czasie 1 ms).

LL_ADC_EnableInternalRegulator(ADC1);
LL_mDelay(1);

Teraz możemy włączyć przetwornik i poczekać, aż się uruchomi.

LL_ADC_ClearFlag_ADRDY(ADC1);
LL_ADC_Enable(ADC1);
while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
	;

Tym razem wykorzystamy oba źródła przerwań – końca konwersji i końca sekwencji.

LL_ADC_EnableIT_EOC(ADC1);
LL_ADC_EnableIT_EOS(ADC1);

Konwersję będziemy wykonywali co 100 ms. Do tego celu wykorzystamy timer programowy oparty na SysTick. Konstrukcję znamy już z poprzednich rozdziałów.

W pętli co 100 ms startujemy konwersję.

LL_ADC_REG_StartConversion(ADC1);

W obsłudze przerwania sprawdzamy obie flagi: EOC i EOS.W przypadku wystąpienia przerwania od końca konwersji, odczytujemy dane i zapisujemy do tablicy. Przerwanie końca sekwencji posłuży nam do wyzerowania licznika konwersji.

void ADC_COMP_IRQHandler(void)
{
	if(LL_ADC_IsActiveFlag_EOC(ADC1) != 0)
	{
		adc_data[conv_cnt] = LL_ADC_REG_ReadConversionData12(ADC1);
		voltage_mv[conv_cnt] = CONVERT_ADC_TO_MV(adc_data[conv_cnt]);
                conv_cnt++;
		LL_ADC_ClearFlag_EOC(ADC1);
	}
 
	if(LL_ADC_IsActiveFlag_EOS(ADC1) != 0)
	{
		conv_cnt = 0;
		LL_ADC_ClearFlag_EOS(ADC1);
	}
}

Teraz możemy uruchomić debugger Run -> Debug (F11) i wpisać w pole Live expression zmienne. Jeżeli zmienimy położenie potencjometrów, odpowiadająca mu wartość wartość odczytana z przetwornika również będzie się zmieniała (adc_data[0] będzie odpowiadała wejściu A0, adc_data[1] wejściu A1, a adc_data[2] wejściu A4).

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 *