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):
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 / 6Con MHz: MHz (è il massimo supportato dall'STM32F334R8, quindi la scelta ha senso); MHz; APB1: 36 MHz (massimo ammesso per questo bus); APB2: 72 MHz; l'ADC è collegato ad APB2, quindi riceve 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 GHz.)
Timer in modalità PWM
Per un'onda quadra di frequenza con duty-cycle da un timer a clock :
(Prescaler = , Period = nei campi htim.Init; la HALHardware Abstraction Layer: libreria di funzioni che nasconde l'accesso diretto ai registri scrive "valore ".) Esempio d'esame: Prescaler = 0, clock del Timer 1 a 72 MHz, OCMode = TIM_OCMODE_PWM1, kHz e : conteggi per periodo , quindi Period = XXXX = 1799 () e Pulse = YYYY = 0{,}28\cdot1800=504 (). In modalità PWM1 l'uscita è alta finché il conteggio è minore di Pulse: ✓. (Se il testo chiede "numero di conteggi" si usa 1800; se chiede il valore del campo Period, .)
Timer in modalità output compare (toggle)
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 , frequenza Hz; in modalità toggle l'uscita cambia livello a ogni evento: onda quadra a 900 Hz con duty-cycle 50%.
Timer in capture (HRTIM)
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)
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 conversioneLetture 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 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
- Configurazione dei pin: un multiplexer di alternate functionmodo in cui un pin viene collegato a una periferica anziché essere un I/O generico collega un pin a una periferica (ci sono 16 mappe).
- Timer HRTIM: ogni modulo A-E ha 4 comparatori e 2 pin;
{{Preload|opzione per cui i registri si aggiornano solo a un comando di update}}abilitato vuol dire che i registri si aggiornano solo con un comando di update: va disabilitato se non c'è. - PWM a 200 kHz sull'STM32G474: con il timer ordinario a 340 MHz (170 MHz con moltiplicatore ×2) ; con l'HRTIM a GHz (risoluzione molto più fine, 14,7 bit).
- Filtri di laboratorio: media mobile di 16 campioni su ADC a 12 bit; passa-basso IIR; PLL con HRTIM (PLL digitale con i timerUn PLL (anello ad aggancio di fase) tiene un segnale generato a frequenza e fase in relazione costante con un riferimento. In un µC si realizza con un timer in capture (misura il periodo del riferimento) e uno in compare (genera gli impulsi). L'errore di fase $\Delta\phi$ (intervallo tra due eventi) va portato a 0 con un regolatore sul periodo $P$ degli impulsi: compensare solo l'errore istantaneo non basta, serve un PI: $P_{int}(k)=P_{int}(k-1)+\frac{\Delta\phi(k)}N$, $P(k+1)=P_{int}(k)+\frac{\Delta\phi(k)}N$ (con $N$ = rapporto delle frequenze), che è dead-beat (errore nullo in un passo, polo in $z=0$). In pratica si riducono i guadagni per robustezza.PLL digitale con i timer →).
- I conti di tempo si fanno sempre dal clock del timer, non dal clock del processore.
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() e conteggi per periodo () e il valore diPulse(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
- HAL: strutture + funzioni (
HAL_..._Init,..._Start); main conwhile(1){}; lavoro nelle ISR. - F334R8: HSE 8 MHz ×
MUL9= 72 MHz (max); AHB ÷1; APB1 ÷2 = 36 MHz; APB2 ÷1 = 72 MHz; ADC su APB2 ÷6 = 12 MHz. G474: 8·85/4 = 170 MHz; HRTIM 5,44 GHz. - PWM: ,
Pulse: 72 MHz, 40 kHz, 28% →Period1799 (0x0707),Pulse504 (0x01F8). - Toggle: 32 MHz,
0x4572: eventi 1800 Hz → onda 900 Hz, duty 50%. Capture:CPT1xR= conteggio all'evento. - ADC: SAR 12 bit, cicli, sequenza injected (3 canali), trigger HRTIM sul fronte, lettura con IRQ/polling/DMA.
- Errori: divisori APB dimenticati;
Period≠; toggle; mancatoStart(Linguaggio C per i microcontrollori - puntatori, tipi e operatoriNei µC a 32 bit (ARM) uncharè 1 byte,short2,intelong4, i puntatori 4 (sizeof(pa)=4).x++restituisce il valore prima dell'incremento,++xdopo;(p++)legge e poi sposta il puntatore disizeof(p)byte. Una macro senza parentesi interne si espande come testo (#define square(x) (xx):64/square(4)=64/(44)= 4). Il prodotto di dueuint16_tè uninta 32 bit con segno: per unuint16_t*uint16_tgrande serve il cast auint32_tprima del prodotto. Divisione intera e resto: $-13/4=-3$, $-13%4=-1$. Per i registri periferici:volatile.Linguaggio C per i microcontrollori - puntatori, tipi e operatori →).