Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
1
Pulse Width Modulation
1
2 CAPITOLO 1. PULSE WIDTH MODULATION
La forma d’onda oscilla tra due stati che possiamo definire come stato
attivo e stato idle in base alla logica di funzionamento che decidiamo di
usare (fig. 1.2)
nell’ampiezza, inoltre, essa può essere sfruttata per regolare la potenza ap-
plicata a carichi come un led o un motore DC.
Per fare ulteriore chiarezza, introduciamo il concetto di valore medio Vav
di un’onda quadra, definito come:
1 ZT 1 Z tactive 1 ZT
Vav = · V (t)dt = · V (t)dt + · V (t)dt; (1.3)
T 0 T 0 T tidle
Figura 1.3: Rappresentazione di segnali PWM generati da due canali dello stesso
TIMER.
Abilitiamo l’uso della periferica nel file halconf.h mettendo l’opportuna define
a TRUE.
Codice 1.1: halconf.h
1 /**
2 * @brief Enables the PWM subsystem.
3 */
4 #if !defined(HAL_USE_PWM) || defined(__DOXYGEN__)
5 #define HAL_USE_PWM TRUE
6 #endif
Ci sono almeno quattro driver (GPT, ICU, PWM, ST) che utilizzano i
TIMER, selezioniamo l’istanza da usare tra quelle che sono ancora disponi-
bili. In caso contrario il compilatore ci avvisa con un errore.
Una volta abilitato il driver, chiamando la funzione halInit() a livello appli-
cativo, il driver PWM passa nello stato di STOP. Attraverso la funzione
pwmStart(), il driver passa nello stato READY dal quale è possibile utiliz-
zare a pieno la periferica.
Funzione Parametri
-pwmp. Puntatore al driver PWM.
void pwmStart(PWMDriver *pwmp, const PWMConfig *config)
-config. Puntatore alla struttura di configurazione.
-pwmp. Puntatore al driver PWM.
void pwmEnableChannel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width) -channel. Canale da abilitare.
-width. Larghezza dell’impulso.
void pwmStop(PWMDriver *pwmp) -pwmp. Puntatore al driver PWM.
7 */
8 pwmmode_t mode;
9 /**
10 * @brief Channel callback pointer.
11 * @note This callback is invoked on the channel compare event. If set to
12 * @p NULL then the callback is disabled.
13 */
14 pwmcallback_t callback;
15 /* End of the mandatory fields.*/
16 } PWMChannelConfig;
1.3 Esercizio 22
Creiamo un nuovo progetto e nominiamolo 22-RT-STM32F4-BLACK-
PILL-PWM. Il nostro intento è quello di pilotare il LED RED, preceden-
temente connesso al PIOA 107 , con un segnale PWM con un periodo di 1 ms
variandone il duty cycle in maniera graduale per aumentare l’intensità lumi-
nosa.
La prima cosa da fare è controllare se il pin PIOA 10 supporta la modalità
TIMER.
Dal documento stm32f401re controlliamo le funzionalità dei pin (fig. 1.5).
Il pin PA10 in modalità alternate 1, funziona da canale 3 del TIMER 1, può
essere dunque utilizzato per pilotare il led rosso.
Abilitiamo il driver PWM, come abbiamo visto precedentemente, nei file
halconf.h e mcuconf.h selezionando l’istanza del TIMER 1.
Nella struttura di configurazione utilizziamo il canale 3 in modalità ACTI-
VE_HIGH e lasciamo a NULL i campi delle callback.
Codice 1.5: struttura di configurazione PWM esercizio 22.
1 static PWMConfig pwmcfg = {
2 10000, /* 10kHz PWM clock frequency. */
3 10, /* Initial PWM period 1ms. */
4 NULL,
5 {
6
Passaggio da active a idle.
7
Capitolo GPIO
1.3. ESERCIZIO 22 9
6 {PWM_OUTPUT_DISABLED, NULL},
7 {PWM_OUTPUT_DISABLED, NULL},
8 {PWM_OUTPUT_ACTIVE_HIGH, NULL},
9 {PWM_OUTPUT_DISABLED, NULL}
10 },
11 0, /* hw dependent. */
12 0
13 };
7 (void)arg;
8 chRegSetThreadName("brighter");
9
10 uint32_t duty = 0;
11
12 while (true) {
13 /*
14 * Changes the PWM channel 0 to 100% duty cycle.
15 */
16 pwmEnableChannel(&PWMD1, 2, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, duty));
17 duty += 1000;
18
19 /* if 100% duty then restart. */
20 if (duty > 10000) {
21 duty = 0;
22 }
23 chThdSleepMilliseconds(250);
24 }
25 }
1.4 Esercizio 23
Modifichiamo l’esercizio precedente utilizzando le callback per pilotare un
secondo LED, quello giallo, collegato al PA9. Lo scopo dell’esercizio è creare
un segnale PWM complementare alzando e abbassando il pin attraverso le
funzioni palClearPad() e palSetPad() all’interno delle callback del periodo e
del canale.
Codice 1.8: main.c esercizio 23
1 /* Called every T_pwm */
2 static void pwmpcb(PWMDriver *pwmp) {
8
Attenzione! I canali sono numerati da 0 a n-1. Il canale 3 fisico del TIMER viene
identificato dal numero 2.
1.4. ESERCIZIO 23 11
3
4 (void)pwmp;
5 palClearPad(GPIOA, 9);
6 }
7
8 /* Called every Active duty time */
9 static void pwmc1cb(PWMDriver *pwmp) {
10
11 (void)pwmp;
12 palSetPad(GPIOA, 9);
13 }
14
15 static PWMConfig pwmcfg = {
16 10000, /* 10kHz PWM clock frequency. */
17 10, /* Initial PWM period 1ms. */
18 pwmpcb,
19 {
20 {PWM_OUTPUT_DISABLED, NULL},
21 {PWM_OUTPUT_DISABLED, NULL},
22 {PWM_OUTPUT_ACTIVE_HIGH, pwmc1cb},
23 {PWM_OUTPUT_DISABLED, NULL}
24 },
25 0,
26 0
27 };
Nota Frequenza
C5 523.25 Hz
D5 587.33 Hz
E5 659.26 Hz
F5 698.46 Hz
G5 783.99 Hz
A5 880.00 Hz
B5 987.77 Hz
C6 1046.50 Hz
1.5 Esercizio 24
Attraverso il PWM siamo in grado anche di generare note pilotando uno
speaker, in questo caso una variazione di duty cycle corrisponde a una varia-
zione di volume della nota prodotta.
Lo speaker in dotazione nel KIT ha un’impedenza di 8 Ω e una potenza di
0.25 W, colleghiamone un’estremità a massa e l’altra al pin PA89 . Pilotiamo
lo speaker in modo da generare i suoni appartenenti a una scala maggiore di
DO.
La tabella 1.2 riporta i valori di frequenza per le note da C5 e C6. Fissiamo la
frequenza del TIMER a 1 MHz, e calcoliamo i valori di period per ciascuna
nota con la formula:
ftimer
period = . (1.8)
fnote
dove fnote rappresenta la frequenza della nota desiderata.
Per generare note diverse quindi è necessario cambiare il period durante l’e-
secuzione del programma, possiamo farlo utilizzando la funzione pwmChan-
gePeriod().
Codice 1.10: main.c esercizio 24.
1 /* Note period */
2 #define C5 1911
3 #define D5 1703
4 #define E5 1517
5 #define F5 1432
6 #define G5 1275
7 #define A5 1136
8 #define B5 1012
9
TIMER 1 channel 1.
1.5. ESERCIZIO 24 13
9 #define C6 956
10
11 #define SIZE 8
12
13 /* C Major scale */
14 static pwmcnt_t scale[SIZE] = {C5, D5, E5, F5, G5, A5, B5, C6};
15
16 static PWMConfig pwmcfg = {
17 1000000, /* 1MHz PWM clock frequency. */
18 100, /* Initial PWM period 0.1ms. */
19 NULL,
20 {
21 {PWM_OUTPUT_ACTIVE_HIGH, NULL},
22 {PWM_OUTPUT_DISABLED, NULL},
23 {PWM_OUTPUT_DISABLED, NULL},
24 {PWM_OUTPUT_DISABLED, NULL}
25 },
26 0,
27 0
28 };
29 ...
30 /*
31 * PWM LED RED thread, times are in milliseconds.
32 */
33 static THD_WORKING_AREA(waThread2, 256);
34 static THD_FUNCTION(Thread2, arg) {
35
36 (void)arg;
37 chRegSetThreadName("player");
38
39 uint32_t i;
40
41 while (true) {
42
43 /*
44 * Changes the period.
45 */
46 for (i = 0; i < SIZE; i++) {
47 pwmChangePeriod(&PWMD1, scale[i]);
48 pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 6000));
49 chThdSleepMilliseconds(1000);
50 pwmDisableChannel(&PWMD1, 0);
51 }
52 chThdSleepMilliseconds(1000);
53 }
54 }
55
56 /*
57 * Application entry point.
58 */
59 int main(void) {
60
61 /*
62 * System initializations.
63 * - HAL initialization, this also initializes the configured device drivers
64 * and performs the board-specific initializations.
65 * - Kernel initialization, the main() function becomes a thread and the
66 * RTOS is active.
67 */
14 CAPITOLO 1. PULSE WIDTH MODULATION
68 halInit();
69 chSysInit();
70
71 /*
72 * Configure PA8 as TIM1 channel 1
73 */
74 palSetPadMode(GPIOA, 8, PAL_MODE_ALTERNATE(1));
75
76 /* Start PWMD1 */
77 pwmStart(&PWMD1, &pwmcfg);
78
79 ...
80 }
1.6 Esercizio 25
Adesso che sappiamo come generare note pilotando uno speaker, scrivia-
mo una nuova demo che suoni un brano e non una semplice scala maggiore.
In un file song.h definiamo il period delle note, la loro durata e la velocity
intesa come intensità di volume. Associamo ad ogni nota del brano la durata
e la corrispondente velocity utilizzando un array multidimensionale10 .
Codice 1.11: main.c esercizio 25.
1 /*
2 * PWM LED RED thread, times are in milliseconds.
3 */
4 static THD_WORKING_AREA(waThread2, 256);
5 static THD_FUNCTION(Thread2, arg) {
6
7 (void)arg;
8 chRegSetThreadName("player");
9
10 uint32_t i;
11
12 while (true) {
13
14 /*
15 * Changes the period.
16 */
17 for (i = 0; i < (sizeof march / sizeof march[0]); i++) {
18 pwmChangePeriod(&PWMD1, march[i][0]);
19 pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, march[i][2]));
20 chThdSleepMilliseconds(march[i][1] - 50);
21 pwmDisableChannel(&PWMD1, 0);
22 chThdSleepMilliseconds(50);
23 }
24 chThdSleepMilliseconds(1000);
25 }
26 }
10
Abbiamo modellato una nota come un array di tre elementi.
1.7. SHELL 15
Come si evince dal codice, i valori di period, duty e durata sono letti dall’arry
multidimensionale march 11 dichiarato nel file song.h.
1.7 SHELL
Aggiungiamo le funzionalità del PWM all’applicativo SHELL. Copia-
mo la precedente SHELL e rinominiamola 26-RT-STM32F4-BLACK-PILL-
SHELL. Implementiamo due nuovi comandi:
11
Il brano trascritto è la marcia imperiale tratta dalla saga di Star Wars.
16 CAPITOLO 1. PULSE WIDTH MODULATION
3
4 static thread_t *tplayp = NULL;
5
6 static PWMConfig pwmcfg = {
7 1000000, /* 1MHz PWM clock frequency. */
8 100, /* Initial PWM period 0.1ms. */
9 NULL,
10 {
11 {PWM_OUTPUT_ACTIVE_HIGH, NULL},
12 {PWM_OUTPUT_DISABLED, NULL},
13 {PWM_OUTPUT_DISABLED, NULL},
14 {PWM_OUTPUT_DISABLED, NULL}
15 },
16 0,
17 0
18 };
19
20 /*
21 * Player song thread
22 */
23 static THD_FUNCTION(playerThread, arg) {
24
25 (void)arg;
26 chRegSetThreadName("player");
27
28 uint32_t i;
29
30 /* Start PWMD1 */
31 pwmStart(&PWMD1, &pwmcfg);
32
33 /*
34 * Plays until terminated
35 */
36 while (!chThdShouldTerminateX()) {
37 /*
38 * Changes the period.
39 */
40 for (i = 0; i < (sizeof march / sizeof march[0]); i++) {
41 if (chThdShouldTerminateX()) {
42 break;
43 }
44 pwmChangePeriod(&PWMD1, march[i][0]);
45 pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, march[i][2]));
46 chThdSleepMilliseconds(march[i][1] - 50);
47 pwmDisableChannel(&PWMD1, 0);
48 chThdSleepMilliseconds(50);
49 }
50 chThdSleepMilliseconds(1000);
51 }
52
53 /* Stop PWMD1 */
54 pwmStop(&PWMD1);
55 chThdExit(MSG_OK);
56 }