AVR-GCC. 7-Segment Displays.

Όπως είδαμε (εδώ) για την απεικόνιση διαφόρων πληροφοριών σε μία εφαρμογή μπορούμε να χρησιμοποιήσουμε μία αλφαριθμητική οθόνη. Πολλές φορές όμως η χρήση μίας τέτοιας οθόνης θα ήταν υπερβολή με αποτέλεσμα το αυξημένο κόστος. Για παράδειγμα σε ένα απλό βολτόμετρο ή σε ένα όργανο ένδειξης θερμοκρασίας. Σε τέτοιες περιπτώσεις μπορούμε να χρησιμοποιήσουμε οθόνες 7 τμημάτων ή 7-segment displays. Στην ουσία κάθε τμήμα είναι ένα LED.

Άρθρο της Wikipedia για τα 7-segment.
Τεκμηρίωση του ATmega8.
Τεκμηρίωση του display (LB-603MP)
Τεκμηρίωση του BC547.

Το κάθε 7-segment display μπορεί να απεικονίσει ένα ψηφίο/χαρακτήρα από τις 127 παραλλαγές που μπορεί να σχηματιστούν με τα 7 τμήματα. Κάθε τέτοιο display έχει συνήθως 9 pins. 7 pins για το κάθε τμήμα, ένα για την απεικόνιση δεκαδικού σημείου (τελεία) και το κοινό pin τροφοδοσίας. Υπάρχουν δύο είδη. Display κοινής καθόδου και κοινής ανόδου.

Εάν θέλουμε να απεικονίσουμε έναν αριθμό με τρία ψηφία θα χρειαστούμε 3 στοιχεία 7 τμημάτων. Θα χρειαστούμε όμως και 3×8=24 pins (μαζί με το δεκαδικό στοιχείο) από τον μικροελεγκτή μας για να τα οδηγήσουμε; Η απάντηση είναι όχι. Θα χρειαστούμε 11 pins. Αν είχαμε 4 7-segmen θα χρειαζόμασταν 12 pins, για 5 θα θέλαμε 13. Γενικά (αριθμός pins για τον έλεγχο) = (αριθμός τμημάτων)+(αριθμός στοιχείων). Θα μπορούσαμε να χρησιμοποιήσουμε και κάποιον αποκωδικοποιητή BCD σε 7 τμήματα (BCD to 7-segment display) όπως το SN7447A ή το HCF4511B για να μειώσουμε ακόμη περισσότερο των αριθμό των pin του μικροελεγκτή.

Το ανθρώπινο μάτι δεν μπορεί να αντιληφθεί πράγματα που αλλάζουν πολύ γρήγορα. Έτσι μπορούμε να ελέγχουμε ένα στοιχείο κάθε φορά και όχι όλα μαζί. Αν το κάνουμε αρκετά γρήγορα το μάτι μας δεν θα καταλάβει την διαφορά. Με τον τρόπο αυτό μειώνουμε τον αριθμό των pin.

Στο παρακάτω παράδειγμα μετράμε την τάση με την βοήθεια του ADC και την απεικονίζουμε στο 7-segment display των τριών στοιχείων. Στην ουσία φτιάξαμε ένα απλό βολτόμετρο. Το συγκεκριμένο display (LB-603MP, κοινής καθόδου) έχει και τα τρία στοιχεία σε ένα πακέτο. Δεν αλλάζει τίποτε από το να ήταν διακριτά.

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

/*Η παρακάτω δήλωση ελέγχει αν στον compiler έχουμε βάλει την παράμετρο F_CPU.
  Αν όχι, την περνά. */

#ifndef F_CPU
    #warning "H F_CPU den exei oristei 8a parei thn timh 4000000"
    #define F_CPU 4000000UL  //  Use unsigned long
#endif

#define digit_1 0
#define digit_2 1
#define digit_3 2

void adc_init(void);
uint16_t adc_read(uint8_t ADC_channel);
void segment(uint8_t digit,uint8_t dp);

int main(void){

    // PD0..PD7 ως έξοδοι.
    DDRD = 0xFF;

    // PB0, PB1, PB2 ως έξοδοι.
    DDRB |= (1<<PB0)|(1<<PB1)|(1<<PB2);

    adc_init();

    uint16_t mvolt;
    uint8_t digit;


    while(1){

        // Μέγιστη τάση 4,9 volts ή 4900 mVolts.
        mvolt = 4900*(adc_read(5)/1023.0);

        /* Η απεικόνιση θα είναι της μορφής x,xx.
           άρα από την mvolt απορρίπτουμε το τελευταίο
           ψηφίο διαιρώντας με το 10.
        */
        mvolt/=10;


        // Το λιγότερο σημαντικό ψηφίο (LSB).
        digit = mvolt%10;
        segment(digit,0);
        // Μάσκα στα 5 msb που δεν χρησιμοποιούμε.
        PORTB=(PORTB&0b11111000)|(1<<digit_3);
        _delay_ms(4);

        digit=(mvolt/10)%10;
        segment(digit,0);
        // Μάσκα στα 5 msb που δεν χρησιμοποιούμε.
        PORTB=(PORTB&0b11111000)|(1<<digit_2);
        _delay_ms(4);

        // Το περισσότερο σημαντικό ψηφίο (MSB).
        digit=(mvolt/100)%10;
        // Ενεργοποιούμε και την υποδιαστολή (dp=1).
        segment(digit,1);
        // Μάσκα στα 5 msb που δεν χρησιμοποιούμε.
        PORTB=(PORTB&0b11111000)|(1<<digit_1);
        _delay_ms(4);

    };

    return 0;
}

void segment(uint8_t digit, uint8_t dp){

/*
              a(PD0)
            __________
           /         /
 f(PD5)   /         / b(PD1)
         /_________/
        /  g(PD6) /
 e(PD4)/         / c(PD2)
      /_________/  # dp(PD7)
        d(PD3)

    Έστω πως θέλουμε το ψηφίο 4. Πρέπει να
    ανάψουν τα τμήματα b,c,f,g. Ή να ενεργοποιηθούν
    τα PD1,PD2,PD5,PD6. Ή PORTD = 0b01100110.

*/

    switch (digit) {

        case 0:
            PORTD=0b00111111|(dp<<7);
            break;

        case 1:
            PORTD=0b00000110|(dp<<7);
            break;

        case 2:
            PORTD=0b01011011|(dp<<7);
            break;

        case 3:
            PORTD=0b01001111|(dp<<7);
            break;

        case 4:
            PORTD=0b01100110|(dp<<7);
            break;

        case 5:
            PORTD=0b01101101|(dp<<7);
            break;

        case 6:
            PORTD=0b01111101|(dp<<7);
            break;

        case 7:
            PORTD=0b00000111|(dp<<7);
            break;

        case 8:
            PORTD=0b01111111|(dp<<7);
            break;

        case 9:
            PORTD=0b01101111|(dp<<7);
            break;

            };
}

void adc_init(){

    ADMUX |= (1<<REFS0);              // AVCC with external capacitor at AREF pin.

    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);  // Prescaler 128 (16 MHz/64=125KHz).

    ADCSRA |= (1<<ADEN);              // Enable ADC.

    ADCSRA |= (1<<ADSC);              // Start ADC conversion

}

uint16_t adc_read(uint8_t ADC_channel){

    int16_t adc_val;


    ADMUX = (ADMUX & 0xF0) | (ADC_channel & 0x0F);

    ADCSRA |= (1<<ADSC);

    while( ADCSRA & (1<<ADSC) );// wait until ADC conversion is complete

    adc_val = ADC;

    return adc_val;
}

Και ένα απλό θερμόμετρο σύμφωνα με αυτά που είχαμε δει εδώ.


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


/*Η παρακάτω δήλωση ελέγχει αν στον compiler έχουμε βάλει την παράμετρο F_CPU.
  Αν όχι, την περνά. */

#ifndef F_CPU
    #warning "H F_CPU den exei oristei 8a parei thn timh 4000000"
    #define F_CPU 4000000UL  //  Use unsigned long
#endif


// Η ακριβής τιμή της R2.
#define R2 9918.0
// Τιμή του NTC στους 25 βαθμούς C.
#define Rn 10000.0

/*
    Vcc
     |
     |
     /
     \
     /  NTC_Res
     \
     /
     |
     |--------ADC_in
     |
     /
     \
     /  R2
     \
     /
     |
     |
    GND

    NTC_Res = (1023*R2)/ADC - R2

*/


// A, B, C και D σταθερές του NTC.
#define A 0.003354016
#define B 0.000256985
#define C 0.000002620131
#define D 0.00000006383091


#define digit_1 0
#define digit_2 1
#define digit_3 2

void adc_init(void);
uint16_t adc_read(uint8_t ADC_channel);
void segment(uint8_t digit,uint8_t dp);
double adc2temp(int);

int main(void){

    // PD0..PD7 ως έξοδοι.
    DDRD = 0xFF;

    // PB0, PB1, PB2 ως έξοδοι.
    DDRB |= (1<<PB0)|(1<<PB1)|(1<<PB2);

    adc_init();

    int16_t temp=0;
    uint8_t digit,i=0,j=0,sign=0,dp_toggle=1;


    while(1){

        if (i==0){
            // Μετατροπή θερμοκρασίας από double σε int.

            temp=(int)10.0*adc2temp(adc_read(5));

            /* 
               Αν έχουμε αρνητική θερμοκρασία
               θέτουμε την sign=1 και αλλάζουμε
               το πρόσημο στην temp.
            */
            if (temp<0) {
                temp=temp*(-1);
                sign=1;
            } else {
                sign=0;
                dp_toggle=1;
            };


            // Η απεικόνιση θα είναι της μορφής xx,x.

        };

        /* 
           Δεν παίρνουμε τιμή συνεχώς από τον ADC.
           Μόνο όποτε η i γίνετε 0. Ή κάθε ~500 msec
           περίπου. Για να μην αλλάζει πολύ γρήγορα
           η απεικόνιση.
        */
        i++;
        if (i==50) i=0;


        /*
           Δεν έχουμε το σύμβολο του μείον (-) στο display
           οπότε αναβοσβήνουμε το δεκαδικό σημείο για 
           αρνητικές θερμοκρασίες.
        */
        j++;
        if (j==50) {
            if (sign==1){dp_toggle^=1;}
            j=0;
        };


        // Το λιγότερο σημαντικό ψηφίο (LSB).
        digit = temp%10;
        segment(digit,0);
        // Μάσκα στα 5 msb που δεν χρησιμοποιούμε.
        PORTB=(PORTB&0b11111000)|(1<<digit_3);
        _delay_ms(4);

        digit=(temp/10)%10;
         // Ενεργοποιούμε και την υποδιαστολή (dp=1).
        segment(digit,dp_toggle);
        // Μάσκα στα 5 msb που δεν χρησιμοποιούμε.
        PORTB=(PORTB&0b11111000)|(1<<digit_2);
        _delay_ms(4);

        // Το περισσότερο σημαντικό ψηφίο (MSB).
        digit=(temp/100)%10;
        segment(digit,0);
        // Μάσκα στα 5 msb που δεν χρησιμοποιούμε.
        PORTB=(PORTB&0b11111000)|(1<<digit_1);
        _delay_ms(4);

    };

    return 0;
}

void segment(uint8_t digit, uint8_t dp){

/*
              a(PD0)
            __________
           /         /
 f(PD5)   /         / b(PD1)
         /_________/
        /  g(PD6) /
 e(PD4)/         / c(PD2)
      /_________/  # dp(PD7)
        d(PD3)

    Έστω πως θέλουμε το ψηφίο 4. Πρέπει να
    ανάψουν τα τμήματα b,c,f,g. Ή να ενεργοποιηθούν
    τα PD1,PD2,PD5,PD6. Ή PORTD = 0b01100110.

*/

    switch (digit) {

        case 0:
            PORTD=0b00111111|(dp<<7);
            break;

        case 1:
            PORTD=0b00000110|(dp<<7);
            break;

        case 2:
            PORTD=0b01011011|(dp<<7);
            break;

        case 3:
            PORTD=0b01001111|(dp<<7);
            break;

        case 4:
            PORTD=0b01100110|(dp<<7);
            break;

        case 5:
            PORTD=0b01101101|(dp<<7);
            break;

        case 6:
            PORTD=0b01111101|(dp<<7);
            break;

        case 7:
            PORTD=0b00000111|(dp<<7);
            break;

        case 8:
            PORTD=0b01111111|(dp<<7);
            break;

        case 9:
            PORTD=0b01101111|(dp<<7);
            break;

            };

}

// Συνάρτηση μετατροπής τις τιμής το ADC σε βαθμούς Κελσίου.
double adc2temp(int RawADC) {

    double NTC_Res, celsius;
    double tmp;                 // Προσωρινή μεταβλητή.

    NTC_Res=((1023 * R2 / RawADC) - R2);

    tmp = log(NTC_Res/Rn);

    tmp = (A+(B*tmp)+(C*tmp*tmp)+(D*tmp*tmp*tmp));

    celsius = (1.0/tmp)-273;    // Μετατροπή βαθμών Κέλβιν σε Κελσίου.


    return celsius;
}


void adc_init(){

    ADMUX |= (1<<REFS0);              // AVCC with external capacitor at AREF pin.

    ADCSRA |= (1<<ADPS1)|(1<<ADPS0);  // Prescaler 128 (16 MHz/64=125KHz).

    ADCSRA |= (1<<ADEN);              // Enable ADC.

    ADCSRA |= (1<<ADSC);              // Start ADC conversion

}

uint16_t adc_read(uint8_t ADC_channel){

    int16_t adc_val;


    ADMUX = (ADMUX & 0xF0) | (ADC_channel & 0x0F);

    ADCSRA |= (1<<ADSC);

    while( ADCSRA & (1<<ADSC) );// wait until ADC conversion is complete

    adc_val = ADC;

    return adc_val;
}
Advertisements
This entry was posted in AVR, Electronics and tagged , , , , , , , , . Bookmark the permalink.

2 Responses to AVR-GCC. 7-Segment Displays.

  1. Gun says:

    please help my, using lm35dz with seven segmen… 00.1 celcius

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