0
\$\begingroup\$

I'm using the ADC of ATMega8 to read the charging and discharging waveforms of a capacitor. However I'm getting an extremely large number of errors when I see the data I get in my laptop. I'm sending the sampled data to the laptop through a USB to TTL converter at 9600 bps. The sampling rate is 15000 samples per second, so there are probably many samples that are lost due to the speed difference. I'm using the default clock of ATMEGA8. The serially transmitted data is received in MATLAB and the plot is shown: enter image description here

The zoomed version of one of these charge discharge cycles is shown below: enter image description here

As you can see, there are a large number of errors but we can make out the actual waveform. What is causing this problem? How can I correct this?

Also, I have a problem with the ADC readings. I have set the reference of the SAR ADC as the default internal 2.56 V. As I understand it, the 8 bit ADC is supposed to give me 0 (Ox00) at 0 V and 255 (OxFF) at 5 V. But here I get 124 for 0 V and 255 for 5 V. Why is this happening? As shown, multiplying the values obtained in the ADC by 5/255 does not give me the original voltage values. The 2.5 V 'offset' shown is due to this error of recognizing 0 V as 124. But I don't know why this is happening.

The C code on the microcontroller end is shown. The program also generates two pulses used to activate the charge and discharge cycles of the capacitor (The pulses are fed into the gate of two MOS switches).

#define F_CPU 1000000UL

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

#define USART_BAUDRATE 9600 
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)  //Calculating prescaler for serial communication

int main(void)
{
    uint32_t i;

    UCSRB |= (1 << RXEN) | (1 << TXEN);  //Enable serial transmission and reception
    UCSRC |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1); //Set character size (8 bits). When URSEL is 1, we can write data to UBRRH register. 
        UBRRH = (BAUD_PRESCALE >> 8);  \\Setting the prescale
        UBRRL = BAUD_PRESCALE;  //Set prescale value
//DDRD = 0xff;   \\Set port D as output (for pulses)
ADMUX = (1<<REFS0) |(1<<ADLAR)|(1<<REFS1); \\Set reference voltage to internal and left adjust result (out of 10 bit result, 8 bits are moved to ADCH register).
ADCSRA = (1<<ADEN)|(1<<ADFR); \\Enable ADC in free running mode
//|(1<<ADPS2) |(1<<ADPS1) |(1<<ADPS0);  
//
ADCSRA|=(1<<ADSC); \\Start conversion
DDRB = 0xff;    \\Set port B as output (for pulses)
    while(1)
    {
    for (i = 0; i< 200; ++i)
        {
        PORTB = 0x01; \\First bit of port B will be high pulse for duration of loop.

//      ADCSRA|=(1<<ADSC);
        while ((UCSRA & (1 << UDRE)) == 0) {}; \\a while loop (with no body) that continues as long as the UDRE bit is set. UDRE is set when it is ready to send data
             UDR = ADCH;  \\Read ADC data

//      PORTB = 0x01;
        }

    PORTB = 0x00;  \\Set all ports off for a brief interval (for nonoverlapping of charge and discharge cycles)
    _delay_ms(5);

    for (i = 0; i<400; ++i)
        {
        PORTB = 0x02; \\Setting second bit of port B high. To activate discharge cycle.

//      ADCSRA|=(1<<ADSC);
        while ((UCSRA & (1 << UDRE)) == 0) {}; \\a while loop (with no body) that continues as long as the UDRE bit is set. UDRE is set when it is ready to send data
             UDR = ADCH; 

//      PORTB = 0x02;
        }  
    }
return 1;
}
\$\endgroup\$
7
  • 2
    \$\begingroup\$ Commenting your code would help. \$\endgroup\$
    – jippie
    Apr 9, 2014 at 18:31
  • 2
    \$\begingroup\$ And you should wait for the AD conversion to complete before reading its value. \$\endgroup\$
    – jippie
    Apr 9, 2014 at 18:38
  • \$\begingroup\$ @jippie What do you mean? Should I add a delay before read operation? What can I do? \$\endgroup\$
    – Analon
    Apr 9, 2014 at 18:44
  • 1
    \$\begingroup\$ You forgot to comment the last while loop. Commenting your code is really very important, not because you are asking others for advice and they don't have a clue about what you are trying to accomplish, but also because in half a year you don't know yourself what you were trying to accomplish. \$\endgroup\$
    – jippie
    Apr 9, 2014 at 19:30
  • 1
    \$\begingroup\$ I suggest that you test the code with a simple voltage (or potentiometer across the supply to produce an adjustable voltage), rather than the capacitor, and see if you can get the code to report expected numbers. Any pattern of misbehavior will be much easier to diagnose. \$\endgroup\$
    – gwideman
    Apr 10, 2014 at 6:19

2 Answers 2

3
\$\begingroup\$

I have set the reference of the SAR ADC as the default internal 2.56 V. As I understand it, the 8 bit ADC is supposed to give me 0 (Ox00) at 0 V and 255 (OxFF) at 5 V.

No that is not the case. a result of 0 represents \$ V_{in}= \frac {ADC \times 2.56V}{256} =0\$ and a result of 255 represents \$ V_{in}= \frac {ADC \times 2.56V}{256} =2.55V\$

Of course in this case to get the voltage representation you can simply divide the ADC result by 100 (255 becomes 2.55 V), or you can multiply by 10 to get the result in mV (255 becomes 2550 mV).

In the code you have provided you have commented the line that sets the ADC clock prescaler which means that the ADC clock is set to 1MHz and may reduce the accuracy.

//|(1<<ADPS2) |(1<<ADPS1) |(1<<ADPS0); 

Also note that the selected prescaler in case you use the above line is 128 which will result to 1MHz/128= 7.8KHz which is too low. You should try to be in the range of 50KHz-200KHz. You can use a value of 8 which results to 125KHz.

(0<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)

Also when using the internal reference voltage you should add an capacitor to AREF pin.


According to the datasheet the error for a baudrate of 9600 when the AVR runs at 1MHz is too high, so the transfered data may be corrupt. Try to use 4800.

\$\endgroup\$
4
  • \$\begingroup\$ In any case, why am I getting '124' for 0 V? Shouldn't 0 V be '0'? I cannot read negative values with ADC, right? Also, can you tell me what is the reason for these errors? Is it the absence of the capacitor in the pin, or the low frequency of 7.8kHz? \$\endgroup\$
    – Analon
    Apr 10, 2014 at 1:49
  • \$\begingroup\$ @Analon No, you can't read negative voltages. According to the datasheet (page 241) the min and max ADC clock is 50KHz and 1MHz, I haven't used a clock lower than that so I'm not sure of the possible effect. The capacitor is used for decoupling of the internal bandgap reference. I would suggest you try with the ADC clock set to a proper range and the AREF capacitor and see if the results are improved, although I can't justify the offset you get in the result. \$\endgroup\$
    – alexan_e
    Apr 10, 2014 at 6:29
  • \$\begingroup\$ @Analon A baudrate of 9600 with 1MHz clock gives an error of -7% so this can be the cause of the wrong readings (problems in UART communication). Can you try with a baudrate of 4800 which gives an error of just 0.2% and report back? \$\endgroup\$
    – alexan_e
    Apr 10, 2014 at 7:12
  • \$\begingroup\$ The problem was solved when I changed the prescale to 125 kHz and added a command to wait for ADC output. \$\endgroup\$
    – Analon
    Apr 11, 2014 at 14:35
2
\$\begingroup\$
while ((UCSRA & (1 << UDRE)) == 0) {}; \\a while loop (with no body) that continues as long as the UDRE bit is set. UDRE is set when ADC is ready to send data

You are confusing the USART Data Register Empty flag with the ADC Conversion Complete flag.

Best with regard to timing efficiency is to:

  • not use free running mode

  • start transmitting a byte through USART (serial)

  • start ADC conversion
  • wait for ADC conversion completion
  • wait for USART transmit completion (which I assume takes longer than the ADC conversion)
  • read ADC value
  • repeat until false

Check this proof of concept that reads the ADC in a loop and converts it to PWM. The loop waits for ADC conversion completion. The USART transmit completion is already in your original code.

In free running mode the ADC conversion is restarted when you read the data register. If you read the data register again before conversion completed, the result is undefined. That is what you are seeing, some bits in your result are 'corrupted'.

\$\endgroup\$
3
  • \$\begingroup\$ You are confusing the USART Data Register Empty flag with the ADC Conversion Complete flag. I don't think so, he is just checking if the USART transmit buffer is empty in order to load the new ADC data to send. Also regarding the corrupt data, with 9600 baud it takes 1/9600 sec to send a bit so a byte takes about 900us. If the ADC clock is set to at least 14KHz (normally it should be 50KHz-200KHz) then an ADC result will be ready before the USART buffer empties. \$\endgroup\$
    – alexan_e
    Apr 9, 2014 at 20:38
  • \$\begingroup\$ I can only transmit a byte through USART once I get some data from ADC, right? So how can I use the order you mentioned? Wont the order be: start ADC conversion; wait for ADC conversion completion;start transmitting a byte through USART (serial); wait for USART transmit completion (which I assume takes longer than the ADC conversion); read ADC value; repeat until false; \$\endgroup\$
    – Analon
    Apr 10, 2014 at 1:44
  • \$\begingroup\$ @Analon You start the initial AD conversion outside the loop. \$\endgroup\$
    – jippie
    Apr 10, 2014 at 4:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.