Summary of ADC EXAMPLE ATMEGA8 DIGITAL VOLT METER AMMETER AVR PROJECT
This article details the design and implementation of a digital voltmeter using an ATmega8 microcontroller. It explains the ADC operation principle, specifically how an analog signal is converted to a 10-bit digital value for display on an LCD. The project utilizes a resistive voltage divider to scale input up to 10V down to the ADC's reference range, with C code provided to handle initialization, conversion, and data formatting in millivolts to optimize memory usage.
Parts used in the Digital Voltmeter Project:
- ATmega8 Microcontroller
- WH1602A LCD Display
- 4 MHz Quartz Resonator (X1)
- Capacitors C1 and C2 (18-20 pF)
- Resistor R1 (10 kΩ) and Capacitor C7 (0.1 μF) for Reset
- Signal LED D1
- Resistors R2 (200 Ω) and R3 (20 Ω)
- Potentiometer VR1 (10 kΩ) for LCD Contrast
- Resistors R4 (3 kΩ) and R5 (1 kΩ) for Voltage Divider
- 10 μH Choke (for noise elimination)
- 0.1 μF Capacitor (for noise elimination)
ADC – analog-to-digital converter (ADC-Analog-to-Digital Converter). Converts a certain analog signal to digital. Bitrate ADC determines the accuracy of the signal conversion. Conversion time – respectively, the speed of the ADC. The ADC is embedded in many microcontrollers of the AVR family and simplifies the use of the microcontroller in any regulation schemes where it is necessary to digitize a certain analog signal.
Consider the principle of operation of the ADC . To convert, you need a reference voltage source and the actual voltage that we want to digitize (the voltage that is converted must be less than the reference voltage). You also need a register where the converted value will be stored, let’s call it Z. Input voltage = Reference voltage * Z / 2 ^ N, where N is the ADC bit . We agree that this register, like ATmega8 , is 10-bit. The transformation in our case takes place in 10 stages. Z9 high bit is set to one.
Next, a voltage is generated (Reference voltage * Z / 1024) , this voltage is compared with an input comparator using an analog comparator, if it is greater than the input voltage, Z9 bit becomes zero, and if it is less, it remains one. Next, go to bit Z8 and the above method, we obtain its values. After the calculation of the Z register is completed, a flag is set, which signals that the conversion is completed and the resulting value can be read. The conversion accuracy and interference, as well as the conversion rate, can greatly influence the conversion accuracy. The slower the conversion occurs, the more accurate it is. Guidance and interference should be fought with inductance and capacitance, as advised by the manufacturer in the datasheet:
In AVR microcontrollers, the AREF pin , or internal sources of 2.56V or 1.23V, can be used as a reference voltage source. Also the source of the reference voltage can be the supply voltage. In some cases and models of microcontrollers there are separate outputs for powering the ADC:AVCC and AGND . Conclusions ADCn – ADC channels.
From which channel the signal will be digitized, you can choose using a multiplexer.
Now we will demonstrate by example what was said above. We will build a model that will work as a voltmeter with a digital scale. We agree that the maximum measured voltage will be 10V. Also let our layout display the contents of the ADC register on the LCD.
Connection diagram:
The binding of the microcontroller and LCD WH1602A is standard. X1 – a quartz resonator at 4 MHz, capacitors C1, C2 – 18-20 pF. The R1-C7chain at the reset pin is 10 kΩ and 0.1 μF, respectively. Signal LED D1 and limiting resistor R2 200 ohms and R3 – 20 ohms. LCD contrast adjustment – VR1 at 10 kΩ. The reference voltage source we will use is built in to 2.56V. With the help of the divider R4-R5 we will achieve the maximum voltage of 2.5V at the input of PC0 , with a voltage on the probe 10V. R4 – 3 kOhm, R5 – 1 kOhm, in their denomination should be treated carefully, but if it is not possible to choose exactly such, you can make any resistive 1: 4 divider and programmatically correct the readings, if necessary. A 10 μH choke and a 0.1 μF capacitor to eliminate noise and pickups on the ADC are not shown in the diagram. Their presence is implied if the ADC is used. Now it’s up to the program:
C program
#include
#define RS 2 // RS = PD2
#define E 3 // E = PD3
#define TIME 10 // Time delay constant for LCD
// Frequency clocking MK - 4 MHz
#define R_division 3.837524 // = R4 / R5 constant
unsigned int u = 0 ; // Global variable with transform content
void pause ( unsigned int a )
{
unsigned int i ;
for ( i = a ; i > 0 ; i - ) ;
}
void lcd_com ( unsigned char lcd ) // Transmit LCD Command
{
unsigned char temp ;
temp = ( lcd & ~ ( 1 << RS ) ) | ( 1 << E ) ; // RS = 0 is a command
PORTD = temp ; // Output to the portD the top tetrad of the command, signals RS, E
asm ( "nop" ) ; // A small delay in 1 tact MK, to stabilize
PORTD = temp & ~ ( 1 << E ) ; // command recording signal
temp = ( ( lcd * 16 ) & ~ ( 1 << RS ) ) | ( 1 << E ) ; // RS = 0 is a command
PORTD = temp ; // Output the command, signals RS, E to portD
asm ( "nop" ) ; // A small delay in 1 tact MK, to stabilize
PORTD = temp & ~ ( 1 << E ) ; // command recording signal
pause ( 10 * TIME ) ; // Pause for command execution
}
void lcd_dat ( unsigned char lcd ) // Write data to LCD
{
unsigned char temp ;
temp = ( lcd | ( 1 << RS ) ) | ( 1 << E ) ; // RS = 1 is data
PORTD = temp ; // Output to the portD the highest data tetrad, RS, E signals
asm ( "nop" ) ; // A small delay in 1 tact MK, to stabilize
PORTD = temp & ~ ( 1 << E ) ; // Data Write Signal
temp = ( ( lcd * 16 ) | ( 1 << RS ) ) | ( 1 << E ) ; // RS = 1 is data
PORTD = temp ; // Display on the portD the lowest tetrad of data, signals RS, E
asm ( "nop" ) ; // A small delay in 1 tact MK, to stabilize
PORTD = temp & ~ ( 1 << E ) ; // Data Write Signal
pause ( TIME ) ; // Pause for data output
}
void lcd_init ( void ) // Initialize the LCD
{
lcd_com ( 0x2c ) ; // 4-wire interface, 5x8 character size
pause ( 100 * TIME ) ;
lcd_com ( 0x0c ) ; // Show image, do not show cursor
pause ( 100 * TIME ) ;
lcd_com ( 0x01 ) ; // Clear DDRAM and set the cursor to 0x00
pause ( 100 * TIME ) ;
}
unsigned int getADC ( void ) // Read ADC
{ unsigned int v ;
ADCSRA | = ( 1 << ADSC ) ; // Start conversion
while ( ( ADCSRA & _BV ( ADIF ) ) == 0x00 ) // Wait for the conversion to complete
;
v = ( ADCL | ADCH << 8 ) ;
return v ;
}
void write_data ( unsigned int u )
{ unsigned char i ;
double voltage = 0 ;
lcd_com ( 0x84 ) ; // Display the ADC register on the LCD
for ( i = 0 ; i < 10 ; i ++ )
if ( ( u & _BV ( 9 - i ) ) == 0x00 ) lcd_dat ( 0x30 ) ;
else lcd_dat ( 0x31 ) ;
lcd_com ( 0xc2 ) ;
voltage = R_division * 2.56 * u * 1.024 ; // Calculate Stress
i = voltage / 10,000 ; // Output Voltage On LCD
voltage = voltage - i * 10000 ;
if ( i ! = 0 ) lcd_dat ( 0x30 + i ) ;
i = voltage / 1000 ;
voltage = voltage - i * 1000 ;
lcd_dat ( 0x30 + i ) ;
lcd_dat ( ',' ) ;
i = voltage / 100 ;
voltage = voltage - i * 100 ;
lcd_dat ( 0x30 + i ) ;
i = voltage / 10 ;
voltage = voltage - i * 10 ;
lcd_dat ( 0x30 + i ) ;
lcd_dat ( 'v' ) ;
}
int main ( void )
{
DDRD = 0xfc ;
pause ( 3000 ) ; // Delay to turn on the LCD
lcd_init ( ) ; // Initialize LCD
lcd_dat ( 'A' ) ; // We write "ADC =" and "U =" on the LCD
lcd_dat ( 'D' ) ;
lcd_dat ( 'C' ) ;
lcd_dat ( '=' ) ;
lcd_com ( 0xc0 ) ;
lcd_dat ( 'U' ) ;
lcd_dat ( '=' ) ;
ADCSRA = ( 1 << ADEN ) | ( 1 << ADPS1 ) | ( 1 << ADPS0 ) ;
// Turn on the ADC, the clock frequency of the inverter = / 8 from the clock microcontroller
ADMUX = ( 1 << REFS1 ) | ( 1 << REFS0 ) | ( 0 << MUX0 ) | ( 0 << MUX1 ) | ( 0 << MUX2 ) | ( 0 << MUX3 ) ;
// Internal reference voltage source Vref = 2.56, the input of the ADC is PC0
while ( 1 )
{
u = getADC ( ) ; // Read data
write_data ( u ) ; // Display them on LCD
pause ( 30,000 ) ;
}
return 1 ;
}
The program is simple. At the beginning, we initialize the I / O ports. In order to serve as an input to the ADC , the pin PC0 must work as an input. Next, we carry out the initialization of the LCD and ADC . Initializing the ADC is to turn it on with the ADEN bit in the ADCSRA register.And the choice of frequency conversion bits ADPS2, ADPS1, ADPS0 in the same register. We also choose the reference voltage source, bitsREFS1 REFS0 in the ADMUX register and input of the ADC : bits MUX0, MUX1, MUX2, MUX3 (in our case, the input of the ADC is PC0 , therefore MUX0.3 = 0 ). Next, in the perpetual cycle, we start the conversion by setting the ADSC bit in the ADCSRA register. We are waiting for the conversion to finish (the ADIF to ADCSRA bit becomes 1). Next, remove the data from the register ADC and display them on the LCD . It is necessary to remove data from the ADC in the following sequence: v = (ADCL + ADCH * 256); if you use v = (ADCH * 256 + ADCL); - point blank does not work.
So there is a trick to not work with fractional numbers. When to calculate the input voltage in volts. We will simply store our voltages in millivolts.For example, the value of the voltage variable 4234 means that we have 4.234 volts. In general, operations with fractional numbers consume a lot of microcontroller memory (our voltmeter firmware weighs slightly more than 4 kilobytes, this is half the memory of ATmega8 programs!), They are recommended to be used only when absolutely necessary. Calculating the input voltage in millivolts is simple: voltage = R_division * 2.56 * u * 1.024;
Here R_division is the coefficient of the resistive divider R4-R5 . So, as the real divider coefficient can differ from the calculated one, our voltmeter will lie. But it’s easy to correct. Using a tester, we measure a certain voltage, we get X volts, and let our voltmeter show Y volts. Then R_division = 4 * X / Y , if Y is greater than X and 4 * Y / X if X is greater than Y. This completes the setting of the voltmeter, and it can be used.
You can also modify your power supply. By inserting a digital voltmeter ammeter on the LCD and overload protection into it (to measure the current, we need a powerful shunt of about 1 Ohm resistance).
In my power supply, I also integrated the overload protection when the current exceeds 2A, then the piezo squeaker begins to squeak earnestly, signaling overload:
Source: avrlab.comz AVR Ammeter Volt Meter Project files Alternative link: adc-example-atmega8-digital-volt-meter-ammeter-project.rar
- How does the ADC determine the accuracy of the signal?
The accuracy is determined by the bitrate of the ADC, where a higher bit count results in greater precision. - What formula calculates the input voltage based on the register value?
The formula is Input voltage = Reference voltage * Z / 2 ^ N, where Z is the register value and N is the ADC bit count. - Can the internal reference voltage be used instead of an external source?
Yes, AVR microcontrollers can use internal sources of 2.56V or 1.23V as the reference voltage. - Which pins are used for powering the ADC in some microcontroller models?
Some models have separate outputs for powering the ADC known as AVCC and AGND. - How is the maximum measured voltage handled if it exceeds the reference voltage?
A resistive divider, such as R4-R5, is used to scale the input voltage so it remains less than the reference voltage at the ADC input. - Why does the article recommend calculating voltage in millivolts rather than fractions?
Operations with fractional numbers consume a lot of microcontroller memory, so storing values in millivolts saves space. - What happens if the actual divider coefficient differs from the calculated one?
The voltmeter readings will be inaccurate, but this can be corrected by measuring a known voltage and adjusting the R_division constant. - How do you correctly read the data from the ADC register?
You must read the data in the sequence v = (ADCL | ADCH << 8); swapping the order will not work.

