Raspberry Pi Pico : PWM con fasi invertite in MicroPython

Abbiamo visto nell’articolo precedente come viene gestito il PWM sul Raspberry Pi Pico e come utilizzare questa funzione da MicroPython per controllare, ad esempio, la velocità e il verso di rotazione di un motore o la luminosità di un LED. Abbiamo visto qualche differenza con il C: sono veramente tante.

L’inversione di fase per i Ponti H

Una funzione che torna molto utile per il pilotaggio dei motori è la possibilità di avere le fasi invertite per le uscite appartenenti allo stesso Slice. Quando si pilotano ponti H in modalità LAP, il ponte H normalmente richiede, per un solo motore, due segnali con le fasi opposte applicate ai due ingressi del ponte: nel momento in cui un segnale di pilotaggio ha, ad esempio, il livello alto che dura x millisecondi, il segnale di pilotaggio all’altro ingresso deve andare a livello basso per esattamente x millisecondi.

Metto qui lo schema classico con il quale si illustrano i ponti H per ricordarne il principio di funzionamento:

Per far girare il motore in un verso si chiuderanno gli interruttori S1 e S4 (ramo A) lasciando aperti S3 e S2 (ramo B). Per farlo girare nel verso opposto si chiuderanno S3 e S2 lasciando aperti S1 e S4. Capite che chiudere, ad esempio, S1 e S2 porta ad un cortocircuito per cui è necessario che quando gli interruttori di un ramo si trovano in uno stato, i due interruttori dell’altro ramo devono trovarsi nello stato opposto.

Nei ponti H gli switch sono sostituiti da Mosfet o Transistor e S1 ed S4 appartengono ad un input (generalmente indicato sui datasheet come input A) e S3 e S2 all’altro input (B). Ecco da dove nasce l’esigenza di avere due segnali PWM che abbiano fasi opposte.

Molti comuni ponti H integrati,  come gli L293, L298, SN754410 (che tutti noi alle prese con robottini abbiamo utilizzato), richiedono i segnali fatti proprio in questo modo. I ponti H venduti su breakout-board hanno spesso a bordo un inverter (ad esempio un IC 74HC04)per fare in modo da utilizzare un unico segnale di pilotaggio e ricavare l’altro tramite una porta NOT, come nel caso del mio ponte H Cent4ur. Nel caso abbiate un ponte H fatto in questo modo, tutta questa storia è chiaramente inutile ma vi servirà per capire come manipolare i registri manualmente da MicroPython sull’RP2040.

Manipolare i registri (senza scomodare l’assembler)

In C abbiamo le funzioni apposite per poter invertire le fasi dei segnali in uscita sui due rami dello stesso canale:

pwm_set_output_polarity(slice, outA, outB);

In MicroPython abbiamo visto invece che le uniche funzioni (metodi) per l’utilizzo della classe PWM sono freq() e duty_u16(). Come fare quindi a gestire le opzioni extra? Accedendo direttamente ai registri di sistema, ma possiamo farlo senza scomodare l’assembler.

Sul Commodore 64 dal Basic avevamo accesso alle istruzioni n=PEEK(r) e POKE r,n rispettivamente per leggere il valore da un registro e impostare un valore in un registro. In MicroPython ci sono i metodi mem (mem32[], mem16[], mem8[]) della classe machine che permettono di leggere e modificare i registri.

Questi metodi non sono riportati sulla documentazione ufficiale del MicroPython (capitolo machine) ma si vede chiaramente, dai sorgenti del MicroPython, che sono implementati per qualsiasi piattaforma dato che si trovano come modulo esterno non legato alla piattaforma stessa (extmod/machine_mem.c)

I registri che si occupano della gestione del PWM sull’ RP2040 partono dall’indirizzo 0x40050000 (indicato nell’SDK come PWM_BASE). Tutto questo è riportato a pag. 542 del datasheet. All’offset 0x00 da PWM_BASE (ovvero proprio 0x40050000) c’è il registro CH0_CSR che rappresenta il Control Status Register del Canale PWM 0. Il significato dei bit di questo registro è riportato a pag. 544:

Vedete che i bit 2 e 3 permettono l’inversione delle uscite A e B e di default si trovano a zero (ultima colonna). Mettendo ad 1 uno solo dei due bit si avranno le due uscite con il segnale invertito. Come si fa dato che MicroPython non ha questa funzione? Utilizzando il metodo mem32[] che permette di accedere ad una locazione di memoria costituita da 32bit:

PWM_BASE=0x40050000
r=machine.mem32[PWM_BASE]
machine.mem32[PWM_BASE]=r^(1<<3)

Vedete che l’indirizzo del registro va scritto tra parentesi quadre. Cosa ho fatto con queste 3 istruzioni? Con la prima ho dichiarato una variabile che contiene l’indirizzo di partenza dei registri PWM, questo indirizzo corrisponde al registro CH0_CSR che permette di controllare il PWM del canale 0. Con la seconda riga leggo il contenuto del registro e lo memorizzo in una variabile, con la terza riga imposto soltanto il bit n°3 (il quarto) che permette di invertire l’uscita B come da datasheet (il simbolo ^ come in C è l’OR logico, e 1<<3 è il bitshift del valore 1 di 3 posizioni verso sinistra che equivale a 1000 in binario o 8 in decimale e in esadecimale se preferite).

Codice per Raspberry Pi Pico

Facendo riferimento all’articolo precedente, questo codice permette di avere due segnali PWM a 150kHz, con duty cycle al 50% sui pin 21 e 22 (GP16 e GP17):

# https://www.settorezero.com
# Raspberry Pi Pico - esempio PWM
# PWM 150kHz, duty 50%, channel B with phase inverted
 
from machine import Pin, PWM
 
PWM_BASE=0x40050000 # this correspond to the first PWM register that is CH0_SR, datasheet p.542
 
pwmA=PWM(Pin(16)) # GP16
pwmB=PWM(Pin(17)) # GP17
pwmA.freq(150000) # 150kHz
pwmA.duty_u16(int(65535/2)) # duty 50%
pwmB.duty_u16(int(65535/2)) # duty 50%
r=machine.mem32[PWM_BASE] # read the actual CH0_SR register value
print(r) # will print 1 => only EN (bit 0) set 
machine.mem32[PWM_BASE]=r^(1<<3) # set bit 3 
r=machine.mem32[PWM_BASE] # re-read register 
print(r) #will print 9 => now also the B_INV bit (bit 3) is set

Ho messo delle istruzioni print per stampare il contenuto del registro prima e dopo la modifica, vedete che prima della modifica il registro contiene il valore 1, ovvero solo il bit EN settato, dopodichè vale 9 perchè è stato settato il 3° bit (che ha valore 8).

Dall’oscilloscopio si può vedere che le due uscite hanno la fase invertita:

Quando vorremo variare il Duty Cycle basterà semplicemente impostarne lo stesso valore per entrambe le uscite dato che il flag impostato nel registro provvederà a fare lui gli aggiustamenti per avere l’opposizione di fase su uno dei due canali. A breve carico sul Repository GitHub anche questi esempi (sul mio instagram personale generalmente pubblico le cose man mano che le faccio anche se non sono complete).

Links

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)