Translate

Monday, 11 January 2016

Measuring angular displacement using rotary potentiometer

                         Alright, in the previous post we discussed about interfacing a character LCD. Now lets pair it with the built in analog to digital converter (ADC) of an atmega controller. We will a rotary potentiometer to measure the angular displacement.... Its similar to how a servo measures its angle. The process is simple as shown below




Before we proceed please ensure that you know how to built a power supply and a basic LCD interface.... Also a little bit knowledge on ADC operation is required.

Things you need

ADC

The atmega 8,16 and 32 use a 10bit successive approximation ADC. The values range from 0 - 1023 (10bit -> 1024). The atmega has three reference sources but it can use only one at a time Here the reference voltage is set to 5V to keep things simple. I have not discussed the topics on frequency and prescalar,the data sheet contains more details, you can look it up

CIRCUIT DIAGRAM

Alright heres the circuit diagram. As you can see it is similar to that of the LCD post but with the addition of the potentiometer



CODE



         Here is a simple code... this code has a drawback which I will address next


////////////////////////////////////////////////////Code for ADC readout//////////////////////////////////////////////////////

/*
* Angular_displacement_using_atmega.cpp
*
* Created: 8/1/2016 8:35:28 PM
* Author: Hemanth
*/

#ifndef F_CPU
#define F_CPU 1000000UL // CPU frequency
#endif

#define LCD_DATA PORTC
#define control PORTB
#define rs PB2
#define rw PB1
#define en PB0

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

void LCD_cmd(unsigned char cmd);
void init_LCD(void);
void LCD_write(unsigned char data);
void stringwrite(const char *q);
void moveto(int line,int pos);
void clearLCD();
void InitialiseADC(char Aref,int prescalar);
int ADC_readout(uint8_t ch);

void InitialiseADC(char Aref,int prescalar)
{
if (Aref=='I')
{ADMUX=(1<<REFS0)|(1<<REFS1);}
else if(Aref=='V')
{ADMUX=(1<<REFS0)|(0<<REFS1);}
else if(Aref=='R')
{ADMUX=(0<<REFS0)|(0<<REFS1);}
if (prescalar==2)
{ADCSRA=(1<<ADEN)|(0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0);}
else if (prescalar==4)
{ADCSRA=(1<<ADEN)|(0<<ADPS2)|(1<<ADPS1)|(0<<ADPS0);}
else if (prescalar==8)
{ADCSRA=(1<<ADEN)|(0<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);}
else if (prescalar==16)
{ADCSRA=(1<<ADEN)|(1<<ADPS2)|(0<<ADPS1)|(0<<ADPS0);}
else if (prescalar==32)
{ADCSRA=(1<<ADEN)|(1<<ADPS2)|(0<<ADPS1)|(1<<ADPS0);}
else if (prescalar==64)
{ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0);}
else if (prescalar==128)
{ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);}
}

int ADC_readout(int channel)
{
channel=channel&0b00000111;//selecting single ended channel refer data sheet for details
ADMUX|=channel;
ADCSRA|=(1<<ADSC);
while(!(ADCSRA & (1<<ADIF)));
ADCSRA|=(1<<ADIF);
return(ADC);
}

void clearLCD()

{ LCD_cmd(0x01);}

void init_LCD(void)
{
LCD_cmd(0x38);// initialization of 16X2 LCD in 8bit mode
_delay_ms(10);
LCD_cmd(0x01); // clear LCD
_delay_ms(10);
LCD_cmd(0x0C); // cursor ON
_delay_ms(10);
LCD_cmd(0x80);//8 go to first line and0 is for 0th position
}

void LCD_cmd(unsigned char cmd)
{LCD_DATA=cmd;
control |=(1<<en); // RS and RW as LOW and EN as HIGH
control &=~(1<<rs);
control &=~(1<<rw);
_delay_ms(10);
control &=~(1<<rs);
control &=~(1<<rw);
control &=~(1<<en); // RS, RW , LOW and EN as LOW
_delay_ms(10);
}

void LCD_write(unsigned char data)
{
LCD_DATA= data;
control |= (1<<rs)|(1<<en); // RW as LOW and RS, EN as HIGH
control &=~(1<<rw);
_delay_ms(10);
control |= (1<<rs); // EN and RW as LOW and RS HIGH
control &=~(1<<rw);
control &=~(1<<en);
_delay_ms(10); // delay to get things executed
return ;
}

void stringwrite(const char *S)
{
while (*S) {
LCD_write(*S++);
}
}

void moveto(int line,int pos)
{
if(line==1)
LCD_cmd(0x80+pos);
if(line==2)
LCD_cmd(0xC0+pos);
if(line==3)
LCD_cmd(0x90+4+pos);
if(line==4)
LCD_cmd(0xD0+4+pos);
}

char buffer[5];

int main(void)
{
int DATA =0;
MCUCSR|=(1<<JTD);//Disable JTAG
MCUCSR|=(1<<JTD);//Have to send it twice
DDRA=0x00;
DDRB=0xFF;
DDRC=0xFF;
DDRD=0xFF;
PORTA=0x00;
PORTB=0X00;
PORTC=0X00;
PORTD=0x00;
PORTD|=(1<<PD7);
init_LCD();
InitialiseADC('V',4);
while(1)
{
moveto(1,0);
stringwrite("ADC Value :");
DATA=ADC_readout(4);
itoa(DATA,buffer,10);
moveto(1,12);
stringwrite(buffer);
}
}
now when you run this code the voltage entering to the adc pin woul be converted to an accurate digital number. heres a little math

referance voltage = 5000mv
ADC resolution = 2^10 bit (1024)

step value = (5000/1024) = 4.88

which means for every 4.88mv increase the ADC would increment by one

example

if the input to the pin is 2.35V the ADC would read out

(2350/4.88) ~  481

the output will always be in the integer format

The drawback

     so did u guys notice the display when moving from a higher to lower value say from 998 to 25....... your display would read as 258..... the 8 being the 8 from the 998 which was not erased from the LCD memory.... the next code has this covered.....you can write it out with a blank space like so

moveto (1,12);
stringwrite ("    ")//four blank spaces
moveto(1,12)// back to the 1000th place
stringwrite (buffer);

Alright so we got the ADC value how do we convert it to degrees?..... well simple math.... and this is the same procedure for distance or temperature sensors which provides an analog output

here we have a potentiometer with the capability of 0 degrees to 270 degrees

and as you learnt from the previous code (try it on an MCU before proceeding) 0 to 270 corresponds to 0 to 1023... so we just have to derive a simple relation...as such

270 * X = 1023

so X = (1023/270) i.e 3.78

so the angle would be the (ADC reading/3.78)... simple.....any doubts comment below

Heres the final board


here's the code

please note for greater accuracy floating variables must be used.. this code is for referance and simple projects only... no error correction methods have been implemented....In theory the rotary potentiometer is assumed as linear... however that is not always practical.

And here's another note if u plan on using more than 1 potentiometer the ADC must be initailsed again and the next channel selected... The first conversion must be discarded

Example

initialiseADC('V',32);// set ADC referance voltage to vcc i.e 5V with a prescalar of 32
DATA1=ADC_readout(2);//discard first conversion
DATA1=ADC_readout(2);
initialiseADC('I',8); // set ADC referance voltage to internal i.e 2.56V with a prescalar of 8
DATA2=ADC_readout(5);//discard first conversion
DATA2=ADC_readout(5);

////////////////////////////////////////////////////Code for angular readout//////////////////////////////////////////////////////
  
/*
* Angular_displacement_using_atmega.cpp
*
* Created: 8/1/2016 8:35:28 PM
* Author: Hemanth
*/

#ifndef F_CPU
#define F_CPU 1000000UL // CPU frequency
#endif

#define LCD_DATA PORTC
#define control PORTB
#define rs PB2
#define rw PB1
#define en PB0

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

void LCD_cmd(unsigned char cmd);
void init_LCD(void);
void LCD_write(unsigned char data);
void stringwrite(const char *q);
void moveto(int line,int pos);
void clearLCD();
void InitialiseADC(char Aref,int prescalar);
int ADC_readout(uint8_t ch);

void InitialiseADC(char Aref,int prescalar)
{
    if (Aref=='I')
    {ADMUX=(1<<REFS0)|(1<<REFS1);}
    else if(Aref=='V')
    {ADMUX=(1<<REFS0)|(0<<REFS1);}
    else if(Aref=='R')
    {ADMUX=(0<<REFS0)|(0<<REFS1);}
    if (prescalar==2)
    {ADCSRA=(1<<ADEN)|(0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0);}
    else if (prescalar==4)
    {ADCSRA=(1<<ADEN)|(0<<ADPS2)|(1<<ADPS1)|(0<<ADPS0);}
    else if (prescalar==8)
    {ADCSRA=(1<<ADEN)|(0<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);}
    else if (prescalar==16)
    {ADCSRA=(1<<ADEN)|(1<<ADPS2)|(0<<ADPS1)|(0<<ADPS0);}
    else if (prescalar==32)
    {ADCSRA=(1<<ADEN)|(1<<ADPS2)|(0<<ADPS1)|(1<<ADPS0);}
    else if (prescalar==64)
    {ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0);}
    else if (prescalar==128)
    {ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);}
}

int ADC_readout(int channel)
{
    channel=channel&0b00000111;//selecting single ended channel refer data sheet for details
    ADMUX|=channel;
    ADCSRA|=(1<<ADSC);
    while(!(ADCSRA & (1<<ADIF)));
    ADCSRA|=(1<<ADIF);
    return(ADC);
}

void clearLCD()

{ LCD_cmd(0x01);}


void init_LCD(void)
{
    LCD_cmd(0x38);// initialization of 16X2 LCD in 8bit mode
    _delay_ms(1);
    LCD_cmd(0x01); // clear LCD
    _delay_ms(10);
    LCD_cmd(0x0C); // cursor ON
    _delay_ms(1);
    LCD_cmd(0x80);//8 go to first line and0 is for 0th position
}

void LCD_cmd(unsigned char cmd)
{LCD_DATA=cmd;
    control |=(1<<en); // RS and RW as LOW and EN as HIGH
    control &=~(1<<rs);
    control &=~(1<<rw);
    _delay_ms(1);
    control &=~(1<<rs);
    control &=~(1<<rw);
    control &=~(1<<en); // RS, RW , LOW and EN as LOW
    _delay_ms(1);
}

void LCD_write(unsigned char data)
{
    LCD_DATA= data;
    control |= (1<<rs)|(1<<en); // RW as LOW and RS, EN as HIGH
    control &=~(1<<rw);
    _delay_ms(1);
    control |= (1<<rs); // EN and RW as LOW and RS HIGH
    control &=~(1<<rw);
    control &=~(1<<en);
    _delay_ms(1); // delay to get things executed
    return ;
}

void stringwrite(const char *S)
{
    while (*S) {
        LCD_write(*S++);
    }
}

void moveto(int line,int pos)
{
    if(line==1)
    LCD_cmd(0x80+pos);
    if(line==2)
    LCD_cmd(0xC0+pos);
    if(line==3)
    LCD_cmd(0x90+4+pos);
    if(line==4)
    LCD_cmd(0xD0+4+pos);
}

char buffer[5];

int main(void)
{
    int DATA =0;
    int ANGLE =0;
   
    MCUCSR|=(1<<JTD);//Disable JTAG
    MCUCSR|=(1<<JTD);//Have to send it twice
    DDRA=0x00;
    DDRB=0xFF;
    DDRC=0xFF;
    DDRD=0xFF;
    PORTA=0x00;
    PORTB=0X00;
    PORTC=0X00;
    PORTD=0x00;
    PORTD|=(1<<PD7);
    init_LCD();
    InitialiseADC('V',4);
    moveto(1,0);
    stringwrite("-----Angular----");
    moveto(2,0);
    stringwrite("measuring device");
    _delay_ms(2000);
    clearLCD();
    moveto(1,0);
    stringwrite("ADC Value :");
    moveto(2,0);
    stringwrite("Angle :   degree");
    InitialiseADC('V',4);
    while(1)
    { DATA=ADC_readout(4);
       
        moveto(2,7);
        stringwrite("   ");// to clear previous value
        moveto(1,12);
        stringwrite("     ");// to clear previous value
        itoa(DATA,buffer,10);
        moveto(1,12);
        stringwrite(buffer);
        ANGLE=DATA/3.78;
        itoa(ANGLE,buffer,10);
        moveto(2,7);
        stringwrite(buffer);
        _delay_ms(200);//delay to reduce flickering
    }
}

--------------------------------------------------------------------------------------------------------------------------

So thats it , hope this helps ...... comment if any doubts



Wednesday, 6 January 2016

Driving Character LCD using Atmega in 8-bit mode

                 So, ever wanted to make a simple message display. well here it is. this article will walk you through the simple steps of getting the LCD working. Before proceeding you must know how to use the ports on the MCU. A lot of tutorials are available online so I decided to jump to the fun stuff.

The image below is a simple message display


It consists of a

  • Charecter LCD
  • MCU (atmega 16)
  • 10k pullup resistor for reset
  • 10k variable resistor for contrast adjustment

here is the circuit diagram

NOTE: I have not used a reset button or an external crystal (x1) on the prototype board. you may use it if you wish as the next tutorials will require it


16X4


The 16 X 4 LCD has 16 columns and 4 rows. The LCD displays two screens with a delay of 2000 milli seconds or two seconds


16X2


The 16 X 2 LCD has 16 columns and 2 rows. The LCD displays two screens with a delay of 2000 milli seconds or two seconds. Note that the same code is used for both LCDs the 16 X 2 ignores line 3 and 4. well technically it is in the memory and can be forced to display but that's beyond the scope of this article.

The LCD has 16 pins the table below tabulates them


Pin number
Pin name
Discription
1
Gnd
Common ground
2
Vcc
5.5V max (check datasheet)
3
VE
Voltage for contrast adjustment use 10k pot
4
RS
Register select; Low for control; High for data
5
RW
Read or write; High for read; Low for write
6
EN
Enable pulse triggers on falling edge (high to low)
7 - 14
D0 - D7
8 bit data bus; 
15
+ LED
LED positive 5V max
16
- LED
Connect to common ground

 So to get the LCD started follow these steps
  • Ensure that  the vcc is powered to +5v and all ground pins are connected together for both the controller and the LCD. 

  •  before programming the chip. power on the circuite and adjust the contrast till you see black squares on the first line of the LCD

  • Now program the chip

  • The LCD will display as shown in the figures above

Possible problems you may encounter

If the LCD is not displaying anything even the black squares before flashing the chip.

  • Check the contrast.
  • Check the voltage and ground connections  

If the LCD is not initialising (i.e black squares not disappering)

  • Check data lines and control lines for shorts.
  • Ensure that the DDR (data direction register) for the data lines and control lines are set as output
  • Ensure that the F_CPU matches the clock speed of the chip. new chips have a frequency of 1 MHZ so F_CPU must be '1000000UL'. this is the value in hertz. for different frequencies change accordingly.
  • Atmega 16 and 32 have the JTAG unit enabled hence portc cannot function as a normal IO port ensure that the line MCUCSR|=(1<<JTD); is typed twice.

If the LCD displays random/junk characters

  • One or more data lines may not be soldered properly
  • Ensure that the F_CPU matches the clock speed of the chip. new chips have a frequency of 1 MHZ so F_CPU must be '1000000UL'. this is the value in hertz. for different frequencies change accordingly.

        So here it is the code to drive the LCD. this code was written in atmel studio.

  • just create a new executable project (c/c++). 
  • Select you device
  • past the code (starting from /*).
  • build the code by pressing F7
  • wait for the build to complete then use a flashing tool (burner as it is regularly called). to flash the program to the chip. 

        That's it, your done. Alter the lines to match your display needs. experiment with the code and find out how to make it work your way. I really recommend learning the theory behind the LCD's operation as it will help you a lot..... especially writing a code from scratch and debugging.

--------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------

/*
* LCD DEMO.cpp
* Created: 29-12-2015 13:16:17
* Author: Hemanth
*/
//////////////////////////////////////////Define CPU Frequency///////////////////////////////////////////////////

#ifndef F_CPU
#define F_CPU 1000000UL // CPU frequency
#endif

////////////////////////////////////////////Value definitions/////////////////////////////////////////////////

#define LCD_DATA PORTC // data port for LCD
#define control PORTB // port for command
#define rs PB2 // Register select pin
#define rw PB1 // Read or write pin
#define en PB0 // Enable pin

////////////////////////////////////////Header files/////////////////////////////////////////////////////

#include <avr/io.h> //header file for DDR and PORT function
#include <util/delay.h> //header file for _dela_ms() function

/////////////////////////////////////////Function prototypes////////////////////////////////////////////////////

void LCD_cmd(unsigned char cmd);
void init_LCD(void);
void LCD_write(unsigned char data);
void stringwrite(const char *q);
void moveto(int line,int pos);
void clearLCD();

/////////////////////////////////////Functions////////////////////////////////////////////////////////

void clearLCD()
{
LCD_cmd(0x01);
}

void init_LCD(void)
{
LCD_cmd(0x38);// initialization of 16X2 LCD in 8bit mode
_delay_ms(10);
LCD_cmd(0x01); // clear LCD
_delay_ms(10);
LCD_cmd(0x0C); // cursor ON
_delay_ms(10);
LCD_cmd(0x80);//8 go to first line and 0 is for 0th position
}

void LCD_cmd(unsigned char cmd)
{
LCD_DATA=cmd;
control |=(1<<en); // RS and RW as LOW and EN as HIGH
control &=~(1<<rs);
control &=~(1<<rw);
_delay_ms(10);
control &=~(1<<rs);
control &=~(1<<rw);
control &=~(1<<en); // RS, RW , LOW and EN as LOW

}

void LCD_write(unsigned char data)
{
LCD_DATA= data;
control |= (1<<rs)|(1<<en); // RW as LOW and RS, EN as HIGH
control &=~(1<<rw);
_delay_ms(10);
control |= (1<<rs); // EN and RW as LOW and RS HIGH
control &=~(1<<rw);
control &=~(1<<en);
// delay to get things executed
}

void stringwrite(const char *S)
{
while (*S) {
LCD_write(*S++);
}}

void moveto(int line,int pos)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//The following block is for 16X4 LCD for 16X2 line 1 is 1st line and line 2 is second line
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(line==1)
LCD_cmd(0x80+pos);//1st line 0th position
if(line==2)
LCD_cmd(0xC0+pos);//2nd line 0th position
if(line==3)
LCD_cmd(0x90+pos);//3rd line 0th position
if(line==4)
LCD_cmd(0xD0+pos);//4th line 0th position
}

/////////////////////////////////////////Main function//////////////////////////////////////////////////

int main(void)
{
MCUCSR|=(1<<JTD);//Disable JTAG
MCUCSR|=(1<<JTD);//Have to send it twice
DDRB=0xFF;// Set all pin of portB to act as output pins
DDRC=0xFF;// Set all pin of portC to act as output pins
DDRD=0xFF;// Set all pin of portD to act as output pins
PORTD|=(1<<PD7);//// Set pin PD7 to logic high.... this is used to turn on the LCD light

init_LCD(); // initializing LCD...... must be called else it will not accept any commands

while(1)
{
moveto(1,0);// 1st line 0th position
stringwrite("Simple 16X4 LCD");
moveto(2,0);
stringwrite("Interfacing in 8");
moveto(3,0);
stringwrite("bitmode~Atmega16");
moveto(4,0);
stringwrite("Hemanth Praveen");
_delay_ms(2000);// wait 2 seconds
clearLCD();// clears the LCD memory
moveto(1,0);
stringwrite("8bit DATA ~PORTC");
moveto(2,0);
stringwrite("Command ~PORTB");
moveto(3,0);
stringwrite("RS~PB2 R/W~PB1");
moveto(4,0);
stringwrite("En~PB0 CLK~1MHz");
_delay_ms(2000);
clearLCD();
}}

--------------------------------------------------------------------------------------------------

ok guys that's it for now do comment if you experience any problems with the code.

Hardware for powering on and programming ATmega

                    Alright, After the power supply  (refer here for power supply ) the next step is to build a board with the chip and the connectors for programming.
here is a simple board ready for programming.
.

                       As you can see the ISP (in system programming) pins are soldered on the board which means that reprogramming the board will be easy as we do not have to remove the chip. 

However you can also use something like this





Here the chips can be programmed separately (as shown above) and later be placed or soldered directly to a board.

The image of the board shown above has the following
  • 28 pin ZIF socket
  • 40 pin ZIF socket
  • 10 pin male socket
  • 16Mhz crystal
  • 2 X 0.33pf ceramic disc capacitors
Even though two ZIF (zero insertion force) sockets are present only one chip can be recognised at a time. If we put both the chips. None of them will be detected.

Here’s the back wiring of the board



Here is the circuit diagram of the board



                  

ok, now that the basic programming and power connections (refer here for power supply ) have been set out, a simple but important topic. 'Reset function in ATmega's. A reset basically means 'returning to defaults' , which in this case returns the controller register values to their defaults. The MCU also execute its code from the beginning.

The ATmega8 has four sources of Reset:
  • Power-on Reset. The MCU is reset when the supply voltage is below the Power-on
    Reset threshold
  • External Reset. The MCU is reset when a low level is present on the RESET pin for
    Longer than the minimum pulse length 
  • Watchdog Reset. The MCU is reset when the Watchdog Timer period expires and the
    Watchdog is enabled
  • Brown-out Reset. The MCU is reset when the supply voltage VCC is below the Brown-out Reset threshold (VBOT) and the Brown-out Detector is enabled
Ill briefly explain the first two as they are commonly used

Power on reset

Well this is the simplest form of resetting the chip. The name says it all ' The chip resets when it is powered on'. Every ATmega chip (at least the ones most commonly used) have a on-chip Power-on Reset (POR) circuit to generate a POR pulse. whenever the supply voltage (VCC) is below the detection level, the POR gets activated. The POR circuit can also be used to trigger a Start-up Reset, and also to detect a failure in supply voltage. A Power-on Reset (POR) circuit ensures that the device is reset from Power-on.

External reset

This function lets you use a pulse to reset the chip. The pulse can be a signal from a button or another microcontroller project. The only thing is that the reset pin (pin 9 in ATmega 16/32) should be grounded to trigger it.



Notice the resistor? (R). This is a pull up resistor. Which means the line in this case pin 9 of the MCU has a defined state. That is when the button SW1 is not pressed the line is pulled high (+5V) to prevent any accidental resets. This is very important. I have not used it on the board containing the ZIF sockets because the programmer has one on board. However to make a stand alone circuit the resistor is required.

If a rest button is not used. The reset pin must be pulled up by the resistor. So what about the value?

Well the best and safest method to use the values mentioned in the data sheet. However here are a few values which worked for me

8K2 to 15K @ 5V
4.7K to 8K2 @ 3.3V(only for ATmega 8L, 16L, 32L)
3K3 @ 1.8V(only for ATmega 8L, 16L, 32L)


   For that you will need an USB to ISP programmer.There are two pin configurations 10pin and 6 pin, both work the same way but the pinout is different.Even though 10 pin only 6 lines are generally used.
A 10 pin USB to ISP AVR programmer
A 6 pin USB to ISP AVR Programmer
 The default directory of the hex file is in my documents but you can change it any time. Any burning software available can be used. Just google it.


Here is a screen shot of what I use

Well guys the in the next post we will discuss on connecting the IO pins to an LED and a button......Ill provide a sample code too.

By the way This article was written with reference from the datasheet