Analog to digital conversion

Hi i am new here and i would like to know some opinions about the what is the best way to convert from analog to digital using single conversion and TimerOne library. I got some troubles doing that. If someone can help i would like to explain whats going on.

have a look at tutorial/AnalogInput
can you give some details of the application? e.g. how often you wish to sample
what are you doing with the acquired samples, e.g. transmitting to a remote server?
what have you got working so far?

Ok this is my code:

#include <TimerOne.h>

byte dataincoming[3];
byte dataoutgoing[654];
int iterator = 0;

void setup()
{
USART_Setup();
ADC_Setup();
Enable_Global_Interrupts();
}

void Enable_Global_Interrupts()
{
//enable the global interrupts bit;
bitSet(SREG, 7);
//end;
}

void USART_Setup()
{
//asynchronous mode operation;
bitClear(UCSR0C, UMSEL00);
bitClear(UCSR0C, UMSEL01);
//end;

//set double speed for asynchronous operation;
bitSet(UCSR0A, U2X0);
//end;

//enable interrupt when reception complete;
bitSet(UCSR0B, RXCIE0);
//end;

//disable parity mode;
bitClear(UCSR0C, UPM00);
bitClear(UCSR0C, UPM01);
//end;

//1-stop bit;
bitClear(UCSR0C, USBS0);
//end;

//set number of data bits in the frame format;
bitSet(UCSR0C, UCSZ00);
bitSet(UCSR0C, UCSZ01);
bitClear(UCSR0B, UCSZ02);
//end;

//set baud rate to 117647 bauds;
UBRR0L = 0x10;
UBRR0H = 0x0;
//end;

//enable USART reception;
bitSet(UCSR0B, RXEN0);
//end;

//enable USART transmission;
bitSet(UCSR0B, TXEN0);
//end;
}

void ADC_Setup()
{
//select A0 as an input port for analog read;
bitClear(ADMUX, MUX0);
bitClear(ADMUX, MUX1);
bitClear(ADMUX, MUX2);
bitClear(ADMUX, MUX3);
//end;

//select the ADC reference to default (5V);
bitSet(ADMUX, REFS0);
bitClear(ADMUX, REFS1);
//end;

//disable digital feature for A0 port;
bitSet(DIDR0, ADC0D);
//end;

//set up prescaler factor to 16;
bitClear(ADCSRA, ADPS0);
bitClear(ADCSRA, ADPS1);
bitSet(ADCSRA, ADPS2);
//end;

//enable conversion complete interrupt;
bitSet(ADCSRA, ADIE);
//end;

//enable the ADC;
bitSet(ADCSRA, ADEN);
//end;
}

void Data_TX()
{
UDR0 = dataoutgoing[iterator];
iterator ++;
}

void Stop_Clear()
{
Timer1.stop();
iterator = 0;
}

ISR(USART_RX_vect)
{
while (bit_is_set(UCSR0A, RXC0))
{
dataincoming[iterator] = (byte)UDR0;
delay(20);
iterator ++;
}

iterator = 0;
}

ISR(ADC_vect)
{
dataoutgoing[iterator + 1] = ADCL;
dataoutgoing[iterator] = ADCH;
iterator += 2;
}

void Analog_Conversion()
{
bitClear(PRR, PRADC);
bitSet(ADCSRA, ADSC);
while (bit_is_set(ADCSRA, ADSC));
return;
}

void Capture_Send(byte period, byte measures)
{
Timer1.initialize((int)period);
noInterrupts();
Timer1.attachInterrupt(Analog_Conversion);
int iterations = (int)(327 / (pow(2, measures)));

for (int i = 0; i < iterations; i ++)
{
if(i == 0)
{
interrupts();
}
Timer1.start();
}

Stop_Clear();
Timer1.initialize(100);
noInterrupts();
Timer1.attachInterrupt(Data_TX);
delay(200);

for (int i = 0; i < iterations; i ++)
{
if(i == 0)
{
interrupts();
}

Timer1.start();
}

Stop_Clear();
}

void loop()
{
switch (dataincoming[0])
{
case 1:
{
delay(250);
UDR0 = (byte)(1);
dataincoming[0] = 0;
break;
}
case 2:
{
Capture_Send(dataincoming[1], dataincoming[2]);
dataincoming[0] = 0;
break;
}
}
}

I hope can help me to find my error

Read the how to use this forum-please read sticky to see how to properly post code. Remove useless white space and format the code with the IDE autoformat tool (crtl-t or Tools, Auto Format) before posting code.

If it is a compile error please include the entire error message. It is easy to do. There is a button (lower right of the IDE window) called "copy error message". Copy the error and paste into a post in code tags. Paraphrasing the error message leaves out important information.

If no compile error, tell us what the code does and how that differs from what you want. The more that we know the better we can help.

Thank for replay Mr. GroundFungus, its not a compile error. If you run this code do not throw an error but the data that supuse to measure its not correct. For example if i put a sinusoidal signal with a 5 volt of amplitude and a frecuency of 10kHz which its razonable for de ADC it suppouse to see a sinusoidal graph, which not happend

ling_chang:
For example if i put a sinusoidal signal with a 5 volt of amplitude and a frecuency of 10kHz which its razonable for de ADC it suppouse to see a sinusoidal graph, which not happend

What did happen?

It suppouse to display a sinusoidal wave graph....which dont

Ok this is the way i though the arduino code:
First i send a byte from desktop app and when arduino capture this data send back the same byte in order to stablish connection. Second step i send from te app the period in microseconds i.e. 20,30, 40 and 50 microseconds and a number indicating the number of measures to take, transoded from 0 to 5 to an 327 samples to 20 samples. This is the information sent freom the app to capture data. With this information the arduino must capture a number of measures at a frequency which period its the number sent from app.
I would like to know if i am using registers, ISR’s and secuency the right way

ling_chang:
It suppouse to display a sinusoidal wave graph....which dont

Yes, but what kind of wave does it send?

ling_chang:
Ok this is my code:

void Capture_Send(byte period, byte measures)

{
 Timer1.initialize((int)period);
 noInterrupts();
 Timer1.attachInterrupt(Analog_Conversion);
 int iterations = (int)(327 / (pow(2, measures)));
 
 for (int i = 0; i < iterations; i ++)
 {
   if(i == 0)
   {
     interrupts();
   }  
   Timer1.start();
 }

Stop_Clear();  
 Timer1.initialize(100);
 noInterrupts();
 Timer1.attachInterrupt(Data_TX);  
 delay(200);

for (int i = 0; i < iterations; i ++)
 {  
   if(i == 0)
   {
     interrupts();
   }
     
   Timer1.start();
 }

Stop_Clear();
}

That doesn’t make much sense to me. Why do you start Timer1 in many times in a loop like that?

My approach would be to attach the Timer1 Compare Match as a trigger to the ADC. This gives you a very accurate sampling rate.

You could try something like this:

/*
 * If the character '1' is received on the Serial input, responds with '1'.
 * 
 * If the character '2' is received on the Serial input, captures a number
 * of ADC samples at a fixed rate, and prints all of it to the serial ouptput.
 * 
 * Timer1 is used as a trigger for the ADC and allows you to select an 
 * accurate sampling frequency.
 * 
 * Pin PB5 (D13 on an UNO) is pulsed to verify the sampling rate using an 
 * oscilloscope.
 */


// Helpers

// Convert the prescaler factor to the correct bit pattern to write to the
// TCCR1B register.
// ATmega328P datasheet § 15.11 (TCCR1A/B)
constexpr uint8_t prescaler_to_CS_bits(uint16_t prescaler) {
  return prescaler == 1 ? 0b001 :
         prescaler == 8 ? 0b010 :
         prescaler == 64 ? 0b011 :
         prescaler == 256 ? 0b100 :
         prescaler == 1024 ? 0b101 :
         0;
}

// Set the prescaler of Timer1 to the given factor.
// Possible values: 1, 8, 64, 256, 1024
// ATmega328P datasheet § 15.11 (TCCR1A/B)
void set_prescaler(uint16_t prescaler) {
  uint8_t bits = prescaler_to_CS_bits(prescaler);
  TCCR1B &= ~(0b111 << CS10);
  TCCR1B |= bits << CS10;
}

// Configure Timer1 in Clear Timer on Compare Match (CTC) Mode.
// ATmega328P datasheet § 15.11 (TCCR1A/B)
void set_CTC_mode() {
  uint8_t modebits = 0b0100; // CTC
  TCCR1A &= ~(0b11 << WGM10);
  TCCR1B &= ~(0b11 << WGM12);
  TCCR1A |= ((modebits >> 0) & 0b11) << WGM10;
  TCCR1B |= ((modebits >> 2) & 0b11) << WGM12;
}

// -------------------------------------------------------------------------- //

// Configuration
constexpr uint16_t buffersize = 500; // Number of ADC samples to read

constexpr uint16_t prescaler = 1;
constexpr uint16_t period = 0x0C7F; // 5.000 kHz (200.00 µs)

// constexpr uint16_t period = 0x06DF; // 9.091 kHz (110.00 µs)
// constexpr uint16_t period = 0x06C4; // 9.233 kHz (108.31 µs) - Maximum

// Toggle pin PB5 (D13 on an UNO) to check the sampling rate on a scope:
// PB5 is made high when the ADC conversion starts, and goes low when the
// conversion is done.
#define PULSE_OUTPUT

// Sample frequency
constexpr float freq = 1. * F_CPU / prescaler / (1ul + period);

// Checks
static_assert(prescaler_to_CS_bits(prescaler) != 0, "Invalid prescaler");

void setup() {
  Serial.begin(1000000); // Fast UART
  Serial.print("Frequency (Hz): ");
  Serial.println(freq, 6);
  Serial.print("Period    (µs): ");
  Serial.println(1e6 / freq, 6);

  noInterrupts();

  set_CTC_mode();            // Timer1 CTC mode
  set_prescaler(prescaler);  // Timer1 Prescaler

  OCR1B = period;            // Compare B: Triggers ADC conversion
  OCR1A = period;            // Compare A: Resets Timer1

  ADMUX  |= 1 << REFS0;      // ADC Default reference
  ADMUX  |= 0 << MUX0;       // ADC channel 0
  ADCSRB |= 0b101 << ADTS0;  // ADC Trigger: Timer1 Compare Match B
  ADCSRA &= ~(1 << ADIE);    // ADC Interrupt Disable
  ADCSRA &= ~(1 << ADATE);   // ADC Auto Trigger Disable
  ADCSRA |= 1 << ADEN;       // ADC Enable

  // Do a first ADC conversion (first conversion is always slower)
  ADCSRA |= 1 << ADSC;       // ADC Start Conversion
  while (!(ADCSRA & (1 << ADIF))); // Wait for conversion to finish
  ADCSRA |= 1 << ADIF;       // Clear conversion ready flag

#ifdef PULSE_OUTPUT
  DDRB   |= 1 << PB5;        // Built-in LED Output mode
#endif

  interrupts();
}

void captureSend();

void loop() {
  switch (Serial.read()) {
    case '1':
      Serial.print('1');
      break;
    case '2':
      captureSend();
      break;
  }
}

// Buffers
volatile uint16_t buffer[buffersize];
volatile uint16_t *const buffer_end = buffer + buffersize;
volatile uint16_t *write_ptr = buffer;
volatile bool done = true;

#ifdef PULSE_OUTPUT
ISR(TIMER1_COMPB_vect) {
  PORTB |= 1 << PB5; // Turn on built-in LED
}
#endif

ISR (ADC_vect) {
  *write_ptr++ = ADC; // Store the ADC sample in the buffer

  if (write_ptr >= buffer_end) { // If the buffer is full
    ADCSRA &= ~(1 << ADATE);     // ADC Auto Trigger Disable
    ADCSRA &= ~(1 << ADIE);      // ADC Interrupt Disable
#ifdef PULSE_OUTPUT
    TIMSK1 &= ~(1 << OCIE1B);    // Timer1 Compare B Match Interrupt Disable
#endif
    write_ptr = buffer;          // Reset write index
    done = true;
  }
#ifndef PULSE_OUTPUT
  else {
    TIFR1 |= 1 << OCF1B; // Clear trigger source to start next conversion
  }
#else
  PORTB &= ~(1 << PB5); // Turn off built-in LED
#endif
}

void captureSend() {
  Serial.flush();
  delay(10); // Wait for incoming serial data interrupts to end

  noInterrupts();
  done = false;
  TCNT1 = 0;            // Timer1 Reset
  TIFR1  |= 1 << OCF1B; // Clear trigger source to start conversion
  ADCSRA |= 1 << ADATE; // ADC Auto Trigger Enable
  ADCSRA |= 1 << ADIE;  // ADC Interrupt Enable
#ifdef PULSE_OUTPUT
  TIMSK1 |= 1 << OCIE1B; // Timer1 Compare B Match Interrupt Enable
#endif
  interrupts();

  while (!done); // Wait until buffer is full

  for (uint16_t val : buffer) // Print the contents of the buffer
    Serial.println(val);
  Serial.println(":::::"); // End marker
}

This code uses the ADC interrupt to store the data, but you could also poll the ADIF flag in a loop to know when the ADC conversion is complete.

The maximum rate is around 9.2 kHz. One triggered ADC conversion takes 13.5 clock cycles @125 kHz (by default), and you also lose some time in the ISRs.
If you want the absolute maximum rate, you can use the ADC in free running mode (i.e. using triggering the ADC as soon as the previous conversion is finished). Then a conversion takes 13 clock cycles, which is a sampling rate of about 9.6 kHz.

Pieter

Thank for your post Mr. PieterP. I will run your code or adapt mine to yours. I did a simulation betwen proteus 8 and my app using Virtual Serial Port Tools and i realized that my code does not work thats why i needed some help. I have not some much experience coding whith registers of Atmega 328p. I do not know if you guys have tested yours circuits using this method i mean connecting proteus and your visual app

ling_chang:
I have not some much experience coding whith registers of Atmega 328p.

The ATmega328P datasheet does a great job at explaining how it all works. Read the section about the ADC first, then look at the section about Timer/Counter 1 (you want CTC mode).

ling_chang:
I do not know if you guys have tested yours circuits using this method i mean connecting proteus and your visual app

I have no idea what Proteus or Visual app are, I just tested it using the Serial monitor and plotter in the Arduino IDE, and an oscilloscope/signal generator.

Note that your original code sent the ADC readings as binary data, whereas my code prints them as ASCII data.

When i say visual app i mean an application i develop in Visual Studio using C# language where i command arduino from the app. Proteus is a virtual tool for design electric and electronics circuits for electrical and electronical engineer where you can debug your arduino code and see the register status