Generare una sinusoide con il microcontroller.
Una forma d'onda qualsiasi può essere generata dal microcontroller
utilizzando un convertitore DA.
|
Purtroppo si tratta di una periferica presente solo su alcuni chip e, dove non
c'è,
l'alternativa è quella di utilizzare un chip esterno, che, a sua volta,
richiede una comunicazione SPI o I2C, che va implementata. La soluzione più comune
diventa quella di utilizzare una rete R-2R esterna.
Qui, però, abbiamo la necessità di un numero di I/O digitali pari alla
definizione della rete: per 8 bit avremo 8 pin impegnati.
Quando ci troviamo a disporre di un numero minore di pin, come nel caso dei chip in
contenitore a 8 pin, questo non è possibile ed occorre utilizzare una diversa tecnica. |
Sappiamo che, in una modulazione PWM dove si mantiene fissa la frequenza e si
varia il duty cycle, si trasferisce all' uscita una percentuale variabile
di energia.
Possiamo utilizzare il segnale PWM in diversi modi, di cui i più comuni sono:
- applicarlo direttamente al pilotaggio del carico, anche attraverso un buffer,
per variare, ad esempio, luminosità di LED o velocità di motori
- oppure, inserendo un
filtro passa basso che isoli solamente la componente di corrente continua,
ottenendo una tensione variabile. In questo caso,il filtro passa basso eliminerà ogni componente alternata, compresa
la frequenza fondamentale.
Ma possiamo anche applicare un filtro che elimini le armoniche
superiori alla fondamentale, mantenendo questa ed estraendola come componente
alternata a frequenza costante, il cui livello varierà seguendo il variare del duty cycle.
Il modulo CCP/PWM è presente su molti PIC,
anche con la possibilità di più canali; lo troviamo anche in piccoli chip a
8 pin, come il 12F683, microcontroller
Midrange abbastanza diffuso e che impiegheremo in questa applicazione.
Ovviamente è possibile replicare la cosa per qualsiasi altro PIC
dotato di CCP/PWM.
Ci si può chiedere quale sia il vantaggio di utilizzare il microcontroller per generare
una forma d'onda. Questo è essenzialmente legato alla elevata precisione in frequenza che dipende dall' oscillatore a
cristallo del clock. La sinusoide in uscita avrà la stessa precisione
dell'oscillatore del clock, precisione difficilmente ottenibile con circuiti
analogici, che richiedono componenti RC di valori non sempre facilmente
reperibili.
Qui, vogliamo ottenere 1kHz, frequenza base per un gran numero di misure.
L'idea è tratta da un lavoro di Roman
Black.
Il circuito
Possiamo realizzare un circuito basato su questi principi.
Viene utilizzato un oscillatore a cristallo da 20MHz oppure, in
alternativa, un oscillatore esterno. Nello schema sono indicati entrambi, ma,
ovviamente, uno solo sarà cablato.
L' oscillatore esterno, a fronte di un
costo maggiore, offre una maggiore precisione e stabilità.
L' oscillatore
classico a cristallo prevede un compensatore per aggiustare la frequenza. I
valori dei condensatori dipendono dal quarzo usato e sono compresi tra 18 e
33pF; nei prototipi sono stati usati, con quarzi di diversi produttori, dei
condensatori NP0 da 22pF.
Con il clock a 20MHz, abbiamo un ciclo istruzione di 200ns. Questo ci
permette di ottenere dal modulo CCP un preciso PWM con una cadenza di 20us.
Il programma prevede una uscita ausiliaria da 50kHz che può essere
utilizzata, come vedremo, per il filtro successivo. Dalla stessa uscita è anche la possibilità di ottenere
in alternativa 1MHz (duty
cycle 40%), sia per facilitare la calibrazione che per uso generale. Questa
funzione è ottenuta chiudendo il pulsante S1.
Un LED bicolore a due pin è utilizzato per segnalare il modo di funzionamento.
Per minimizzare il consumo si sfrutta l'interruttore S1: quando questo è
chiuso, il microcontroller porta a livello alto il pin GP0, accendendo il LED
rosso (R) attraverso R2. Quando S1 è aperto, GP0 sarà portato a 0,
accendendo il LED verde (G) attraverso R1.
I componenti sono:
R1 |
R2 |
R3 |
C1 |
C2 |
C3 |
C4 |
C5 |
Q1/QG1 |
S1 |
LED |
820 |
820 |
47k |
18-33p |
3-30p |
100n |
100n |
100u |
20MHz |
interr. |
bicol. |
Il segnale a 1kHz è inviato ad un filtro passa basso che
sopprime le armoniche e alla cui uscita si troverà la sinusoide con una
distorsione minima.
Una prima soluzione è data da una serie di filtri RC:
Si tratta di tre passa basso di cui il primo comprende anche una
induttanza. Il vantaggio è dato dal consumo nullo sull' alimentazione e da un
relativo basso costo; nonostante un calcolo preciso richieda valori poco
comuni, ugualmente si possono ottenere risultati positivi con componenti
facilmente reperibili. Per contro, introduce una certa attenuazione. C4
è un non polarizzato che elimina la componente continua.
I valori sono:
R1 |
R2 |
R3 |
L1 |
C1 |
C2 |
C3 |
C4 |
330 |
330 |
820 |
2.2mH |
0.33uF |
68nF |
68nF |
10uF np |
Una seconda soluzione, un poco più complessa, è quella di utilizzare un
filtro digitale. Possiamo scegliere uno dei vari integrati ideati per questo
scopo, basati su capacità commutate e prodotti da vari costruttori. Si
prestano bene MAX295/296 di Maxim, che hanno un rapporto 50:1 tra
frequenza di clock e frequenza filtrata.
Qui usiamo un MAX296,
Bessel di ottavo ordine
L' alimentazione è filtrata con un LC (L1/C5/C6) che separano molto bene
il filtro dai disturbi digitali indotti sull' alimentazione.
Clock e PWM sono inviati direttamente al MAX; questo è previsto per una
alimentazione duale, che, volendo implementare, garantisce i risultati
migliori. Però, qui il circuito è previsto per una alimentazione singola e
si rende necessaria una massa virtuale costituita da R1/R2/C8.
R6 è una resistenza di protezione che separa il connettore X2 dal circuito.
I MAX29x integrano anche un operazionale (pin 3-4) che viene utilizzato come
ulteriore passa basso (topologia Sallen-Key multiple feedback):
I componenti:
R1 |
R2 |
R3 |
R4 |
R5 |
R6 |
C1 |
C2 |
C3 |
C4 |
C5 |
C6 |
C8 |
L1 |
10k |
10k |
16.5k |
14.7k |
14.7k |
50 |
22nF |
10uF
np |
100uF |
4nF7 |
10nF |
100uF |
4uF7
tant. |
1mH |
Le resistenze del filtro sono approssimate ai valori SMD più facilmente
reperibili.
Lo sviluppo è possibile con gran facilità usando la scheda LPCuB:
I filtri sono realizzati esternamente su breadboard per una rapida
variazione dei componenti.
La versione finale potrà poi essere realizzata su un circuito ad hoc.
Il programma
Nel programma, il PWM viene alimentato con il clock a 20MHz: per un ciclo,
stabilito da PR2, di 100 impulsi di clock, otterremo una frequenza di 50kHz.
; initialize CCP module for PWM
; Microchip suggestion on data sheet
clrf
CCP1CON ; CCP Module is off
clrf
TMR2 ; clear Timer2
banksel PR2
movlw
(.100-1)
movwf
PR2
banksel GPIO
clrf
INTCON ; clear T0IF
banksel TRISIO
bcf
Sout ; make pin output
banksel GPIO
clrf
PIR1 ; clear peripheral interrupts Flags
; CCP1 ON, and set to PWM mode
movlw
b'00001101'
movwf
CCP1CON
movlw
.52 ; first value from the table
movwf
CCPR1L
; and start TRM2
movlw
b'00000100'
movwf
T2CON |
Ad ogni impulso in uscita del segnale PWM viene associato un diverso valore
di duty cycle, prelevato da una tabella di 50 passi, ottenendo così una
variazione sinusoidale del duty cycle stesso tra circa il 15 e l'85% ogni
kilohertz.
La direttiva dt consente
di compilare rapidamente la tabella retlw.
; "harmonic compensated" sine table
sinetable addwf PCL,f
dt 52,57,62,66,70,74,77,80,82,84,85,86,86
dt 86,85,83,81,78,75,72,69,65,61,56,52
dt 48,44,39,35,31,28,25,22,19,17,15,14,14
dt 14,15,16,18,20,23,26,30,34,38,43,48 |
Contemporaneamente viene generato un segnale ad onda quadra da 50kHz che
sarà usato per il filtro digitale. Questo segnale ha un duty cycle del 50% in
quanto i filtri attivi richiedono questo per operare al meglio; la cosa è
ottenuta semplicemente all'interno del loop di aggiornamento del PWM, con un
impulso a livello alto pari a 50 cicli istruzione.
; 50kHz pulse
bsf
Faux ; pulse start
movlw 0x10
; 49 cycles
movwf d1
dcyc decfsz d1, f
goto
dcyc
bcf
Faux ; pulse end
|
Dato che il prossimo impulso sarà attivato alla fine di un ciclo PWM che
dura 100 cicli istruzione, ecco che abbiamo il duty cycle corretto.
Un bonus ulteriore è fornito dall'interruttore S1, la cui chiusura devia
l' esecuzione su un tratto di programma che genere un'onda quadra con duty
cycle al 40% e frequenza di 1MHz, che può essere usata per la taratura dell'
oscillatore. L' asimmetria è dovuta alla necessità del test sullo stato
dell'interruttore che commuta le modalità e al loop, dato che il ciclo
istruzione è 200ns.
; Calibrate mode: make a 1MHz fixed freq
on pin Faux
; 5 instructions per cycle,
; output squarewave 1MHz with 40% on duty.
cal_mode:
clrf
CCP1CON ; CCP1 module is turned OFF
; MHz mode - LED red on
LEDRED
loop_1MHz:
bsf
Faux ; output high
nop
bcf
Faux ; output low
btfsc btn
; test switch for SINE mode
goto done_1MHz
; switch hi - exit go to SINE mode
bsf
Faux ; no - output high
nop
bcf
Faux ; output low
goto
loop_1MHz ; loop
done_1MHz:
; out from MHz mode
goto
sine_mode ; back to sine mode |
L' interruttore è anche dotato di un tempo di debounce, per evitare che i
rimbalzi generino stati di uscite casuali.
Il LED bicolore viene comandato in funzione della posizione di S1.
La realizzazione
E' stata realizzata una versione portatile alimentata a batteria.
Qui, sono utilizzati 4 accumulatori NiMH che fornisco una tensione variabile
tra 4.8V (carichi) e 4.4V (scarichi), il che rientra nelle possibilità del
microcontroller, anche se è probabile che la variazione della tensione di
alimentazione si rifletta negativamente sulla stabilità. E' stato usato
il filtro passivo, che non consuma corrente, aumentando la durata della
batteria (l' intero circuito assorbe circa 7.5 mA).
L'oscillatore è a cristallo e il trimmer capacitivo consente un aggiustamento
della frequenza.
Una versione "da banco" è facilmente realizzata con il
solito regolatore 7805 e un wall plug esterno. Qui è stato implementato il
filtro digitale e l'oscillatore del clock è un modulo esterno.
Il firmware è identico per entrambe le versioni; va solo modificato il
config iniziale dove si voglia usare l'oscillatore esterno:
; config per oscillatore a
quarzo
__config _HS_OSC & _WDTE_OFF & _PWRTE_ON & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF & _MCLRE_OFF
; config per oscillatore esterno
__config _EC_OSC & _WDTE_OFF &
_PWRTE_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF & _MCLRE_OFF |
Il segnale in uscita dal PWM va considerato nella sua componente alternata
alla frequenza fondamentale. Però, dato che la variazione del duty cycle non
è continua, ma a passi, si introducono sensibili componenti a frequenze
multiple, che vanno abbattute dal filtro passa basso. In particolare la
seconda armonica è la più evidente, mentre gli errori di campionamento dei
valori in tabella sono secondari.
Sia il filtro passivo che quello attivo rendono risultati soddisfacenti:
Filtro passivo, prima cella RLC e uscita. Accanto, la FFT data dall'
oscilloscopio.
All' inizio del filtro si nota la forte presenza di armoniche, che vengono poi
ridotte all' uscita.
La sinusoide in uscita a una ampiezza a vuoto di circa 1,4V.
Ritoccando il compensatore è possibile aggiustare il valore della
frequenza dell' oscillatore. Con gli strumenti a disposizione è stato
rilevato come migliore valore la frequenza di 1.000.070 Hz.
Qui vediamo l' uscita a 1MHz e quella a 50kHz:
Per quanto riguarda il filtro digitale, questa è l'uscita.
C vs. Asm
L'idea
originale dispone di un sorgente in C nel quale si nota una consistente
intrusione di Assembly.
Questo perchè non è possibile utilizzare il C in modo da assicurare un
totale controllo a livello di istruzioni singole, come in questo caso, dove le
temporizzazioni, ad esempio quella per generare il MHz, sono stringenti.
L' Assembly offre un pieno controllo dei tempi, così che, ad esempio, l'
inserimento del clock a 50kHz è estremamente semplice, mentre il C avrebbe
richiesto ancora sezioni asm.
Qui abbiamo preferito un approccio interamente Assembly, che è molto
meglio calibrato sull'applicazione, la quale non richiede calcoli o strutture
logiche complesse, ma solamente una gestione ottimale dei bit degli I/O. E,
per quello che riguarda i registri di controllo del modulo CCP/PWM, non esiste
una reale differenza tra C e Assembly nella forma e nella comprensione del
sorgente.
Come bonus, l' eseguibile prodotto dal sorgente Assembly è di dimensioni
sensibilmente minori e richiede meno RAM.
Per concludere, si può costatare che, per programmi dove si ha a che
fare essenzialmente con I/O e SFR ed occorre rispettare temporizzazioni
critiche, l' approccio in C non sempre è quello migliore e andrebbe
riservato ad altri ambiti, sopratutto in piccoli PIC con risorse limitate e
poco ottimizzati per questo linguaggio.
Il sorgente è stato compilato per il 12F683, ma è adattabile con gran
facilità a qualsiasi altro PIC dotato del modulo CCP/PWM, semplicemente
adeguando la programmazione per accedere agli I/O digitali (escludendo le
funzioni alternative), mentre la gestione del PWM resta sostanzialmente
invariata passando ad altri Midrange, Enhanced Midrange e anche PIC18F. E'
pure
facilmente adattabile ai 10F32x, che hanno MSSP e ingresso del clock esterno,
rinunciano al clock ausiliario o al LED di indicazione (a causa del basso
numero di pin).
E, data la semplicità del sorgente, non sarà difficile
passarlo anche a microcontroller di altri produttori.
Il principio potrà essere applicato per ottenere valori diversi della
frequenza in uscita, modificando il clock e/o il PWM.
Il sorgente
;******************************************************************************
; Sine1kHz.asm Precision 1kHz sinewave generator
;
from an idea of www.RomanBlack.com/
;
; PIC 12F683 - 20MHz xtal
;-----------------------------------------------------------------------------
; I/O pins;
;
#define LED GPIO,GP0 ; Out LED Red
#define Faux GPIO,GP1 ; Out Faux
#define Sout GPIO,GP2 ; Out CCP1
#define btn GPIO,GP3 ; In button
; GP4 oscout
; GP5 oscin
;--------------------------------------------------------------------
;
LIST
p=12F683 r=DEC
#include
<p12F683.inc>
; for chrystal oscillator
__config _HS_OSC & _WDTE_OFF & _PWRTE_ON &
_BOREN_OFF & _IESO_OFF & _FCMEN_OFF & _MCLRE_OFF
; for external oscillator module
; __config _EC_OSC & _WDTE_OFF & _PWRTE_OFF & _BOREN_OFF
& _IESO_OFF & _FCMEN_OFF & _MCLRE_OFF
;*********************************************************************
; Data RAM area
CBLOCK 0x20
pwmstep ; PWM step 0-49
debounce ; debouncing switch input
d1 ; temporary for delay
ENDC
;********************************************************************
; LOCAL MACROS
; CFLSB Compare File to Literal and Skip if Below
; Compare register with literal and jump next line if
; file < literal
CFLSB MACRO file,lit
movlw (255-lit+1) ; W = -lit
addwf file,W
; W = file + W = file + (-lit)
skpnc
; C=0 for file<lit, than skip next line
ENDM
; C=1 for file>=lit, than execute next line
LEDGREEN MACRO
bcf LED
ENDM
LEDRED MACRO
bsf LED
ENDM
;=====================================================================
; MAIN
RESVEC ORG 0x00
Main goto init
; "harmonic compensated" sine table
sinetable addwf PCL,f
dt 52,57,62,66,70,74,77,80,82,84,85,86,86
dt 86,85,83,81,78,75,72,69,65,61,56,52
dt 48,44,39,35,31,28,25,22,19,17,15,14,14
dt 14,15,16,18,20,23,26,30,34,38,43,48
;--------------------------------------------------------------------
init:
; preload GPIO output latch
movlw 0
movwf GPIO
;comparators OFF, all pins digital
movlw b'00000111'
movwf CMCON0
; analog off and all possible as out
banksel ANSEL
clrf TRISIO
clrf ANSEL
; TMR0 not used, but clear T0CKI
movlw b'10001000'
movwf OPTION_REG
banksel GPIO
; setup any variables before main loop
clrf pwmstep
clrf debounce
;--------------------------------------------------------------------
; Sine mode: use PWM to make the sine out pin
sine_mode:
; LED green is on, red is off
LEDGREEN
; initialize CCP module for PWM
; Microchip suggestion on data sheet
clrf CCP1CON ; CCP
Module is off
clrf TMR2
; clear Timer2
banksel PR2
movlw (.100-1)
movwf PR2
banksel GPIO
clrf INTCON ; clear
T0IF
banksel TRISIO
bcf Sout
; make pin output
banksel GPIO
clrf PIR1
; clear peripheral interrupts Flags
; CCP1 ON, and set to PWM mode
movlw b'00001101'
movwf CCP1CON
movlw .52
; first value from the table
movwf CCPR1L
; and start TRM2
movlw b'00000100'
movwf T2CON
; loop: load new PWM value every TMR2 cycle
slp0 incf pwmstep,f ; inc to next step
in sinewave
CFLSB pwmstep, .50 ; sine has 50 steps
clrf pwmstep
movf pwmstep,w
; read new PWM value
call sinetable
; from sinetable
slp1 btfss PIR1,TMR2IF ; wait for TMR2 cycle to restart
goto slp1
movwf CCPR1L
; load new value
bcf PIR1,TMR2IF ;
clear TMR2 int flag
; 50kHz pulse
bsf Faux
movlw 0x10
;49 cycles
movwf d1
dcyc decfsz d1, f
goto dcyc
bcf Faux
; pulse end
; also check for switch being low, if so go to calibrate mode
btfsc btn
goto slp2
incf debounce,f
; if debounce > 250 goto cal_mode.
CFLSB debounce, .250
goto cal_mode
goto slp0
;else debounce = 0;
slp2 clrf debounce
goto slp0
; back to loop
;--------------------------------------------------------------------
; Calibrate mode: make a 1MHz fixed freq out pin Faux
; 5 instructions per output cycle,
; output squarewave 1MHz with 40% on duty.
cal_mode:
; CCP1 module is turned OFF
clrf CCP1CON
; CAL mode LED red is on
LEDRED
loop_1MHz:
bsf Faux
; output high
nop
bcf Faux
; output low
btfsc btn
; test switch for SINE mode
goto done_1MHz ; switch HI,
so exit this mode and go back to SINE
bsf Faux
; output high
nop
bcf Faux
; output low
goto loop_1MHz ; loop
done_1MHz:
; out from cal_mode
goto sine_mode ; cal mode
is done, back to sine mode
;-----------------------------------------------------------------------------
END |
|