Ημιτονική κυματομορφή με την χρήση PWM.

Για ένα project που δουλεύω τις τελευταίες μέρες εκμεταλλεύομαι την παραγωγή ημιτονικού σήματος με την χρήση της εξόδου PWM ενός AVR (ATmega8) μικροελεγκτή. Πως μπορεί να γίνει κάτι τέτοιο; Ο ποιο εύκολος τρόπος είναι να “κρύψω” ένα ημιτονικό σήμα μέσα στη PWM. Η λογική είναι πολύ απλή. Απλά μεταβάλουμε το εύρος της PWM (Pulse Width=εύρος Modulation) με ημιτονικό τρόπο.

pwm2sine

Τεκμηρίωση του ATmega8.
Τα αρχεία με τους κώδικες ΕΔΩ.

Στο παραπάνω βίντεο βλέπουμε την γραμμική μεταβολή του εύρους της PWM εξόδου από 0% μέχρι 100% (0~255). Ο κώδικας είναι πολύ απλός. Αρχικά ενεργοποιούμε (συνάρτηση pwm_init) τον Timer/counter1 σε λειτουργία Fast PWM με TOP το 255 (mode 5, σελ 96~100 ρης τεκμηρίωσης), Prescaler 1 και Set OC1A/OC1B on Compare Match, clear OC1A/OC1B at
BOTTOM, (inverting mode)”. Το τελευταίο το θέλουμε για να έχουμε 0 volts στο 0% της (OCR1A=255) PWM. Στην πραγματικότητα δεν έχουμε PWM από 0 μέχρι 5 volts από 0% μέχρι 100% σε Fast PWM. Μπορούμε να επιλέξουμε να έχουμε σχεδόν 0 Volts ή σχεδόν 5 volts. Αυτό ορίζεται από τα bit COM1A0, COM1A1 του καταχωρητή TCCR1A. Έτσι για OCR1A=255 έχουμε 0 (ground) volts και για OCR1A=0 έχουμε σχεδόν 5 volts. Στην παρακάτω εικόνα βλέπουμε πως και στο 100% έχουμε μία αιχμή στο 0. Στις περισσότερες εφαρμογές είναι επιθυμητό το 0 volts στο 0%. Σελίδα 90 της τεκμηρίωσης του ATmega8.

100pwm

Η συχνότητα της PWM (Fast PWM) θα είναι 31 kHz περίπου (31,250). Για συχνότητα λειτουργίας του μικροελεγκτή τα 8 MHz. Δίνεται από τον παρακάτω τύπο.

form_5

#include <avr/io.h>
#include <util/delay.h>

void pwm_init(void);

int main(void){

    pwm_init();

    uint16_t i=0;

    while(1){
        // Σταδιακή μείωση του OCR1A.
        while (i<=255) {
            OCR1A=0xFF-i;
            _delay_ms(20);
            i++;
        };

        i=0;
    };

    return 0;
}

void pwm_init(void){

    DDRB |= (1<<PB1);

    OCR1A=0xFF;
    TCCR1B |= (1<<WGM12)|(1<<CS10);
    TCCR1A |= (1<<COM1A0)|(1<<COM1A1)|(1<<WGM10);
}

Όπως είπαμε πρέπει να μεταβάλουμε ημιτονικά το εύρος της PWM (τιμή του OCR1A). O ποιο απλός τρόπος είναι να έχουμε έναν πίνακα με τις τιμές που πρέπει να πάρει ο καταχωρητής OCR1A. Έφτιαξα πρόχειρα ένα python script για τον σκοπό αυτό. Το script δέχεται τέσσερα ορίσματα (τα τρία πρώτα είναι υποχρεωτικά). Τις μοίρες του ημίτονου που θέλουμε (0-360), το πλήθος των σημείων με τα οποία θέλουμε να φτιάξουμε το ημίτονο (συνήθως όχι κάτω από 32 και όχι πάνω από την τιμή TOP της PWM), την τιμή TOP της PWM και τέλος την τιμή (μικρότερη ή ίση του TOP της PWM) που θέλουμε να έχει το πλάτος (peak-to-peak) της παραγόμενης κυματομορφής. Αν δεν δώσουμε τέταρτο όρισμα το πλάτος της κυματομορφής θα πάρει την τιμή TOP. Αν έχουμε εγκατεστημένα και τα πακέτα scipy, numpy και matplotlib το script θα εμφανίσει και μία γραφική παράσταση της PWM με το ημίτονο.

pwm2sine pwm2sine

pwm2sine pwm2sine

pwm2sine

Στην τέταρτη εικόνα που έχουμε λίγα σημεία (16) βλέπουμε πως η κυματομορφή δεν είναι ομαλή. Εμφανίζεται παραμορφωμένη. Στην πέμπτη εικόνα έχουμε δώσει και όρισμα για το πλάτος της κυματομορφής.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>

#define POINTS 128

static const uint8_t sine[128] PROGMEM =
{
        0x80, 0x86, 0x8c, 0x93, 0x99, 0x9f, 0xa5, 0xab, 0xb1, 0xb6, 0xbc, 0xc1, 0xc7, 0xcc, 0xd1, 0xd5, 
        0xda, 0xde, 0xe2, 0xe6, 0xea, 0xed, 0xf0, 0xf3, 0xf5, 0xf8, 0xfa, 0xfb, 0xfd, 0xfe, 0xfe, 0xff, 
        0xff, 0xff, 0xfe, 0xfe, 0xfd, 0xfb, 0xfa, 0xf8, 0xf5, 0xf3, 0xf0, 0xed, 0xea, 0xe6, 0xe2, 0xde, 
        0xda, 0xd5, 0xd1, 0xcc, 0xc7, 0xc1, 0xbc, 0xb6, 0xb1, 0xab, 0xa5, 0x9f, 0x99, 0x93, 0x8c, 0x86, 
        0x80, 0x7a, 0x74, 0x6d, 0x67, 0x61, 0x5b, 0x55, 0x4f, 0x4a, 0x44, 0x3f, 0x39, 0x34, 0x2f, 0x2b, 
        0x26, 0x22, 0x1e, 0x1a, 0x16, 0x13, 0x10, 0x0d, 0x0b, 0x08, 0x06, 0x05, 0x03, 0x02, 0x02, 0x01, 
        0x01, 0x01, 0x02, 0x02, 0x03, 0x05, 0x06, 0x08, 0x0b, 0x0d, 0x10, 0x13, 0x16, 0x1a, 0x1e, 0x22, 
        0x26, 0x2b, 0x2f, 0x34, 0x39, 0x3f, 0x44, 0x4a, 0x4f, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x74, 0x7a, 
};

void pwm_init(void);

int main(void){

    pwm_init();

    uint16_t i=0;

    while(1){


        while (i<=POINTS-1) {
	  
            OCR1A=0xFF-pgm_read_byte(&sine[i]);
            _delay_us(78);
            i++;
        };

        i=0;
    };

    return 0;
}

void pwm_init(void){

    DDRB |= (1<<PB1);

    OCR1A=0xFF;

    TCCR1B |= (1<<WGM12)|(1<<CS10);
    TCCR1A |= (1<<COM1A0)|(1<<COM1A1)|(1<<WGM10);
}

Στον παραπάνω κώδικα η διαφορά είναι πως την τιμή του OCR1A την διαβάζουμε από τον πίνακα που δημιουργήσαμε για την ημιτονική κυματομορφή. Στην γραμμή 33 βλέπουμε μία καθυστέρηση _delay_us(78). Αλλάζοντας τον χρόνο αυτό σε (usec) αλλάζουμε την συχνότητα του ημιτονικού σήματος που παράγουμε. Έστω πως θέλουμε να έχουμε συχνότητα 100 Hz. Το σήμα μας αποτελείτε από 128 σημεία. Τα 100 Hz έχουν περίοδο 1/100=0,01 sec. Πρέπει επομένως να “προβάλουμε” σε 0,01 δευτερόλεπτα 128 σημεία. Άρα κάθε σημείο πρέπει να έχει διάρκεια 0,01/128=0.000078125 ή 78 usec περίπου. Πρέπει να “παίξουμε” λίγο με την τιμή αυτή για να πάρουμε ακριβώς 100 Hz αφού και η υπόλοιπες εντολές απαιτούν κάποιο χρόνο εκτέλεσης. Στο συγκεκριμένο παράδειγμα για ακριβώς 100 έπρεπε να βάλω 76.6 usec. Πρέπει να τονίσουμε πως δεν μπορούμε να πάρουμε πολύ μεγάλες τιμές συχνότητας (ίσως 1~2 kHz με συχνότητα PWM 32 kHz). Όσο πλησιάζουμε την συχνότητα της PWM θα έχουμε παραμορφώσεις, μείωση πλάτους κ.α. Συνδέουμε τον παλμογράφο στην έξοδο PWM (pin PB1) και παρατηρούμε την κυματομορφή (παρακάτω βίντεο).

Που είναι η ημιτονική κυματομορφή; Όπως είπαμε είναι κρυμμένη μέσα στην PWM. Για να πάρουμε την κυματομορφή μας πρέπει να φιλτράρουμε την PWM. Αυτό θα το κάνουμε με ένα low pass RC φίλτρο. Θέλουμε να φιλτράρουμε τα 31 kHz της PWM και να πάρουμε τα 100 Hz του ημιτόνου. Η συχνότητα αποκοπής ενός τέτοιου φίλτρου δίνεται από την σχέση Fc=1/(2πRC). Αν επιλέξουμε R=1000 Ohm και C=100 nF θα έχουμε συχνότητα αποκοπής τα 1591 Hz περίπου. Αρκετά για να “κόψουν” τα 31 kHz της PWM και να επιτρέψουν την διέλευση των 100 Hz του ημίτονου.

RC filter

Στο παρακάτω βίντεο βλέπουμε την έξοδο του κυκλώματος μετά από το φιλτράρισμα της PWM. Όπως βλέπουμε αρχικά η ημιτονική τάση δεν είναι και τόσο καθαρή. Προσθέτουμε λοιπόν και ένα δεύτερο ίδιο RC δικτύωμα. Η δεύτερη ημιτονική τάση που εμφανίζεται είναι αποτέλεσμα του “διπλού φιλτραρίσματος”. Είναι πολύ ποιο καθαρή και ομαλή. Επίσης βλέπουμε πως η συχνότητα είναι σταθερή στα 100 Hz.

Το ίδιο φιλτράρισμα στο LTSpice.

LTSpice1 LTSpice2

schematic

Με τον ίδιο τρόπο μπορούμε να δημιουργήσουμε και άλλων ειδών κυματομορφές. Στην ουσία έχουμε κατασκευάσει ένα DDS (Direct Digital Synthesizer).

DSC01544

Advertisements
This entry was posted in AVR, Electronics and tagged , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s