STM32G0 i ADC

Tu możesz pisać o swoich problemach z pisaniem programów w języku C/C++ dla STM.
Awatar użytkownika
Marcin
User
User
Posty: 309
Rejestracja: środa 09 wrz 2015, 19:30
Lokalizacja: Królewskie miasto Sandomierz

STM32G0 i ADC

Postautor: Marcin » sobota 25 sty 2020, 22:24

Hej wszystkim.

Od pewnego czasu siedzę nad ADC w STM32G0, chciałbym tu odrobinkę przybliżyć jego funkcjonalność i zachęcić początkujących do jego poznania. Swój opis oprę na przetworniku zawartym w układzie STM32G071. Układ ten to jeden z nowszych produktów STMicroelectornics bazujący na rdzeniu Cortex-M0+, pracuje z częstotliwością do 64MHz. Pomimo ciekawych funkcjonalności jakie w nim zaimplementowano jest całkiem tani. Znajdziemy w nim jeden przetwornik ADC z rozdzielczością 12 bitów. W porównaniu z przetwornikami znanymi z wielu z atmeg ten ma znacznie bardziej rozbudowaną funkcjonalność, dla znawców AVRów na pierwszy rzut oka to prawie jak drugi mały i niezależny mikrokontroler w mikrokontrolerze.

Na początek trochę teoretycznego opisu.

Zegar
Na początek przyjrzyjmy się sygnałowi zegarowemu. Datasheet podaje, że ADC w STM32G071 może być taktowany zegarem o częstotliwości 0,14-35MHz. Poniżej schemat układu zegarowego ADC:
RCC.JPG

Do wyboru mamy dwa źródła zegarowe, synchroniczny pochodzący z szyny APB (PCLK) lub asynchroniczny dedykowany dla ADC mogący pochodzić z trzech różnych źródeł - wewnętrzny oscylator (HSE), PLLP lub SYSCLK. Multiplexer wybierający źródło sygnału taktowania ADC i podział sygnału pochodzącego z PCLK a także dzielnik sygnału asynchronicznego jest częścią ADC, multiplexer który wybiera źródło sygnału synchronicznego należy do bloku RCC.
Napięcie odniesienia
Kolejną rzeczą o której warto wspomnieć to źródło napięcia odniesienia (Vref) niezbędne do funkcjonowania przetwornika. Do ADC jest dostarczane ono poprzez pin mikrokontrolera Vref. Na pin ten możemy podać napięcie z naszego zewnętrznego źródła napięcia odniesienia jak i również podłączyć wewnętrzne źródło napięcia odniesienia dostarczane z bloku Power control. Blok ten oferuje nam dwa poziomy napięć, 2.048V oraz 2.5V. Są pewne minimalne wymagania niezbędne do uzyskania z bloku Power Control żądanego napięcia odniesienia. Aby wewnętrznie uzyskać napięcie odniesienia 2.048V mikrokontroler mus być zasilany napięciem minimum 2.4V, dla uzyskania napięcia odniesienia 2.5V musimy zasilić mikrokontroler napięciem minimum 2.8V.

Wybór napięcia odniesienia ma wpływ na pomiar i wartości mierzone przez ADC. Wartość tego napięcia to jednocześnie największa mierzalna przez przetwornik wartość. Im większa wartość Vref tym większa możliwa wartość do zmierzenia przez ADC, im mniejsza wartość Vref tym dokładniejszy pomiar (mniejsza wartość napięcia mierzonego przypadająca na jeden bit wyniku) O tym wspomnę przy przeliczaniu zmierzonej wartości na realną wartość napięcia.

W temacie wyboru napięć odniesienia bardzo ważna uwaga. W obudowach które nie posiadają wyprowadzonego na zewnątrz pinu Vref (np. LQFP32) pin ten wewnętrznie jest na stałe podłączony do pinu zasilania Vdd. W takim przypadku napięcie odniesienia które jest dostarczane do ADC jest równie zapięciu zasilającego mikrokontroler (Vdd) W tym przypadku nie ma możliwości podłączenia własnego źródła napięcia odniesienia czy wyboru któregoś z wewnętrznych dostarczanych przez blok Power control.

Wejścia przetwornika ADC
W STM32G071 jest tylko jeden przetwornik ADC który posiada do 19 multipleksowanych kanałów. 16 kanałów to analogowe wejścia przeznaczone do pomiaru sygnałów zewnętrznych oznaczone jako ADC_INxx, gdzie xx oznacza numer kanału. Kanały te są połączone z dedykowanymi pinami IO mikrokontrolera. W grupie tej nie ma kanałów od 12 do 14, te 3 kanały są zarezerwowane dla sygnałów: wewnętrzny czujnik temperatury, wewnętrzne napięcie odniesienia oraz zewnętrzny pin Vbat do monitorowania napięcia baterii. Najlepiej pokazuje to reference manual:
Inputs.JPG

Mniejsze obudowy mogą nie posiadać wyprowadzonych wszystkich zewnętrznych kanałów analogowych, dla przykładu mikrokontroler STM32G071KBT6 który jest w obudowie LQFP32 posiada analogowe wejściowe kanały od numeru 0 do 10, nie posiada analogowych kanałów o numerach 11, 15, 16, 17 oraz 18.

Bardzo istotna rzecz przy wyborze kanału który będzie użyty do pomiaru, pin odpowiadający używanemu kanałowi powinien być skonfigurowany jako wejście / wyjście analogowe.

Zmianę konfiguracji kanałów nie możemy dokonać podczas konwersji (pomiaru) przez przetowrnik, każda zmiana konfiguracji wybranych kanałów wymaga zatrzymania aktualnej konwersji. Gotowość nowej konfiguracji sygnalizowana jest ustawieniem flagi Channel Configuration Ready flag (CCRDY) znajdującej się w rejestrze ADC interrupt and status register (ADC_ISR)

Źródła wyzwalania konwersji i tryby pracy przetwornika ADC
Przed rozpoczęciem opisu trybów pracy warto wspomnieć o źródłach wyzwalania pomiarów. Konwersja może być zainicjowana jawnie programowo przez programistę poprzez ustawienie bitu ADSTART. Konwersja może być również wyzwolona czysto sprzętowo jednym z siedmiu trigerów. Trigerami takimi są liczniki TIM1, 2, 3, 6, 15. Niektóre zdarzenia generowane przez te liczniki mogą wyzwalać konwersję ADC. Trigerem wyzwalającym konwersję jest również EXTI line 11. Rozpoczętą konwersję można programowo zatrzymać, i tu uwaga, nie kasując bit ADSTART, do zatrzymania wymagane jest ustawienie bitu ADSTP. Zatrzymanie konwersji sygnalizowane jest wyzerowaniem bitu ADSTART.

Przejdźmy do trybów pracy. Przetwornik ADC w naszym układzie oferuje ich kilka. Tryby pomiaru możemy podzielić na:

Single mode – pomiar pojedynczego kanału, w tym trybie po wyzwoleniu przetwornik mierzy wartości napięcia tylko na jednym wybranym kanale.

Scan mode – sekwencja pomiarów z wielu kanałów uprzednio zdefiniowanych.

Tryb Scan mode możemy ze względu na organizację kolejności pomiarów podzielić na dwa dodatkowe tryby:

Sequencer not fully configurable – gdzie nie określamy jawnie sekwencji skanowania kanałów, kolejność skanowania kanałów określona jest przez numerację skanowanych kanałów, w trybie tym możemy określić kierunek skanowania kanałów, od najniższego do najwyższego lub odwrotnie.

Sequencer fully configurable – gdzie jawnie określamy kolejność skanowanych kanałów. Możemy w tej grupie zdefiniować własną kolejność niezależną od numeracji kanałów. Grupa ta może zawierać maksymalnie 8 mierzonych kanałów. Do grupy tej możemy dodać tylko kanały o numerach od 0 do 14.

Pod względem ciągłości pomiarów wyróżniamy dwa tryby:

Single-shot – w którym po uruchomieniu przetwornik dokonuje jednokrotnego pomiaru pojedynczego kanału lub sekwencji wybranych kanałów.

Continus mode – w którym przetwornik dokonuje ciągłej konwersji z wybranego kanału lub grupy kanałów.

Discontinuous mode – tryb przypominający tryb krokowy. Przetwornik po wyzwoleniu w trybie scan mode wykonuje pomiar tylko jednego kanału i zatrzymuje się. Każde następne wyzwolenie programowe czy sprzętowe rozpoczyna pomiar następnego kolejnego zdefiniowanego w trybie scan mode kanału.

Wynik każdego pomiaru zapisywany jest do rejestru ADC data register (ADC_DR), po zakończeniu konwersji wybranego kanału i zapisaniu jego wyniku do ADC_DR ustawiana jest flaga End of conversion flag (EOC) Flagę tą można programowo skasować poprzez wpisanie do niej 1. Kasowanie flagi dokonywane jest również w momencie odczytu wartości zapisanej w ADC_DR. Zakończenie całej sekwencji konwersji grupy kanałów zdefiniowanej w trybie scan mode jest sygnalizowane ustawieniem flagi End of sequence flag (EOS) Skasowanie jej następuje również jak w przypadku poprzednim poprzez zapisanie do niej jedynki. Flagi EOS i EOC znajdziemy w rejestrze ADC interrupt and status register (ADC_ISR)

W czasie pracy ADC może dojść do sytuacji, w której program nie zdążył jeszcze odczytać danych z ADC a przetwornik już chce wrzucić do ADC_DR nowe dane nadpisując nimi nieodczytane poprzednie wartości. Taka sytuacja będzie sygnalizowana flagą overrun flag (OVR) Możliwe jest zablokowanie nadpisania dotychczasowej wartości w ADC_DR jeśli nie została jeszcze odczytana, w takim przypadku nowa wartość zostanie utracona bez zapisu. Można również zezwolić na nadpisanie ADC_DR pomimo braku odczytu dotychczasowej wartości, w takim przypadku dotychczasowa nieodczytana wartość jest tracona i zastąpiona nową z aktualnej konwersji.

Powyższe dwie sytuacje mogą prowadzić do utraty jeszcze nieodczytanych danych czy też porzucania nowych bez zapisu. W tej sytuacji pomocny może okazać się tryb Wait mode conversion, który zapobiega rozpoczęciu nowej konwersji do czasu wyzerowania flagi End of conversion flag (EOC) a więc i odczytania dotychczasowych danych z rejestru ADC_DR (jak wyżej wspomniałem flaga ta jest automatycznie zerowana podczas odczytu z ADC_DR)

Zdarzenia opisane wyżej mogą również generować przerwania, aby było to możliwe należy włączyć poszczególne źródła przerwań w ADC interrupt enable register (ADC_IER)

Zdarzenia mogą również generować żądania do DMA, myślę że na temat DMA warto poświęcić oddzielny wątek.

Spójrzmy teraz na sam wynik konwersji. Przetwornik ma rozdzielczość do 12 bitów, w przypadku kiedy tak duża rozdzielczość nie jest wymagana możemy zmniejszyć jego rozdzielczość. Do wyboru mamy rozdzielczość 12, 10, 8 i 6 bitów. Im mniejsza rozdzielczość tym szybszy czas konwersji. Wynik w 16-bitowym rejestrze ADC_DR może być wyrównany do prawej lub lewej strony.

Odczyt i przeliczanie zmierzonej wartości
Aby odczytać wartość napięcia mierzonego przez ADC należy posłużyć się wzorem:
calc.JPG

n - rozdzielczość

Przypuśćmy, że wynik zmierzony przez ADC wynosi 1, czyli najmniejsza możliwa do zaprezentowania przez przetwornik wartość, rozdzielczość 12 bitów a napięcie referencyjne 2.5V, podstawiając do wzoru Vin = 1*2,5/4096 daje nam w przybliżeniu 0,00061035V, a więc ok 610uV, jest to w tej konfiguracji najmniejsza możliwa wartość jaką przetwornik może na znmierzyć. Zmniejszając wartość napięcia referencyjnego zmniejszy się wartość napięcia przypadającego na 1 bit wyniku. Jeżeli napięcie wejściowe jest równe napięciu referencyjnemu otrzymamy wynik 0xFFF czyli maksimum jakie możemy zapisać w 12-bitowej liczbie. Jeżeli podamy na wejście napięcie większe niż napięcie referencyjne, wynik nie przekroczy nam maksymalnej rozdzielczości, będzie zawsze równy 0xFFF.
Uruchomienie przetwornika ADC
Na zakończenie teoretycznego opisu chciałbym wspomnieć o przygotowaniu przetwornika do pracy. Do jego poprawnej pracy musimy włączyć wewnętrzny regulator napięcia, dokonywane jest to poprzez ustawienie bitu ADVREGEN w rejestrze ADC control register (ADC_CR) Po jego włączeniu musimy odczekać krótką chwilę, do czasu aż wewnętrzny regulator napięcia zacznie stabilnie pracować. Czas ten jest podany w dokumentacji mikrokontrolera (tADCVREG_SETUP)

Przetwornik przed pierwszym użyciem warto skalibrować, pozwoli nam to skorygować błąd (Offset) przesunięcia, który może być różny w każdym egzemplarzu i wynika on z procesu technologicznego. Dla przykładu mój mikrokontroler na starcie podawał wartość około 0x20. Po kalibracji każdy wynik pomiaru jest korygowany o tą wartość. Kalibrację możemy dokonać tylko kiedy wewnętrzny regulator napięcia jest włączony i ustabilizowany (ADVREGEN=1) przetwornik jest wyłączony (ADEN=0) oraz DMA (DMAEN=0) w ADC wyłączone. Kalibrację uruchamiamy ustawiając bit ADCAL w DC control register (ADC_CR), dopóki kalibracja nie będzie ukończona bit ADCAL pozostaje ustawiony. Ukończenie kalibracji zgłaszane jest wyzerowaniem bitu ADCAL (oraz ustawieniem flagi EOCAL) a do rejestru ADC_DR zapisywany jest wynik offsetu. Wynik kalibracji dostępny jest również w rejestrze ADC_CALFACT.

Po włączeniu i ustabilizowaniu wewnętrznego regulatora napięcia i kalibracji możemy włączyć nasz przetwornik ustawiając bit ADEN, przetwornik swoją gotowość zgłosi ustawiając flagę ADRDY w rejestrze ADC_ISR.

Wyłączenia przetwornika nie dokonujemy poprzez kasowanie bitu AREN, procedura wyłączenia przetwornika wymaga ustawienia bitu ADDIS znajdującego się w ADC_CR. Zakończenie procedury wyłączenia przetwornika ADC sygnalizowane jest sprzętowym wyzerowaniem bitów ADEN oraz ADDIS.

Praktyczny przykład użycia przetwornika ADC
Po części teoretycznej przyszedł czas na praktyczny projekt. A więc tworzymy prosty przykład wyzwalania i konwersji danych za pomocą ADC. W następnej części wrzucę przykładowy program w którym użyjemy naszego przetwornika. Nie opisałem jeszcze w części teoretycznej zagadnienia analogowego watchdoga oraz nadpróbkowania, myślę że i to warto będzie na końcu dodać jako dodatkowe możliwości ADC.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Zealota
Posty: 10
Rejestracja: środa 08 maja 2019, 08:49

Re: STM32G0 i ADC

Postautor: Zealota » poniedziałek 27 sty 2020, 08:46

Dzięki za poradnik, dla mnie jak znalazł :)
"Zdarzenia mogą również generować żądania do DMA, myślę że na temat DMA warto poświęcić oddzielny wątek."
Popieram i wnioskuję o przybliżenie tego aspektu.
Po pobieżnej lekturze RM widzę, że samo DMA jednak trochę, różni się od starszych rodzin jak F1 czy F4, zatem z chęcią przeczytam dalszą część artykułu.

Awatar użytkownika
Marcin
User
User
Posty: 309
Rejestracja: środa 09 wrz 2015, 19:30
Lokalizacja: Królewskie miasto Sandomierz

Re: STM32G0 i ADC

Postautor: Marcin » wtorek 14 kwie 2020, 18:39

Praktyczny przykład użycia przetwornika ADC

Po części teoretycznej przyszedł czas na praktyczny projekt. Jako platformy sprzętowej użyłem płytki GreenPill G070 według projektu @ ZbeeGin. Mikrokontroler to STM32G071KBT6 (pinowo zgodny z STM32G070) Jako źródło napięcia mierzonego użyłem potencjometru w roli dzielnika napięcia. Wejście pomiarowe na pinie PB2. Do wyświetlania rezultatu użyłem wyświetlacza LCD TFT opartego na sterowniku ILI9341. W przykładach pojawia się obsługa GPIO, tu nie będę tematu poruszał, już został wyjaśniony przez kolegę @ ZbeeGin tutaj a także w tej publikacji

ADC.jpg

Przykłady są oparte na mikrokontrolerze STM32G071KBT6, który nie posiada wyprowadzonego pinu napięcia odniesienia (Vref). Pin ten wewnętrznie jest połączony z napięciem zasilania, dlatego w naszych przykładach Vref zawsze będzie równe napięciu zasilającemu uC (w mojej konfiguracji 3,3V)

Przygotowałem trzy scenariusze, w każdym pewne elementy jak konfiguracja portów, zegara czy kanałów ADC będzie stała. Rożnica będzie polegała natomiast na sposobie wyzwalania pomiaru czy pobierania informacji o zakończeniu konwersji. W każdej sytuacji elementem odmierzającym czas rozpoczęcia pomiaru będzie timer 6.


Wersja I
Chyba najprostszy przykład użycia, ale i najmniej optymalny. W tej najprostszej sytuacji konwersję rozpoczynamy programowo i czekamy na zakończenie pomiaru. Po zakończeniu pomiaru możemy przetworzyć / wyświetlić wynik. Kod

Kod: Zaznacz cały

   // ------ GPIO PIN CONFIG ----------
   
   RCC->IOPENR |=RCC_IOPENR_GPIOBEN;         //ENABLE clock for GPIOB
   LL_GPIO_SetPinMode(GPIOB, 2, LL_GPIO_MODE_ANALOG);
   
   
   // ------ ACD CONFIG ----------
   
   RCC->APBENR2 |= RCC_APBENR2_ADCEN;      //ENABLE clock for ADC
   
   ADC1->CFGR2 |= (2UL << ADC_CFGR2_CKMODE_Pos);               //ADC clok PCLK/4 (Synchronous clock mode)
   
   ADC1->CHSELR |= ADC_CHSELR_CHSEL10;      //CHANNEL_10 on pin 10, port B
   while(!(ADC1->ISR & ADC_ISR_CCRDY));   //Wait on channel configuration


   // ------ ENABLE ADC -------
   
   ADC1->CR |= ADC_CR_ADVREGEN;               //enable power
   for(uint16_t t=0; t<60000; t++);          //wait on power
   
   ADC1->CR |= ADC_CR_ADCAL;                     //run calibration
   while(ADC1->CR & ADC_CR_ADCAL);          //wait on calibration   
   
   ADC1->CR |= ADC_CR_ADEN;                     //Enable ADC
   while(!(ADC1->ISR & ADC_ISR_ADRDY));   //Wait until ADC is ready
   
   
   // ------- TIM6 ----------
   
   RCC->APBENR1 |= RCC_APBENR1_TIM6EN;      //ENABLE cloc for TIM6
   TIM6->PSC = 6399;
   TIM6->ARR = 4999;
   TIM6->DIER |= TIM_DIER_UIE;                  //ENABLE interupts
   
   
   // ------- TIM6 INTERUPTS ----------
   
   NVIC_SetPriority(TIM6_DAC_LPTIM1_IRQn, 0);
   NVIC_EnableIRQ(TIM6_DAC_LPTIM1_IRQn);   
      
      
   // ------ ENABLE TIM6 -------
   
   TIM6->CR1 |= TIM_CR1_CEN;
   
   


Przerwania:

Kod: Zaznacz cały

void TIM6_DAC_LPTIM1_IRQHandler (void)

{
    if(TIM6->SR & TIM_SR_UIF)
    {
        ADC1->CR |= ADC_CR_ADSTART;                    //start conversion     
        while(!(ADC1->ISR & ADC_ISR_EOC));    //wait on conversion
        PrintData(ADC1->DR);                                //prind result
        TIM6->SR &= ~TIM_SR_UIF;                        //clear flag
    }

}


Na początku konfiguracja GPIO, konfiguracja i załączenie źródła zegarowego oraz konfiguracja kanałów pomiarowych. Po tym włączenie ADC, tu zwrócę uwagę, po włączeniu wewnętrznego źródła zasilania (ADC1->CR |= ADC_CR_ADVREGEN) umieściłem pętlę wstrzymującą program na krótki okres czasu, o tym wspominałem w poprzednim poście, dokładniej w sekcji "Uruchomienie przetwornika ADC". Jest to czas niezbędny do ustabilizowania wewnętrznego źródła zasilania. Po włączeniu zasilania kolejnym krokiem jest kalibracja oraz ostatecznie włączenie ADC.

Po konfiguracji i włączeniu przetwornika przechodzony do timera 6 oraz przerwań od niego pochodzących. Nie będę się tu skupiał na omawianiu licznika, to jest temat na odrębny wątek. Timer "tykając" generuje przerwania. W obsłudze przerwania od tego timera programowo rozpoczynamy każdy pomiar ADC ustawiając bit ADC_CR_ADSTART, po czym czekamy jego zakończenie i udostępnienie wyniku pomiaru. Wynik pomiaru jest zapisywany do rejestru DR, a sam fakt zakończenia pomiaru i gotowości nowych danych do odczytu będzie zgłoszony ustawieniem flagi “End Of Conversion” (ADC_ISR_EOC w ISR) Po zakończeniu konwersji możemy odczytać nowy wynik pomiaru z rejestru DR i przetworzyć / wyświetlić (w moim przykładzie robię to funkcją PrintData, która wyświetla wynik na LCD w formie tekstowej oraz jako wskaźnik procentowy) Przed opuszczeniem obsługi przerwania kasuję flagę przerwania pochodzącą od licznika TIM6, którego obsługę kończymy. Wspomnę, że nie ma konieczności ręcznego kasowania flagi ADC_ISR_EOC w ADC, flaga ta jest automatycznie kasowana podczas odczytu danych z ADC.


Wersja II
W poprzedniej wersji programowo wyzwalaliśmy pomiar i czekaliśmy z wyświetleniem wyniku do momentu zakończenia pomiaru i konwersji. W tym przykładzie dokonamy małej modyfikacji, samo wyzwolenie również programowe w przerwaniu od licznika TIM6, natomiast nie czekajmy na wynik pomiaru, pozwólmy samemu ADC poinformować nas o zakończeniu konwersji i gotowości do odczytu nowych nowych danych. Do tego posłuży nam przerwanie od ADC. Na początek kod:

Kod: Zaznacz cały

   // ------ GPIO PIN CONFIG ----------
   
   RCC->IOPENR |=RCC_IOPENR_GPIOBEN;         //ENABLE clock for GPIOB
   LL_GPIO_SetPinMode(GPIOB, 2, LL_GPIO_MODE_ANALOG);
   
   
   // ------ ACD CONFIG ----------
   
   RCC->APBENR2 |= RCC_APBENR2_ADCEN;      //ENABLE clock for ADC
   
   ADC1->CFGR2 |= (2UL << ADC_CFGR2_CKMODE_Pos);               //ADC clok PCLK/4 (Synchronous clock mode)
   
   ADC1->IER |= ADC_IER_EOCIE;                  //ENABLE interpts for end of conversion
   
   ADC1->CHSELR |= ADC_CHSELR_CHSEL10;      //CHANNEL_10 on pin 10, port B
   while(!(ADC1->ISR & ADC_ISR_CCRDY));   //Wait on channel configuration
   
   NVIC_SetPriority(ADC1_COMP_IRQn, 0);
   NVIC_EnableIRQ(ADC1_COMP_IRQn);            


   // ------ ENABLE ADC -------
   
   ADC1->CR |= ADC_CR_ADVREGEN;               //enable power
   for(uint16_t t=0; t<60000; t++);          //wait on power
   
   ADC1->CR |= ADC_CR_ADCAL;                     //run calibration
   while(ADC1->CR & ADC_CR_ADCAL);          //wait on calibration   
   
   ADC1->CR |= ADC_CR_ADEN;                     //Enable ADC
   while(!(ADC1->ISR & ADC_ISR_ADRDY));   //Wait until ADC is ready
   
   
   // ------- TIM6 ----------
   
   RCC->APBENR1 |= RCC_APBENR1_TIM6EN;      //ENABLE cloc for TIM6
   TIM6->PSC = 6399;
   TIM6->ARR = 4999;
   TIM6->DIER |= TIM_DIER_UIE;                  //ENABLE interupts
   
   
   // ------- TIM6 INTERUPTS ----------
   
   NVIC_SetPriority(TIM6_DAC_LPTIM1_IRQn, 0);
   NVIC_EnableIRQ(TIM6_DAC_LPTIM1_IRQn);   
      
      
   // ------ ENABLE TIM6 -------
   
   TIM6->CR1 |= TIM_CR1_CEN;         
   


Przerwania


Kod: Zaznacz cały

void ADC1_COMP_IRQHandler(void)
{
    if(ADC1->ISR & ADC_ISR_EOC)
    {
        PrintData(ADC1->DR);
    }
}
 

void TIM6_DAC_LPTIM1_IRQHandler (void)
{
    if(TIM6->SR & TIM_SR_UIF)
    {
        ADC1->CR |= ADC_CR_ADSTART;                    //start conversion
        TIM6->SR &= ~TIM_SR_UIF;                        //clear flag
    }
}


Porównując poprzedni przykład konfiguracja GPIO bez zmian, w konfiguracji przetwornika ADC jedna mała zmiana, dodatkowo w ADC włączone zostają przerwania od zdarzenia EOC (End Of Conversion) dzięki którym po zakończeniu pomiaru i konwersji ADC zgłosi nam gotowość danych do odczytu. Uruchomienie przetwornika pozostaje bez zmian. Konfiguracja timera 6 też również bez zmian. Trochę zmian w obsłudze przerwań, pewne elementy z funkcji obsługi przerwania timera 6 usunięte, za to dodana nowa funkcja obsługi przerwania od ADC.

Przeanalizujmy działanie. Po konfiguracji GPIO, konfiguracji i włączeniu ADC konfigurujemy i włączamy timer 6, który jak w poprzednim przykładzie generuje przerwania. W funkcji obsługi przerwania timera włączamy pomiar ADC, nie oczekujemy jak w poprzednim przypadku na wynik konwersji marnując jednocześnie czas procesora, kasujemy flagę przerwania timera i opuszczamy przerwanie zwalniając CPU. ADC w memencie zakończenia konwersji zgłasza zdarzenie, które generuje kolejne przerwanie. Po wejściu do jego obsługi (funkcja ADC1_COMP_IRQHandler) odczytujemy dane z DR oraz wyświetlamy wynik na LCD. Po wyświetleniu wyniku możemy opuścić przerwanie ponownie zwalniając procesor. I jak poprzedniej wersji, nie musimy ręcznie kasować flagi ADC_ISR_EOC, flaga jest automatycznie kasowana w momencie odczytu danych z ADC (z rejestru DR).

Zaleta tego przykładu w stosunku do poprzedniego, nie blokujemy CPU czekając na wynik konwersji, w tej konfiguracji po uruchomieniu pomiaru zwalniamy CPU, ADC gdy zakończy pomiar i konwersję sam się do nas zgłosi z wynikiem.


Wersja III
W trzeciej wersji dokonujemy dalszych usprawnień, rezygnujemy z programowego wyzwolenia pomiaru na rzecz czysto sprzętowego źródła wyzwalania pomiaru, którym dalej będzie timer 6. A więc na początek kod

Kod: Zaznacz cały

   // ------ GPIO PIN CONFIG ----------
   
   RCC->IOPENR |=RCC_IOPENR_GPIOBEN;         //ENABLE clock for GPIOB
   LL_GPIO_SetPinMode(GPIOB, 2, LL_GPIO_MODE_ANALOG);
   
   
   // ------ ACD CONFIG ----------
   
   RCC->APBENR2 |= RCC_APBENR2_ADCEN;      //ENABLE clock for ADC
   
   ADC1->CFGR2 |= (2UL << ADC_CFGR2_CKMODE_Pos);               //ADC clok PCLK/4 (Synchronous clock mode)
   
   ADC1->CFGR1 |= (0x1 << ADC_CFGR1_EXTEN_Pos)                  //Hardware trigger detection on the rising edge
                     | (0x5 <<  ADC_CFGR1_EXTSEL_Pos);               //TIM6_TRGO
   
   ADC1->IER |= ADC_IER_EOCIE;                  //ENABLE interpts for end of conversion
   
   ADC1->CHSELR |= ADC_CHSELR_CHSEL10;      //CHANNEL_10 on pin 10, port B
   while(!(ADC1->ISR & ADC_ISR_CCRDY));   //Wait on channel configuration
   
   NVIC_SetPriority(ADC1_COMP_IRQn, 0);
   NVIC_EnableIRQ(ADC1_COMP_IRQn);            


   // ------ ENABLE ADC -------
   
   ADC1->CR |= ADC_CR_ADVREGEN;               //enable power
   for(uint16_t t=0; t<60000; t++);          //wait on power
   
   ADC1->CR |= ADC_CR_ADCAL;                     //run calibration
   while(ADC1->CR & ADC_CR_ADCAL);          //wait on calibration   
   
   ADC1->CR |= ADC_CR_ADEN| ADC_CR_ADSTART;            //Enable ADC
   while(!(ADC1->ISR & ADC_ISR_ADRDY));   //Wait until ADC is ready

   
   // ------- TIM6 ----------
   
   RCC->APBENR1 |= RCC_APBENR1_TIM6EN;      //ENABLE cloc for TIM6
   TIM6->PSC = 6399;
   TIM6->ARR = 4999;
   TIM6->CR2 = 0x02 << 4;                        //The update event is selected as a trigger output (TRGO).
      
   // ------ ENABLE TIM6 -------
   
   TIM6->CR1 |= TIM_CR1_CEN;         


Przerwania

Kod: Zaznacz cały

void ADC1_COMP_IRQHandler(void)
{
    if(ADC1->ISR & ADC_ISR_EOC)
    {
        PrintData(ADC1->DR);
    }
}


Na wstępie słowo o interconnect matrix, jest to peryferium pozwalające na bezpośrednie przesyłanie zdarzeń pomiędzy różnymi peryferiami. Zdarzenie takie generowane przez jedno z peryferiów może być wyzwalaczem dla innego peryferium. Dla przykładu timer 6 za pomocą interconnect matrix może przesyłać zdarzenia do innych peryferów, w tym do ADC.

Wróćmy do przykładu. Konfiguracja GPIO bez zmian, W konfiguracji ADC dodatkowo włączamy sprzętowe wyzwolenie konwersji oraz określamy jego źródło (w naszym przypadku tim6) Włączając ADC musimy ustawić również bit ADSTART w rejestrze konfiguracyjnym ADC. Trochę zmian również w konfiguracji timera 6, nie włączamy przerwań od niego, nie będą tu potrzebne, natomiast ustawiamy generowanie zdążenia TRGO, które będzie generowane zamiast przerwania, zdarzenie to poprzez wewnętrzny blok interconnect matrix jest przesyłanie do ADC i stanowi źródło wyzwolenia pomiaru ADC. Po zakończeniu pomiaru ADC zgłasza gotowość odczytu danych zgłaszając zdarzenie, które następnie generuje przerwanie, i jak w poprzednim przykładzie dane odczytujemy, wyświetlamy i zwalniamy CPU.

Przewagą tego rozwiązania jest niewątpliwie całkowita rezygnacja z programowego wyzwalania pomiaru. W tym przykładzie po konfiguracji oraz uruchomieniu ADC i TIM6 regularny pomiar uruchamiany jest czysto sprzętowo w ogóle nie zajmując czasu procesora, wszystko dzieje się niejako “pod maską”. Po zakończeniu konwersji przetwornik ADC informuje nas zakończeniu pomiaru oraz konwersji i gotowości danych do odczytu.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Zealota
Posty: 10
Rejestracja: środa 08 maja 2019, 08:49

Re: STM32G0 i ADC

Postautor: Zealota » wtorek 14 kwie 2020, 21:12

Świetnie - kolejny wpis, dzięki. No i nadal czekam na to DMA :)

Awatar użytkownika
Marcin
User
User
Posty: 309
Rejestracja: środa 09 wrz 2015, 19:30
Lokalizacja: Królewskie miasto Sandomierz

Re: STM32G0 i ADC

Postautor: Marcin » piątek 17 kwie 2020, 23:37

Będzie i o DMA, tylko wcześniej zrealizuję to co w trakcie.


Wróć do „Programowanie STM w C/C++”

Kto jest online

Użytkownicy przeglądający to forum: Bing [Bot] i 0 gości