Salta al contenuto
Note per Studenti Microcontrollori STM32 - clock, timer, ADC e codice HAL

Microcontrollori STM32 - clock, timer, ADC e codice HAL

In questa pagina 8

Le esercitazioni di laboratorio e le domande di programmazione (parte 3 degli appelli) usano due microcontrollori STMicroelectronics con core ARM Cortex-M: STM32F334R8 (72 MHz, ADC SAR a 12 bit, timer HRTIM) e STM32G474RE (170 MHz, HRTIM a 5,44 GHz). Si programmano in C con l'ambiente integrato Keil µVision e la libreria HAL (Hardware Abstraction Layer), con il codice di configurazione generato dal programma STM32CubeMXprogramma che genera il codice di configurazione dei periferici. Questa nota raccoglie i calcoli che l'esame richiede (Timer - prescaler, compare, capture e watchdogUn timer è un contatore binario di periodi di clock: $f_{cont}=f_{clk}/PSC$ ($PSC$ = fattore del prescaler). In modalità compare/match genera un evento (interruzione, reset del conteggio, azione su un pin) quando il conteggio raggiunge il registro di match: per un evento ogni $T$ servono $N=T,f_{clk}/PSC\le2^{16}$ conteggi, e si prende il minimo $PSC$ (massima risoluzione). Con uscita in modo toggle la frequenza dell'onda quadra è metà di quella degli eventi, il duty-cycle 50%. In modalità capture il valore del contatore, salvato a due fronti, dà il periodo: $T=C_2-C_1$ (corretto anche con un overflow), risoluzione $1/f_{clk}$. Il watchdog reinizializza il sistema se non viene azzerato in tempo.Timer - prescaler, compare, capture e watchdog →, Modulazione PWM - duty-cycle, risoluzione e demodulazioneLa PWM trasferisce un segnale modulante $V^$ nella larghezza degli impulsi di un'onda quadra a frequenza fissa: con duty-cycle $d=t_1/T$ e livelli $\pm\frac E2$ la media è $V_{med}=(d-\frac12)E=E,\frac{V^}{V_p}$. L'informazione sta nel valor medio; il resto dello spettro (armoniche della portante) si elimina con un passa-basso (demodulazione). Il modulatore digitale è un timer: contatore + comparatore, $N=f_{clk}/f_{PWM}$ conteggi per periodo, risoluzione $n=\log_2N$ bit (massima frequenza per $n$ bit: $f_{clk}/2^n$), registro di compare = duty-cycle. Servono almeno 8 bit per evitare cicli limite.Modulazione PWM - duty-cycle, risoluzione e demodulazione →, Architetture di ADC e DACADC flash: $2^n-1$ comparatori in parallelo, il più veloce (GS/s) ma costoso, $n<10$. A gradinata/a rampa: contatore + DAC (o rampa) + comparatore, $T_{conv}\le2^n$ cicli di clock (lentissimo). SAR: ricerca binaria con DAC e comparatore, $T_{conv}=(n+1)$ cicli: il più usato nei µC. Sigma-delta: ADC a 1 bit + DAC a 1 bit in retroazione e filtro digitale, sovracampionamento (molti bit, banda ridotta). Multistadio/pipeline: più ADC a pochi bit in serie; la pipeline triplica (con 3 stadi) la frequenza di conversione. DAC: a resistenze pesate, a traliccio R-2R ($V_o=-V_{ref},C/2^n$, corrente costante), a voltage scaling (monotono), seriale.Architetture di ADC e DAC →).

L'approccio HAL

La configurazione di una periferica consiste nel chiamare funzioni di libreria che accedono ai (molti) registri di controllo. Si comincia sempre dichiarando strutturetipo di dato C che raggruppa più campi: qui contiene i parametri di una periferica che corrispondono alle varie funzionalità (per esempio TIM_OC_InitTypeDef, RCC_OscInitTypeDef, HRTIM_TimeBaseCfgTypeDef), si assegnano i valori ai campi usando per lo più costanti simboliche definite nei file .h e si invoca la funzione (HAL_..._Init, HAL_..._ConfigChannel, HAL_..._Start). Il programma principale dopo l'inizializzazione entra in un while(1){} e il lavoro vero (filtro, regolatore, PLLanello ad aggancio di fase usato per moltiplicare la frequenza del clock) sta nelle ISR (..._IRQHandler in stm32xxxx_it.c) (Quantizzazione, fattori di scala e cicli limiteUn algoritmo di filtro o regolatore pensato per un processore ideale va adattato alla realtà: temporizzazionedistribuzione nel tempo delle operazioni di un algoritmo (l'algoritmo nella ISR dell'ADC, durata minore di $T_c$), acquisizione (condizionamento del segnale per sfruttare tutto il fondo scala dell'ADC), fattori di scala (normalizzazione intera o 1.15; evitare overflow), quantizzazione di ingresso, uscita, coefficienti e prodotti (sposta i poli, genera errori a regime e cicli limite). Una frequenza di campionamento troppo alta rende più critica la quantizzazione dei coefficienti: va scelta coerente con la lunghezza di parola.Quantizzazione, fattori di scala e cicli limite →). Un livello di astrazione basso dà più opzioni.

Albero dei clock (STM32F334R8)

Dal quarzo esterno (HSEHigh Speed External: oscillatore al quarzo esterno) si ottiene il clock di sistema con un PLL. Impostazioni tipiche (tema d'esame):

c
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;                 // moltiplica per 9
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;          // HCLK = SYSCLK / 1
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;            // PCLK1 = HCLK / 2
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;            // PCLK2 = HCLK / 1
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;         // clock ADC = PCLK2 / 6

Con fHSE=8f_{HSE}=8 MHz: fSYSCLK=8⋅9=72f_{SYSCLK}=8\cdot9=72 MHz (è il massimo supportato dall'STM32F334R8, quindi la scelta ha senso); fHCLK=72f_{HCLK}=72 MHz; APB1: 36 MHz (massimo ammesso per questo bus); APB2: 72 MHz; l'ADC è collegato ad APB2, quindi riceve 72/6=1272/6=\mathbf{12} MHz. (Nell'STM32G474RE il clock di sistema a 170 MHz si ottiene, per esempio, dall'oscillatore interno HSIHigh Speed Internal: oscillatore interno del chip con PLL ×85 e divisione per 4; il clock interno di HRTIM, con un PLL dedicato ×32, arriva a 170⋅32=5,44170\cdot32=5{,}44 GHz.)

Timer in modalità PWM

Per un'onda quadra di frequenza fPWMf_{PWM} con duty-cycle DD da un timer a clock fclkf_{clk}: fPWM=fclk(PSC+1)(ARR+1),Pulse=round(D⋅(ARR+1)).f_{PWM}=\frac{f_{clk}}{(PSC+1)(ARR+1)},\qquad Pulse=\mathrm{round}\big(D\cdot(ARR+1)\big). (Prescaler = PSCPSC, Period = ARRARR nei campi htim.Init; la HALHardware Abstraction Layer: libreria di funzioni che nasconde l'accesso diretto ai registri scrive "valore −1-1".) Esempio d'esame: Prescaler = 0, clock del Timer 1 a 72 MHz, OCMode = TIM_OCMODE_PWM1, fPWM=40f_{PWM}=40 kHz e D=28%D=28\%: conteggi per periodo N=72 MHz40 kHz=1800N=\frac{72\text{ MHz}}{40\text{ kHz}}=1800, quindi Period = XXXX = 1799 (0x0707\texttt{0x0707}) e Pulse = YYYY = 0{,}28\cdot1800=504 (0x01F8\texttt{0x01F8}). In modalità PWM1 l'uscita è alta finché il conteggio è minore di Pulse: 504/1800=28%504/1800=28\% ✓. (Se il testo chiede "numero di conteggi" si usa 1800; se chiede il valore del campo Period, N−1N-1.)

Timer in modalità output compare (toggle)

c
htim1.Init.Prescaler = 0;  htim1.Init.Period = SYNCPER;
sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);

Si configurano solo i parametri, e il timer parte con HAL_TIM_OC_Start. Con timer a 32 MHz e SYNCPER = 0x4572 = 17778: il timer conta alla frequenza di clock (prescaler 0) = 32 MHz; il periodo di conteggio è 17778 tick, cioè un evento ogni 1777832 MHz\frac{17778}{32\text{ MHz}}, frequenza ≈1800\approx1800 Hz; in modalità toggle l'uscita cambia livello a ogni evento: onda quadra a 900 Hz con duty-cycle 50%.

Timer in capture (HRTIM)

c
void HRTIM1_TIMB_IRQHandler(void){
   HAL_HRTIM_IRQHandler(&hhrtim, HRTIM_TIMERINDEX_TIMER_B);
   y = HRTIM1->sTimerxRegs[HRTIM_TIMERINDEX_TIMER_B].CPT1xR;   }

Nella variabile y viene caricato il valore di conteggio del timer B al momento dell'evento di capture (registro CPT1xR della capture unit 1) che ha generato l'interruzione: sottraendo due letture consecutive si ha il periodo.

ADC (HAL)

c
AdcHandle.Init.Resolution = ADC_RESOLUTION12b;  AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
AdcHandle.Init.ScanConvMode = ENABLE;  AdcHandle.Init.ContinuousConvMode = DISABLE;
InjectionConfig.InjectedNbrOfConversion = 3;
InjectionConfig.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_HRTIM_TRG2;
InjectionConfig.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONV_EDGE_RISING;
HAL_ADC_Init(&AdcHandle);  HAL_ADCEx_InjectedConfigChannel(&AdcHandle, &InjectionConfig);
HAL_ADCEx_InjectedStart_IT(&AdcHandle);          // abilita l'interruzione di fine conversione

Letture tipiche del questionario: l'ADC è di tipo SAR; la risoluzione scelta è 12 bit; la scelta della risoluzione influisce sulla durata della conversione (un SAR impiega n+1n+1 cicli); i canali acquisiti sono quelli della sequenza injectedtipo di conversione ADC ad alta priorità, avviata da un trigger esterno (qui 3); la conversione è sincronizzata con un altro processo interno (il trigger è HRTIM_TRG2 sul fronte di salita: le conversioni partono a istanti fissati dal timer, a frequenza costante); l'allineamento a destra dà un dato intero a 12 bit nel registro. La ISRroutine di servizio di una interruzione (ADC1_2_IRQHandler) legge i campioni con HAL_ADCEx_InjectedGetValue(..., ADC_INJECTED_RANK_1) e calcola il filtro (Realizzare un filtro o un regolatore a virgola fissaRealizzare in C un filtro a virgola fissa vuol dire scegliere per ogni dato il formato (coefficienti $<1$ in 0.16 U, coefficienti $>1$ in 1.15, 4.12, 5.11 S...), moltiplicare con aritmetica a 32 bit ($16\times16\to32$) e riportare il prodotto al formato di arrivo con uno shift a destrascorrimento dei bit verso destra: equivale a dividere per una potenza di 2; per sommare due prodotti con scale diverse si fa lo shift prima di sommare. Con $a+b=2^{16}$ il filtro $y=\big(a,y_{k-1}+b,(u\ll4)\big)\gg16$ non va in overflow in un uint32_t. Nel codice il formato di ogni variabile va dichiarato in un commento; la funzione $\sin x\approx x-\frac{x^3}6+\frac{x^5}{120}$ mostra come si approssima una funzione con polinomi a coefficienti in virgola fissa.Realizzare un filtro o un regolatore a virgola fissa →). Per la lettura dei dati convertiti e la loro elaborazione in tempo reale si possono usare polling, interruzioni o DMA (Sottosistema di I-O - polling, interruzioni e DMALe periferiche si collegano al bus con una interfaccia che ha registri di dati (DREG), controllo (CREG) e stato (SREG); nei µC e DSP sono mappati in memoria (si leggono e scrivono come locazioni). Poiché le periferiche sono asincrone e lente, la sincronizzazione avviene per polling (inefficiente), per interruzioni (la più usata) o per DMA (costoso, per grandi quantità di dati). Una interruzione: completa l'istruzione, salva il contesto (PC e registro di stato, spesso altro), esegue la ISR, ripristina. Le sorgenti si distinguono con ISR di stato, interruzioni vettorizzate o daisy-chaincollegamento delle periferiche in catena, in cui il segnale di riconoscimento passa dall'una all'altra; le NMI non sono mascherabili.Sottosistema di I-O - polling, interruzioni e DMA →). Per l'ADC con ContinuousConvMode = ENABLE la conversione riparte da sola.

Altri elementi di laboratorio

Errori comuni

  • Dimenticare la divisione del bus APBAdvanced Peripheral Bus: bus delle periferiche, con clock derivato da quello del bus principale (timer e ADC non ricevono il clock di sistema ma quello del loro bus diviso).
  • Confondere Period (N−1N-1) e conteggi per periodo (NN) e il valore di Pulse (conteggi in alto).
  • Dimenticare che in modalità toggle la frequenza in uscita è la metà di quella degli eventi.
  • Non avviare il timer (HAL_..._Start) dopo averlo configurato.

Versione ripasso

Esercizi su questo argomento

Teoria collegata