ADC only working once on ATMEGA324PA - c

I have some code which should read the values of a couple of ADC pins, each time around the commutator loop.
static uint16_t adc0;
static uint16_t adc1;
void init(void) {
...
hw_configure_adcs();
...
}
void loop(void) {
...
adc0 = hw_read_adc(0);
adc1 = hw_read_adc(1);
...
}
void hw_configure_adcs(void) {
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS0);
}
uint16_t hw_read_adc(uint8_t n) {
ADMUX = (1<<REFS0) | (n & 0x07);
ADCSRA |= (1<<ADSC); // start conversion
uint16_t count;
for (count = 0; !(ADCSRA & (1<<ADIF)); count++); // wait for conversion to complete
// ADCSRA |= (1<<ADIF); // tried with and without this
return (ADCH << 8) | ADCL; // return ADC value
}
What I see is odd: The values of adc0 and adc1 are set to the same value and never change, until the AVR chip is restarted/reflashed.
(The value is 0x00d1 at 0.71V and 0x0128 at 1.00V, which seems reasonable.)
I have tried:
Turning the voltage down: adc0 and adc1 stay constant and only go down when the AVR code is reflashed (and hence the chip restarted).
Turning the voltage up: adc0 and adc1 stay constant and only go up when the AVR code is reflashed (and hence the chip restarted).
Returning count from hw_read_adc() instead of the ADC value: This returns varying numbers between 0x34 and 0x38 which are different for the two ADCs and continuously vary over time.
From these tests, I infer that the ADCs are being read, but that I am missing some "clear ADCH and ADCL and get them ready to accept the new reading" step.
I have re-read section 23 of http://www.atmel.com/images/Atmel-8272-8-bit-AVR-microcontroller-ATmega164A_PA-324A_PA-644A_PA-1284_P_datasheet.pdf many times, but have obviously overlooked something vital.

After much googling, I found: http://www.avrfreaks.net/forum/adc-only-happens-once-reset
The problem was that return (ADCH << 8) | ADCL; was compiled so that it read the high register first (as you might expect).
Page 252 of the datasheet says: "Otherwise, ADCL must be read first, then ADCH".
Changing my code to return ADC fixed the problem.
My guess as to what was happening is:
The read from ADCH occurred.
The read from ADCL had the effect of locking the ADC-result, to prevent tearing.
The next ADC read had nowhere to write its result, as the ADC-result was locked.
Repeat...

The Problem with your code is you read ADCH first.
ADCL must be read first, then ADCH, to ensure that the content of the Data Registers belongs to the same conversion. Once ADCL is read, ADC access to Data Registers is blocked. This means that if ADCL has been read, and a conversion completes before ADCH is read, neither register is updated and the result from the conversion is lost. When ADCH is read, ADC access to the ADCH and ADCL Registers is re-enabled.
So the correct code should be-
return ADCL | (ADCH << 8) ;

Related

C program stops execution when assigning variable

I'm not sure if this is happening when assigning a variable specifically but when debugging the assembly code, the compiler executes RJMP $+0000 where it hangs the program.
EDIT: I added included libraries if that's relevant
#define __DELAY_BACKWARD_COMPATIBLE__
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/delay.h>
#include <stdint.h>
void ReadTemp(uint8_t address){
ADCSRA = ADCSRA | 0b10000111; //enable ADC, CLK/128 conversion speed
ADMUX = ADMUX | 0b01000000; //Use internal 2.56V Vref and PA0 as input, right-hand justified
ADCSRA |= (1 << ADSC); //start conversion
while(!(ADCSRA & (1 << ADIF))) {} // wait until process is finished;
uint8_t low_value = ADC & 0x00FF;
// or low_value = ADCL;
uint8_t high_value = ADC & 0xFF00; //problem here
...
}
I don't know what any of this is doing, but I do see an error in the bitwise math.
uint8_t low_value = ADC & 0x00FF;
uint8_t high_value = ADC & 0xFF00; //problem here
low_value and high_value are both 8 bits (uint8_t). I am going to go out on a limb here and say ADC is 16 bit. For high_value, you are anding ADC with 0xFF00 then truncating the value to 8 bit. high_value will always be zero.
What should be done is:
uint8_t high_value = (ADC & 0xFF00) >> 8;
This will grab the left byte of ADC and shift it right by 8 bits then assign it to the high_value byte storage giving you the correct value.
How you are doing low_value is correct. As a matter of fact, you could simply do:
uint8_t low_value = ADC;
There is something somewhat suboptimal here:
uint8_t low_value = ADC & 0x00FF;
You are reading ADC, which is a 16-bit register, implemented as a pair
of 8-bit registers. As shown in the disassembly, this requires two in
instructions, one per byte. And then you are just throwing away one of
those bytes. You may think that the compiler is smart enough to avoid
reading the byte it is going to discard right away. Unfortunately, it
cannot do that, as I/O registers are declared as volatile. The
compiler is forced to access the register as many times as the source
code does.
If you just want the low byte, you should read only that byte:
uint8_t low_value = ADCL;
You then wrote:
uint8_t high_value = ADC & 0xFF00;
As explained in the previous answer, high_value will be zero. Yet the
compiler will have to read the two bytes again, because the I/O register
is volatile. If you want to read the high byte, read ADCH.
But why would you want to read those two bytes one by one? Is it to put
them back together into a 16-bit variable? If this is the case, then
there is no need to read them separately. Instead, just read the 16-bit
register in the most straight-forward fashion:
uint16_t value = ADC;
A long time ago, gcc didn't know how to handle 16-bit registers, and
people had to resort to reading the bytes one by one, and then gluing
them together. You may still find very old example code on the Internet
that does that. Today, there is absolutely no reason to continue
programming this way.
Then you wrote:
//problem here
Nope, the problem is not here. That is not what generated the rjmp
instruction. The problem is probably right after that line, in the code
you have chosen not to post. You have some bug that manifests itself
only when optimizations are turned on. This is typical of code that
produces undefined behavior: works as expected with optimizations off,
then does weird “unexplainable” things when you enable optimizations.

potentiometer adc practice in atmega128

I'm a beginner in this field. My goal is to change the output of 8 LEDs (which are connected to PORTA) according to the potentiometer. I have connected the middle line of the potentiometer to PF0, which is ADC0. I also connected the other two lines to the 5V and ground.
I know there's no problem with the chip or connection because the LEDs are working just fine.
But no matter how I change the code below (what I mean by changing is by slightly changing the ADMUX and ADCSRA registers) no output is shown!
I am using atmega128 with 16MHZ clock. Below is the code that I'm trying to solve.
#include <asf.h>
#include <avr/io.h>
#define F_CPU 16000000L
int init_board(void)
{
DDRA=0xff;
PORTA=0x01;
}
int ADC_init(void)
{
//ADCSRA
ADCSRA = 0b10000111;
//ADMUX
ADMUX = 0b01100000; // middle line connected to ADC0
}
int main (void)
{
init_board();
ADC_init();
ADCSRA |= (ADSC >> 1);
while(1)
{
if(ADSC == 0)
{
uint8_t disp_value = ADCL;
PORTA = disp_value;
delay_ms(200);
ADCSRA |= (ADSC >> 1);
}
}
}
I have no idea why the code doesn't work.
I suppose it's because it didn't set my register correctly, but I've followed all the instructions on the atmega128 datasheet.
First issue is your bit shifting, it should be ADCSRA |= (1 << ADSC).
Next issue is results reading. You set fifth bit of ADMUX to 1, so ADLAR=1 and in that mode result is left adjusted so you should read ADCH.
Moreover when you switch to 10-bit resolution, i.e. you start working with multi-byte results, be aware that reading only ADCL is not enough, see datasheet 23.3 for explanation: "Once ADCL is read, ADC access to data registers is blocked. This means that if ADCL has been read, and a conversion completes before ADCH is read, neither register is updated and the result from the conversion is lost. When ADCH is read, ADC access to the ADCH and ADCL Registers is re-enabled."
Lastly, using hardcoded delays for reading is not good practice especially when you change code later to read ADC as fast as possible. In such case after conversion start you should check if ADIF flag is set or react with interrup when ADEN is set. Refer to datasheet for details.

AVR in C - Storing value of register in variable

I'm using an ATmega328. I am currently doing several measurements using the 10-bit ADC. I would like to store the values it converts in variables in order to be able to operate with them. For example:
int a;
(...)
ADMUX = 0b01000011; //Vref = 5V, ADC3
ADCSRA |= (1<<ADSC); //Starts conversion
while(!(ADCSRA & (1<<ADIF))); //Wait until it finishes
ADCSRA |= (1<<ADIF); //Clear flag
Suppose that the ADC stored the value 576 in ADCH:ADCL. Is it possible to achieve, somehow, the variable a to take that same value? (i.e. a=576;).
The full 16-bit result register should be accessible as such:
a = ADC;
But if you want to read both parts manually, then
a = ADCL;
a |= ADCH << 8;
That has to be done in two separate statements to force ADCH to be read last. The I/O modules have a temporary register to hold the high byte, preventing the module itself from corrupting the read value if it changes the value of the register. (i.e. if the ADC finishes another conversion and stores the new value.)
If you have interrupts that access the ADC (or need to use the value at a), you'll need to disable them for the duration of the access (that also goes for a = ADC, since it also compiles into multiple 8-bit reads).
The answer above is close, still comes up a little short based on the recommendations from the chip manufacturer. To be safe you should follow the instructions in the authoritative reference linked below.
Simply turn off interrupts just before and restore them after:
unsigned int a; // 16-bit word
// other code
cli();
a = ADCL;
a |= ADCH << 8;
sei();
Every example in this reference from the chip manufacturer follows this pattern for atomic 16-bit reads and writes. Reference: AVR Application Note 072.

ADC conversion error in code ATMEGA328p

I am trying to read values from ADC0 on the ATMEGA328p. The values expected are between 0-5v. This is due to ADC0 being connected to a potentiometer connected to the 5v output of a Xplained mini. I am getting either 0v or 5v usually. With no variation when the potentiometer is changed. I have looked at multiple ADC examples and tutorials online but cant find the error in my code.
void adc_initialise (){
//set vref to AVcc, channel selection is initially ADC0 as wanted
ADMUX |= (1<<6);
//set ADC enable, Set adc clock prescalar 64
ADCSRA |= (1<<7)|(1<<2)|(1<<1);
}
uint16_t adc_read (){
ADCSRA |= (1<<6); // start conversion
while( ADCSRA & (1<<ADSC) ); //wait until conversion is complete
return ADCW;
}
float adc_calculation(uint16_t adcValue){
float stepSize = (5.0/1024.0);
float voltageIn = adcValue*stepSize;
return voltageIn;
}
then in my main i have
while(1){
adc_initialise();
uint16_t adcValue = adc_read();
float voltageIn = adc_calculation(adcValue);
adcConverterToUART(voltageIn);//I know that this part of the code is working as I have hardcoded many test values and all have transmitted correctly.
}
And as mentioned above I know the error is not in my UART code but somewhere in the above ADC code. Cheers in advance for any help.
I could mention a few things. You could try it if it help.
You should do your adc_initialise() before the while(1).
You initialize it again and again.
while( ADCSRA & (1<<ADSC) ); here you should add maybe NOPs that the compiler don't optimize it out of the code.
The rest looks good in my eyes.
Do you get any value of the conversion ?
mfg
EDIT1:
I looked in one of my old files.
There we made it like this to get the value from the ADC.
// Get count value
adValue = ADCL;
adValue |= (UI_16_t)(ADCH << 8);
ADCL is the low byte of the value and ADCH the high byte.
We shifted the high byte in the front of the low byte to get the value.

ADC not working with ATMEGA8

I'm trying to execute the following piece of code on ATMEGA8 but the ADC doesn't seem to be working.
#include <avr/io.h>
#include "LCD.h"
int main()
{
int val=0;
ADCSRA=0x87;//ADc enabled and prescaler set to fosc/128
ADMUX= 0xC0;//REFS0 and REFS1 set using internal 2.5 volts as Vref
DDRC=0x00;// as input for the adc
PORTC=0x00;
DDRB=0xff;
while (1)
{
ADCSRA |=(1<<ADSC);
while(!(ADCSRA&(1<<ADIF)));
lcd_string("Done Conversion");
val=ADCL;
PORTB=ADCL;
ADCSRA |=(1<<ADIF);//(reseting ADIF to 1)
lcd_print(2,1,val,3);
}
return 0;
}
You have not read ADCH. The data sheet says
When ADCL is read, the ADC Data Register is not updated until ADCH is
read. Consequently, if the result is left adjusted and no more than
8-bit precision is required, it is sufficient to read ADCH. Otherwise,
ADCL must be read first, then ADCH.
val = ADCL;
val = ((ADCH<<8) | val) & 0x3F;
You are writing the result to an 8-bit port. If you want an 8-bit conversion then set the ADLAR bit in ADMUX. The 10-bit conversion will then be left-shifted by 6 bits and you can ignore the ls 2 bits in ADCL.
ADMUX = 0xE0;
...
val = ADCH;
BTW read-modify-write of ADCSRA is not recomended. To clear bit 4 – ADIF, the ADC Interrupt Flag, you could try
ADCSRA = 0x97; // rewrite config and clear ADIF
Which is your original configuration with the ADIF bit set to clear that flag. Alternatively, you could test bit 6 ADSC which remains high until the conversion is complete, and no action is required to clear it. Since you have not enabled the ADC interrupt, there is no need to clear the ADIF flag.
while (ADCSRA & (1<<ADSC)); // wait for conversion to complete

Resources