Salta al contenuto
Note per Studenti Linguaggio C per i microcontrollori - puntatori, tipi e operatori

Linguaggio C per i microcontrollori - puntatori, tipi e operatori

In questa pagina 7

La parte 3 di ogni appello (circa 10 minuti) è fatta di domande sul linguaggio C usato per programmare i µC (Microcontrollori STM32 - clock, timer, ADC e codice HALI µC STM32 (core ARM Cortex-M) del corso si configurano con la libreria HAL: si riempiono strutture C (...InitTypeDef) con valori simbolici e si chiamano funzioni di libreria. Clock (STM32F334R8): oscillatore esterno HSE 8 MHz × PLL (MUL9) = SYSCLK 72 MHz (massimo); AHB ÷1, APB1 ÷2 (36 MHz), APB2 ÷1 (72 MHz); clock dell'ADC = clock del bus ÷ divisore (÷6 → 12 MHz). Timer in PWM: $f=f{clk}/((PSC+1)(Period+1))$, Pulse = duty $\times$ conteggi. Toggle: onda quadra a metà della frequenza di match. ADC: SAR a 12 bit, conversioni injected avviate da un trigger del timer, lettura con interruzione.Microcontrollori STM32 - clock, timer, ADC e codice HAL →): leggere un frammento e dire che cosa stampa o che cosa fa. Gli errori tipici sono sempre gli stessi: dimensione dei tipi, incremento prefissooperatore scritto prima della variabile: ++i incrementa e poi restituisce il nuovo valore e postfissooperatore scritto dopo la variabile: i++ restituisce il valore prima dell'incremento, aritmetica dei puntatoriregola per cui sommare 1 a un puntatore lo fa avanzare di un elemento del tipo puntato, macrosostituzione di testo eseguita dal preprocessore prima della compilazione, promozioni di tipo.

Dimensioni dei tipi (processore a 32 bit)

Tipo Byte Note
char, int8_t, uint8_t 1
short, int16_t, uint16_t 2
int, long, int32_t, uint32_t 4
long long, int64_t 8
float 4 virgola mobile in singola precisione
puntatore (qualunque) 4 l'ampiezza dell'indirizzo è la parola della macchina: 32 bit = 4 byte

{{sizeof|operatore che restituisce la dimensione in byte di un tipo o di una variabile}} restituisce byte. Su un processore a 32 bit int *pa; sizeof(pa) vale 4 (non 32: 32 sono i bit). I tipi a larghezza esatta (uint16_t, int32_t della libreria {{stdint.h|libreria C con i tipi a larghezza esatta, come uint16_t e int32_t}}) sono quelli che si usano per i dati a virgola fissa (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 →).

Puntatori

Un puntatore contiene l'indirizzo di una variabile: int *pa = &a; (&\& = indirizzo di; *pa = valore puntato). Un puntatorevariabile che contiene l'indirizzo di un'altra variabile a un tipo di kk byte, incrementato di 1, avanza di kk byte (aritmetica dei puntatori: pa+1 punta alla locazione successiva dello stesso tipo).

Esempio 1 (tema d'esame del 3 settembre 2020, processore a 32 bit):

c
int a = 10; int *pa = &a;
printf("Il valore puntato è: %d", *pa);          // 10
printf("La dimensione del puntatore è: %ld", sizeof(pa));   // 4

Risposta: 10, 4.

Esempio 2 (appello gennaio 2021 e 2022):

c
short int a = 10; short int *pa = &a;

a. *pa vale 10. b. sizeof(pa) = 4 (è un puntatore). c. sizeof(a) = 2 (short). d. printf("%d", (*pa)++); stampa 10: l'operatore postfisso restituisce il valore prima di incrementare; dopo l'istruzione a vale 11. e. printf("%d", *(pa++)); stampa 11 (il valore attuale di a) e poi sposta pa di sizeof(short)=2 byte, quindi pa non punta più ad a.

Incremento prefisso e postfisso

  • i++ (postfisso): l'espressione vale il valore di prima, poi i aumenta.
  • ++i (prefisso): i aumenta, poi l'espressione vale il valore nuovo. Esempio 3 (appello febbraio 2022, doSomething(30)):
c
int doSomething(int i) { return (i++); }          // restituisce 30 (valore prima di incrementare)
int main(void) { int i = doSomething(30); printf("%d", --i); }   // --i = 29

La funzione restituisce 30 (l'incremento riguarda la copia locale e avviene dopo il return del valore); in main i=30, --i decrementa a 29 e stampa 29.

Macro

#define fa una sostituzione di testo prima della compilazione, senza valutare nulla. Esempio 4 (esempio di appello 2020-21):

c
#define square(x) (x*x)
int i = 64/square(4);      // diventa 64/(4*4)

Il risultato è 64/16=464/16=\mathbf4. Se la macro fosse #define square(x) x*x si avrebbe 64/4*4 = (64/4)*4 = 64; e square(a+1) con (x*x) diventa (a+1*a+1), non (a+1)2(a+1)^2: le parentesi vanno messe attorno a ogni uso del parametro e al corpo della macro, ((x)*(x)).

Operatori su bit e interi

Programmi per µC

  • Struttura tipica: inizializzazione (clock, periferiche), poi un while(1){}; il lavoro nelle ISR. Una variabile condivisa tra ISRroutine di servizio di una interruzione e main va dichiarata {{volatile|parola chiave che dice al compilatore che la variabile può cambiare fuori dal flusso del programma, per esempio in una ISR}} (altrimenti il compilatore può tenerla in un registro e non vedere i cambiamenti).
  • I registri delle periferiche sono locazioni di memoria: *(volatile uint32_t *)0x40021000 = 0x1; oppure tramite le strutture della libreria (TIMx->CNT, HRTIM1->sTimerxRegs[...].CPT1xR).
  • Buffer circolare (indirizzamento circolare in software): per una media mobile su 16 campioni si usa un vettore e un indice che riparte da 0: yma -= xma[k]; yma += x; xma[k++] = x; if(k==16) k=0;. Un buffer di lunghezza potenza di 2 permette k = (k+1) & 15.
  • Un printf non esiste in un µC senza sistema operativo (si usa il debugger o la UART); gli esercizi d'esame usano printf solo per chiedere un valore.

Errori comuni

  • Scrivere sizeof(pa)=32 (bit) invece di 4 (byte).
  • Prendere il valore di i++ come quello dopo l'incremento.
  • Dimenticare che le macro non hanno tipi né parentesi implicite.
  • Moltiplicare due uint16_t e pretendere un uint32_t corretto senza il cast.
  • Dividere numeri interi negativi aspettandosi l'arrotondamento per difetto.

Versione ripasso

Esercizi su questo argomento

Teoria collegata