Wednesday, 14 November 2012

SPI : Connecting Three Slaves To a Master Microcontroller Using ATmega16

Introduction to SPI:

SPI stands for Serial Peripheral Interface, it allows high-speed synchronous data transfer between the ATmega16 and peripheral devices (or between several AVR devices). It has the following properties:
  • Full-duplex
  • Three-wire Synchronous Data Transfer
  • Master and Slave Operation

How It Works:

  • The Master initiates the communication cycle when pulling low the Slave Select SS pin.
  • Writing a byte to the Master Data Register (Master SPDR) starts the clock on the SCK line.
  • After shifting one byte, the clock stops, and the flag SPIF is set.
  • If the SPI interrupt enable bit (SPIE) is set, an interrupt is requested, and the Master will synchronize the Slave by pulling high the SS line.

Connection:

  1. MOSI (PB5 in ATmega16): Master Output Slave Input. For master micro-controller this pin is output and for slave micro-controller this pin is input.
  2. MISO (PB6 in ATmega16): Master Input Slave Output. For master micro-controller this pin is input and for slave micro-controller this pin is output.
  3. SCK (PB7 in ATmega16): SPI clock. For master micro-controller this pin is output and for slave micro-controller this pin is input.
  4. SS (active low) (PB4 in ATmega16): Slave Select or Slave enable. For master micro-controller this pin is output and for slave micro-controller this pin is input.
The four pins of SPI communication is connected as the above figure.

Connecting More Than One Slave:

You can connect more than one slave SPI module to one Master module. You will have to add a slave select line in the master module for every slave you add as in the following picture:

Testing:

In this test I have connected three SPI modules of ATmega16 as slaves to one master SPI module of ATmega16. I used PB1 as slave enable for slave number 1, PB3 as slave enable for slave number 2 and PB4 as slave enable fo slave number 3. I used 8 DIP switches connected to PORTA for the master and the three slaves to change the data to be sent between them. I used the three external interrupts of ATmega16 to select which slave to send the data to (i.e. to send data from master to slave number one press the button connected to INT0 and so on). The data sent is displayed on PORTC.



The SPI specifications used in the testing:

// SPI initialization
// SPI Clock Rate: 250.000 kHz
// SPI Clock Phase: Cycle Half
// SPI Clock Polarity: Low
// SPI Data Order: MSB First

Sample of the master code:

// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
// Place your code here
PORTB.1=0;     // enable slave no.1
SPDR=PINA;   // read the data from PORTA and send it
}

// External Interrupt 1 service routine
interrupt [EXT_INT1] void ext_int1_isr(void)
{
// Place your code here
PORTB.3=0;   // enable slave no.2
SPDR=PINA;   // read the data from PORTA and send it
}

// External Interrupt 2 service routine
interrupt [EXT_INT2] void ext_int2_isr(void)
{
// Place your code here
PORTB.4=0;   // enable slave no.3
SPDR=PINA;   // read the data from PORTA and send it
}

// SPI interrupt service routine
interrupt [SPI_STC] void spi_isr(void)
{
unsigned char data;
data=SPDR;
// Place your code here
PORTC=data;     // dispaly data on PORTC
PORTB.4=1;PORTB.3=1;PORTB.1=1;  // disable all the slaves
}

// Declare your global variables here

void main(void)
{
PORTA=0x00;
DDRA=0x00;

PORTB=0x1A;
DDRB=0xBA;

PORTC=0x00;
DDRC=0xFF;

// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Rising Edge
// INT1: On
// INT1 Mode: Rising Edge
// INT2: On
// INT2 Mode: Rising Edge
GICR|=0xE0;
MCUCR=0x0F;
MCUCSR=0x40;
GIFR=0xE0;

// SPI initialization
SPCR=0xD0;
SPSR=0x00;

// Clear the SPI interrupt flag
#asm
    in   r30,spsr
    in   r30,spdr
#endasm

// Global enable interrupts
#asm("sei")

while (1)
      {
      // Place your code here
      };
}

Sample of the Slave code:

// SPI interrupt service routine
interrupt [SPI_STC] void spi_isr(void)
{
unsigned char data;
data=SPDR;
// Place your code here
PORTC=data ;
}

void main(void)
{
PORTA=0x00;
DDRA=0x00;

PORTB=0x00;
DDRB=0x40;

PORTC=0x00;
DDRC=0xFF;

// SPI initialization
SPCR=0xC0;
SPSR=0x00;

// Clear the SPI interrupt flag
#asm
    in   r30,spsr
    in   r30,spdr
#endasm

// Global enable interrupts
#asm("sei")

while (1)
      {
      // Place your code here
      SPDR=PINA;
      };
}

You can download the project and simulation files from the following link:
You can download this tutorial in PDF format from the following link:

Friday, 26 October 2012

Solar Tracking System Using ATmega16

Introduction

Extracting useable electricity from the sun was made possible by the discovery of the photoelectric mechanism and subsequent development of the solar cell – a semi conductive material that converts visible light into a direct current. By using solar arrays, a series of solar cells electrically connected, a DC voltage is generated which can be physically used on a load. Solar arrays or panels are being used increasingly in remote areas where placement of electricity lines is not economically viable.

Light gathering is dependent on the angle of incidence of the light source providing power (i.e. the sun) to the solar cell’s surface, and the closer to perpendicular, the greater the power.

Day sunlight will have an angle of incidence close to 90°in the morning and the evening. At such an angle, the light gathering ability of the cell is essentially zero, resulting in no output. As the day progresses to midday, the angle of incidence approaches 0°, causing a steady increase in power until at the point where the light incident on the panel is completely perpendicular, and maximum power is achieved.

From this background, we see the need to maintain the maximum power output from the panel by maintaining an angle of incidence as close to 0° as possible. By tilting the solar panel to continuously face the sun, this can be achieved. This process of sensing and following the position of the sun is known as Solar Tracking.

The Sensing Element And Signal Processing

Many different methods have been proposed and used to track the position of the sun. The simplest of all uses use two LDR– a Light Dependent Resistor separated by a small plate to act as a shield to sunlight, as shown in the next figures.  




The stable position is when the two LDRs having the same light intensity (position A).When morning arrives, The left LDR is turned on(small resistance approximately shorted), causing a signal to turn the motor continuously counterclockwise until the two LDRs having the same light intensity again(position B). As the day slowly progresses (position C) is reached shortly, turning on the LDRs. The motor turns clockwise, and the cycle continues until the end of the day, or until the minimum detectable light level is reached.



The above figures show that when the sun at the right to the solar panel LDR2 has small value resistance and the other LDR has no light (large resistance) and a software in the micro-controller translate this to signals to control the stepper motor to rotate the panel to the left. 

Circuit description

The two LDRs are connected to a bridge and the output of the bridge is connected to a comparator (the analog comparator of the microcontroller is used).

When LDR1 has higher light intensity than LDR2 then the resistance of LDR1 is smaller than that of  LDR2 then voltage at AIN0 is higher than that of AIN1 and the output of comaparator is high.

When LDR2 has higher light intensity than LDR1 then the resistance of LDR1 is larger than that of  LDR2 then voltage at AIN0 is smaller than that of AIN1 and the output of comparator is low.

Then the output of the comparator is used in the UC program to control the stepper motor RV1 variable resistor is used to balance the bridge when the two LDRs having the same light intensities (due to the mismatch between the two LDRs).

The minimum light detectable circuit

It's a circuit used to detect the condition when there is no sunlight to turn off the tracking system
It uses a summing op. amp. Circuit it's output is given by :
Where 1.23V is the internal bandgap reference used by the analog comparator in the UC.
By calculating the value of V(AIN0) and V(AIN1) at sunset and adjusting RV2 the output of the comparator can be used to turn on or turn off the solar tracking system.

Microcontroller program for sun tracking system

// declaration of variables
#include <mega16.h>
#include <delay.h>
char temp ;
unsigned char seq[4]={0x10,0x40,0x20,0x80};
int i= 0;

// check the sum out
      ADCSRA.7=0;   // disable ADC
      SFIOR |= 0x08;   // enable MUX
      ADMUX |= 0x02; ADMUX &= 0xFA;    // selecting PA2
      ACSR.6=1; // select band gap reference
            
      if (ACSR.5==1)       //  no sunlight
        {
        PORTC.3=0;             // disable sun tracking system
        }
      else                      // there is sunlight
        {
        SFIOR &=0xF7; // disable the MUX
        ACSR.6=0;      // disable the band gap ref.
        PORTC.3=1; // enable H-bridge
        if (ACSR.5==1)       //  rotate counter clockwise
        {
        while (ACSR.5==1)
        {
        temp =PIND;
        temp &=0x0F;
        temp |= seq[i];
        PORTD = temp;
        delay_ms(100);
        i=(i+1)%4;
        }
        PORTD &=0x0F;          // stop the motor
        }
        else
        {
        while (ACSR.5==0)     //  rotate clockwise
        {
        i=(i+3)%4;
        temp =PIND;
        temp &=0x0F;
        temp |= seq[i];
        PORTD = temp;
        delay_ms(100);
        }
        PORTD &=0x0F;        // stop the motor
        }
        }


Some notes on the program

  • The sequence of the stepper motor is stored in a temporary variable (temp) because we use only 4 pins of PORTD and may be another outputs or inputs connected to the other pins of PORTD so it's first stored in a temporary variable so that it's not changed when changing the sequence of the stepper motor.
  • The piece of program if written in while (1) (endless loop) the motor will not stop in a certain position as always the output of the comparator will give one of the two states (high or low), so it's better to execute this program once every 20 or 30 minutes according to the motion of the sun path.

YOU CAN DOWNLOAD THE SIMULATION AND THE PROJECT HERE
Download Here
YOU CAN DOWNLOAD THIS TUTORIAL IN PDF HERE
Download Here

Friday, 7 September 2012

Frequency meter (Counter) using ATmega16 or ATmega8


In this tutorial we are going to build simple frequency meter (frequency counter) using ATmega16 and codevision avr compiler. This simple frequency meter (frequency counter) can measure frequency up to 4Mhz (theoretically) because we are using 8Mhz clock for the ATmega16 micro-controller. But you can use an external crystal oscillator of frequency 16Mhz and increase uo the range to 8Mhz.

How it works :

The idea behind the frequency meter is so simple as frequency or Hz means the number of pulses (cycles) in one second. So, This is what we are going to do. We are going to count the number of pulses of a signal in one second and this is simply the frequency of the signal.

In details, we are going to use timer1 of ATmega16 to count the pulses of the signal we are going to measure it's frequency (external pulses on T1 pin2 (PB1)) and use the normal mode. we will start counting the pulses and make a delay of one second then we stop the timer and read it's register (TCNT1) which contains the number of pulses (counts).

But here you are going to ask what if timer1 have made an overflow???
The answer is very simple, We enable the overflow interrupt of timer1 and we count the number of overflows made by timer1 and the overflow means that timer1 has made 2^16 count (65536 counts as it's a 16 bit register) so the number of pulses in one second will be calculated using the following equation :

Frequency = i*2^16 + TCNT1

Where i is the number of overflows in one second.

The reading of the frequency meter is updated every 1 second.

Here is a sample of the code with comments as possible :

#include <mega16.h>

// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x1B ;PORTA
#endasm
#include <lcd.h>  /* the reading is displayed on LCD */
#include <delay.h>
#include <stdlib.h>  /* this library is used to display variables on lcd ( More details ) */

unsigned long int freq;  /* to store value of frequency value */
unsigned int i=0,dur;  /* i=number of overflows in one second */
/* dur to store the value of TCNT1 register */
char buffer[8];
/* array char to store the frequency value as a string to be displayed on lcd ( More details ) */
float freqf;    /* used to display the fractions of frequency with the suitable unit as shown later */

// Timer1 overflow interrupt service routine

interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
i++ ;  // count the number of overflows in one second
}

void main(void)
{
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer1 Stopped
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x04;

// LCD module initialization
lcd_init(16);

#asm("sei")

while (1)
      {                  
      TIMSK=0x04;  // enable overflow interrupt of timer1 
      TCCR1B=0x07;   /* start timer1 with external pulses (T1 rising edge) */
      delay_ms(1000);    // wait for one second
      TCCR1B=0x00;    //stop timer1
      TIMSK=0x00;     //disable interrupt
      dur=TCNT1;       /* store the number of counts in TCNT1 register */
      freq = dur + i*65536; /* calculate the frequency as in previous equation */
      TCNT1=0x0000;  /* clear TCNT1 register for the next reading */
      i=0;     /* clear number of overflows in one second for the next reading */
//////////////////// display ////////////////////////
      lcd_gotoxy(0,0);
      lcd_putsf("freq=");
      lcd_gotoxy(0,1);
      if(freq>=1000000)    /* if frequency more than or equal 1Mhz use "Mhz" on lcd */
      {
      freqf=(float)freq/1000000;  //divide by 1Mhz scale
      ftoa(freqf,3, buffer);
      lcd_puts(buffer); 
      lcd_putsf("MHZ");
      }
      else if (freq>=1000)  /* if frequency more than or equal 1khz use "Khz" on lcd */
      {
      freqf=(float)freq/1000;
      ftoa(freqf,3, buffer);
      lcd_puts(buffer); 
      lcd_putsf("KHZ");
      }
      else   // if frequency less than 1khz use "hz" on lcd
      {
      ltoa(freq, buffer);
      lcd_puts(buffer); 
      lcd_putsf("HZ");
      }    
      };
}

Practical Results :

Actual Frequency
Measured Frequency
Relative error
10 hz
10 hz
0 %
100 hz
97 hz
3 %
1 khz
0.963 khz
3.7 %
10 Khz
9.62 Khz
3.8 %
100 khz
96.1 khz
3.9 %
1 Mhz
0.961 Mhz
3.9 %
3 Mhz
2.88 Mhz
4 %

The max frequency measured theoritically is 4Mhz but in practical it's 3Mhz with max error 4 % .

Here is a short video showing the circuit working :

video

You can download the code and simulation file from the following link :

NEW : Here is the same cicuit of frequency meter but using ATmega8 microcontroller.


You can download the code and simulation files from the following link :
Download Here

You can download this tutorial in PDF from the following link :
Download Here

Tuesday, 28 August 2012

KS0108 GLCD with ATmega16


In this tutorial we are going to explain how to interface the graphical LCD with ATmega16 microcontroller and the compiler used is codevision avr. The GLCD used is 128x64 pixel with KS0108 driver.
Since old compilers of codevision doesn't include library for GLCD. Then you have to download the KS0108 GLCD library and you can download it HERE
This library supports English and Arabic and we are going to explain how to use this library in the following steps.

1-Adding The Library to your codevision compiler :

After downloading the files extract them and go to the folder of your installed codevision avr and default it will be in drive c with folder named cvavr2 >> open it copy the files downloaded in the folder named (inc).

2-including the library in your project and configuring connections :

Make a new project and include the GLCD library by writing #include <glcd.h> and the press build all project so that the library is included to modify the connections as shown :


Now you will find that the library is included in the code navigator tab at your left ( see the arrow ) now press on glcd.h to define the PORTS to be connected with LCD as shown in the next figure :


In line1 : Define the PORT for data ( in example we use PORTA , but it can be replaced by any PORT )
In line2 : As PORTA is selected then write DDRA (direction of dataport)
In line3 : As PORTA is selected then write PINA
Line 4 to 8 define the pins of the control signals of the GLCD, you can choose any pin ( in example we used the 1st 5 pins of PORTB )
The last line #define CS_ACTIVE_LOW is used if the control signals cs1 and cs2 are active low, if they are active high this line is commented.

The last thing is to set the used PORTS in connection as OUTPUT pins :
DATADDR = 0xff;              // define data port as output
DDRB = 0x3f;                     // define control pins as output

The connection of the GLCD is shown in the following figure :


3- How to use the library (functions):

In this step we will explain how to use the library and it's functions :

Method
Description
Parameters
glcd_on()
Turn on the GLCD
No
glcd_off()
Turn off the GLCD
No
set_start_line(unsigned char line)
Changes the top line on the display
line: line number to be set at the top (Range: 0 - 63)
goto_col(unsigned int x)
Go to the specified column
x: Column number
(Range: 0 - 127)
goto_row(unsigned int y)
Go to the specified
row 
y: Row address
(Range: 0 - 7)
goto_xy(unsigned int x, unsigned ,int y)
Go to the specified column and row
x: Column number
y: Row address
glcd_write(unsigned char b)
Writes 1 byte data at the
current location
b: 1 – byte data to be written at the current
location
glcd_clrln(unsigned char ln)
Clears the specified row 
ln: the number of the row to be cleared (Range: 0 - 7)
glcd_clear()
Clears the display
No
glcd_read(unsigned char column)
Reads the byte at the current position
column: Current column number 
point_at(
unsigned int x,
unsigned int y,
byte color)
Adds a point at the specified location
x: column number
y: row number
color: 0 : Light spot
         1 : Dark spot
h_line(
unsigned int x,unsigned int y, byte l,byte s,byte c)
Draws a horizontal line
x: The column number at which the line starts
y: The row number at which the line starts
l: Line length
s: The space between line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1= Dark spots
v_line(
unsigned int x,unsigned int y, signed int l,byte s,byte c)
Draws a vertical line
x: The column number at which the line starts
y: The row number at which the line starts
l: Line length
s: The space between line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
line(unsigned int x1,unsigned int y1, unsigned int x2,unsigned int y2, byte s,byte c)
Draws a general (inclined) line
x1: The column number at which the line starts
y1: The row number at which the line starts
x2: The column number at which the line ends
y2: The row number at which the line ends
s: The space between line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
rectangle(
unsigned int x1,unsigned int y1, unsigned int x2,unsigned int y2,
               byte s,byte c)
Draws a rectangle
x1: The x of the upper left point
y1: The y of the upper left point
x2: The x of the lower right point
y2: The y of the lower right point
s: The space between each line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
cuboid(
unsigned int x11,unsigned int y11, unsigned int x12,unsigned int y12,
unsigned int x21,unsigned int y21,
unsigned int x22,unsigned int y22,
        byte s,byte c)
Draws a cuboid by defining two surfaces
x11: The x of the upper left point of the first surface
y11: The y of the upper left point of the first surface
x12: The x of the lower right point of the first surface
y12: The y of the lower right point of the first surface
X21: The x of the upper left point of the second surface
Y21: The y of the upper left point of the second surface
x22: The x of the lower right point of the second surface
y22: The y of the lower right point of the second surface
s: The space between each line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
h_parallelogram(
unsigned int x1,unsigned int y1,
unsigned int x2,unsigned int y2,
byte l,byte s,byte c)
Draws a parallelogram its upper and lower sides are horizontal
x1: The x of the upper left point
y1: The y of the upper left point
x2: The x of the lower right point
y2: The y of the lower right point
l: The length of the horizontal side (upper or lower)
s: The space between each line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
v_parallelogram(
unsigned int x1,unsigned int y1,
unsigned int x2,unsigned int y2,
 byte l,byte s,byte c)
Draws a parallelogram its right and left sides are vertical
x1: The x of the upper left point
y1: The y of the upper left point
x2: The x of the lower right point
y2: The y of the lower right point
l: The length of the vertical side (right or left)
s: The space between each line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
h_parallelepiped(
unsigned int x11,unsigned int y11,
unsigned int x12,unsigned int y12,
byte l1,
unsigned int x21,unsigned int y21, unsigned int x22,unsigned int y22,
byte l2,
byte s,byte c)
Draws a parallelepiped its bases are two horizontal parallelograms
(Seeh_parallelogram)
x11: The x of the upper left point of the first surface
y11: The y of the upper left point of the first surface
x12: The x of the lower right point of the first surface
y12: The y of the lower right point of the first surface
X21: The x of the upper left point of the second surface
Y21: The y of the upper left point of the second surface
x22: The x of the lower right point of the second surface
y22: The y of the lower right point of the second surface
s: The space between each line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
l1: The length of the horizontal side of the first surface
l2: The length of the horizontal side of the second surface
v_parallelepiped(
unsigned int x11,unsigned int y11,
unsigned int x12,unsigned int y12, byte l1,
unsigned int x21,unsigned int y21,
unsigned int x22,unsigned int y22, byte l2,
byte s,byte c)
Draws a parallelepiped its bases are two vertical parallelograms
(Seev_parallelogram)
x11: The x of the upper left point of the first surface
y11: The y of the upper left point of the first surface
x12: The x of the lower right point of the first surface
y12: The y of the lower right point of the first surface
X21: The x of the upper left point of the second surface
Y21: The y of the upper left point of the second surface
x22: The x of the lower right point of the second surface
y22: The y of the lower right point of the second surface
s: The space between each line points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
l1: The length of the vertical side of the first surface
l2: The length of the vertical side of the second surface
circle(
unsigned int x0,unsigned int y0,
unsigned int r,byte s,byte c)
Draws a circle
x0: The x point of the center of the circle
y0: The y point of the center of the circle
r: Circle’s radius
s: The space between perimeter points:
    0 = solid line
    1 = dotted line
  >1 = dashed line
c:  0 = Light spots
     1 = Dark spots
glcd_putchar(byte c,int x,int y,byte l, byte sz)
Writes a character at the specified position, with size sz
c: The character to be typed
x: The column number to start typing the character at (One character occupies 8 columns)
y: The row number to type the character at
l: The language of the character
   0 = English
   1 = Arabic
sz: Font size (from 1 to 7)
glcd_puts(
byte *c,int x,int y,unsigned char l, byte sz,signed char space)
Writes a string ,stored in the flash memory ,on the display
c: A pointer to the string to be written on the display
x: The column number to start typing the character at (One character occupies 8 columns)
y: The row number to type the character at
l: The language of the character
   0 = English
   1 = Arabic
sz: Font size (from 1 to 7)
space:
English: Character spacing
Arabic and Farsi:Word spacing
bmp_disp(flash byte *bmp,
unsigned int x1,unsigned int y1,
unsigned int x2,unsigned int y2)
Displays a bmp image array stored in the flash memory
bmp: A pointer to the array where the bmp image is stored
x1: The x of the upper left point of the image on the glcd
y1: The y of the upper left point of the image on the glcd
x2: The x of the lower right point of the image on the glcd
y2: The y of the lower right point of the image on the glcd

Examples :

h_line(1,1,20,0,0) à Draws bright horizontal line
h_line(1,1,20,0,1) à Draws dark horizontal line

Writing Arabic and English texts :

#pragma used+
char ar_string[] = "بسم الله الرحمن الرحيم";   
char en_string[] = "Hello";
#pragma used-
glcd_clear();
glcd_puts(ar_string,127,0,1,1,0);//Writes an Arabic string
glcd_puts(en_string,5,5,0,1,0);  //Writes an English(Latin) string

/* note : codevision avr don't support Arabic so when you write Arabic it will appear like this in the editor :"ÈÓã Çááå ÇáÑÍãä ÇáÑÍíã" don't change it */


Special Thanks To Osama's Lab for this great library.
For More details : Osama's Lab : GLCD library

Download the project and it's simulation HERE
Download this tutorial in PDF HERE