[TUT][SOFT] AVR 0/1-series ADC free-running batch

Go To Last Post
11 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

During prepare on lectures (basic course of micrcocontrollers for physicist) i've create some demonstration codes and i believe that can be useful for others. This example demonstrates program which wait for trigger signal (falling edge at PC0) and start batch of 100 AD conversions with 10bit resolution and with little bit overclocked rate 192ksps. It should test extreme possibilities of ADC (and you will see ADC will not disappoint us). Application create pulses at PB2 informing us about data processing and enabling measure ADC rate. Data handling is done in blocking way in main while loop (entering into IRQ routine takes so much time in these time-tight situation). After each batch is ADC restarted and who wants, can observe data by debugger (or can write simple UART routine and sends them into PC terminal). Chip is clocked at full speed of 20MHz. ADC is overclocked at 2.5MHz. Conversion time is fixed 13 ADC tics, additional sampling time is zero (that means sampling takes 2 tics from mentioned 13). That leads into 2.5/13 = 192ksps. Delay between conversions is zero because i've tray acheive posible fastest conversion rate (who wants can use them to slight change of conversion rate). From same reason (speed) application dont use "acummulation" (oversampling). In this examples is selected 1.5V reference. Because freerun mode should be stoped by disabling ADC (errata from attiny416 datasheet) we should eneble "VREF run forever" function. If not, reference will be disabled with disabling ADC and we should to waint to its stabilisation after each ADC enabling.

 

/* tutorial TinyAVR 1-Series
* ADC3 - free-running mode 10bit 192ksps (overclocking)
* Program waits for trigger (falling edge at PC0)
* then starts continuous ADC conversion with rate ~192ksps
* in main loop waits until each conversion is done and save results to array "adc_data"
* after burst of 100 samples reset ADC and wait for trigger again
* ADC input is AIN9 (PB4), at PB2 output indicates data processing (for freq. measurment)
* Acquiered data can be observed by debuger. Who needs, can send data to PC over UART.
* Acqusition rate can be adjusted in ADC.CTRLD...
*/

/* Ve fuses zvolen 20MHz oscilátor */
#define F_CPU 20000000UL
#include <util/delay.h>
#include <avr/io.h>

void clock_20MHz(void);
void init_adc(void);

volatile uint16_t adc_data[100]; // array to store ADC results
volatile uint8_t idx=0; // index in adc_data

int main(void){
 clock_20MHz(); // clock chip at full speed (20MHz)
 PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // PB4 input for ADC (disable input buffer)
 PORTB.DIRSET = PIN2_bm; // PB2 output for indicating time of each conversion
 PORTC.DIRCLR = PIN0_bm; // PC0 trigger input
 PORTC.PIN0CTRL = PORT_PULLUPEN_bm; // fo sure if somebody left input unconnected
 init_adc(); // setup ADC and voltage reference

 while (1){
  while(!(PORTC.IN & PIN0_bm)){} // wait until trigger is L
  while(PORTC.IN & PIN0_bm){} // wait until trigger is H
  // falling edge comes. Start first conversion (next will start automaticaly)
  ADC0.COMMAND = ADC_STCONV_bm;
  // for all 100 samples
  for(idx=0;idx<sizeof(adc_data)/sizeof(adc_data[0]);idx++){
   while(!(ADC0.INTFLAGS & ADC_RESRDY_bm)){} // wait until conversion is done
   PORTB.OUTSET = PIN2_bm; // info as that conversion ends and processing data
   adc_data[idx] = ADC0.RES; // save result
   PORTB.OUTCLR = PIN2_bm; // processing is done, stop indicating
   }
  // after all 100 samples ...
  ADC0.CTRLA &=~ ADC_ENABLE_bm; // ... stop ADC (it stops free-running mode)
  ADC0.INTFLAGS = ADC_RESRDY_bm; // for sure, clear flag
  ADC0.CTRLA |= ADC_ENABLE_bm; // start ADC again (doesnt start a conversion)
 }
}

void init_adc(void){
 VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc; // select voltage reference for ADC
 VREF.CTRLB |= VREF_ADC0REFEN_bm; // reference should run even with disabled ADC
 ADC0.CTRLA = ADC_RESSEL_10BIT_gc  | ADC_FREERUN_bm; // 10bit resolution, freerunning mode
 ADC0.CTRLB = ADC_SAMPNUM_ACC1_gc; // no sample acumulation (no averaging or oversampling)
 // sampling capacitor 5pF, internal reference, clock 20M/8 = 2.5MHz (specs limit is 1.5MHz !)
 ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV8_gc | ADC_REFSEL_INTREF_gc;
 ADC0.CTRLD = 0; // delay between ADC conversion (0 => running as fast as posible)
 ADC0.MUXPOS = ADC_MUXPOS_AIN9_gc; // input for ADC is AIN9 (PB4)
 ADC0.SAMPCTRL = 0; // shortest possible sampling time (2+0 ADC ticks)
 ADC0.CTRLA |= ADC_ENABLE_bm; // enable ADC (doesnt start conversion)
 _delay_us(25); // wait for voltage reference stabilisation
}

//Setup 20MHz clock 20MHz from internal RC oscillator
void clock_20MHz(void){
 // may disable interruption around this code
 _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc); // select 20MHz oscillator
 _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // disable prescaler (route 20MHz directly to core)
}

As a proof that ADC realy runs at 192ksps including oscillograph. Signal "TRIG" shows falling edge which starts conversion batch. Signal "BUSY" indicats PB2 status (processing ADC data at end of each conversion). Blue signal is analog input.
 

oscillograph of waveform, trigger and program reaction

and reconstruction of measured signal by simple octave script (sorry for czech axis labels ... x-axis is time in microseconds, y-axis voltage in volts)

reconstructed waveform

Data was gathered simply from debugger (Watch function on adc_result array -> mark all data -> copy data -> paste info txt file). Reaction time on trigger is fast  (i've tested 5 batches with no significant jitter). Hmm... ive forgot measure jitter exactly :(

If this "commented example" fits into tutorial section and if you want, i can add some other examples. Like working example of oversampling (to verified precise 11.5bit result from this 10bit ADC) and many others...

best wishes
Michal Dudka

edit: corrected "clock_20MHz()" in source code to use macros from xmega.h

Last Edited: Thu. Feb 14, 2019 - 01:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In my opinion, this is an useful example, since information on AVR-0/1 is not exactly abundant.

 

I would only change one thing, since this is meant as a tutorial, it's more correct to use the _PROTECTED_WRITE macro to access protected registers:

 

//Setup 20MHz clock 20MHz from internal RC oscillator
void clock_20MHz(void){
    // may disable interruption around this code
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc); // select 20MHz oscillator
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // disable prescaler (route 20MHz directly to core)
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice idea, but i can't find it in (up to date) header file. It looks like that macro is written "only" for Xmegas... 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks a lot Michal, really interesting tutorial.

 

gripennn wrote:

Nice idea, but i can't find it in (up to date) header file. It looks like that macro is written "only" for Xmegas... 

 

which atdf version you are using ? maybe the latest atdf 1.3.229 will solve the problem. http://packs.download.atmel.com/

Looking forward for more tutorials.

 

Regards,

Moe

 

EDIT: link to Packs repository

Last Edited: Thu. Feb 14, 2019 - 09:13 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have latest version (1.3.229). Do you have that macro in header file ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have something else:

 

atdf 1.3.229 its done like this:

/*suppose you want to write ccp*/
int8_t CLKCTRL_init()
{

	ccp_write_io((void *)&(CLKCTRL.OSC32KCTRLA), 1 << CLKCTRL_RUNSTDBY_bp /* Run standby: enabled */);

	return 0;
}

and the :

ccp_write_io.............

is implemented as statis inline:

 

static inline void ccp_write_io(void *addr, uint8_t value)
{
	protected_write_io(addr, CCP_IOREG_gc, value);
}

its easier to include ccp.h and then you can use the first code up without digging inside.

 

the protected_write_io can be implemented like this:

extern void protected_write_io(void *addr, uint8_t magic, uint8_t value);

but for this in atdf 1.3.229 you can include protected_io.h and then its all done

EDIT: comment code

EDIT 2: protected_write_io function added

 

Hope it works for you!

Moe

 

Last Edited: Thu. Feb 14, 2019 - 01:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

gripennn wrote:
but i can't find it in (up to date) header file. It looks like that macro is written "only" for Xmegas... 
But these are Xmega's so the usual inclusion of avr/io.h gives access to that macro.

 

io.h contains:

#if __AVR_ARCH__ >= 100
#  include <avr/xmega.h>
#endif

and also:

D:\test>avr-gcc -mmcu=attiny814 -dM -E - < NUL | grep ARCH
#define __AVR_ARCH__ 103

103 is >= 100. Also:

 

 

That's what you get if you build an empty main{} with an include of just <avr/io.h> for tiny814. As you can see xmega.h is included.

 

As I say AVR0/AVR-1 are Xmega chips.

 

The one rather sad thing is that the documentation at:

 

https://www.nongnu.org/avr-libc/...

 

does not mention xmega.h and hence no mention of:

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually Cliff, I havent read it anywhere. I was lucky to read it here....from you guys.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are right. I've been confused by Atmel studio. When i write macro _PROTECTED_WRITE to source code, IDE does not recognise it (with no response on "go to implementation")


but when i've open and close xmega.h file, everything start working fine

Thanks for Help. I'll edit source code in example.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

gripennn wrote:

Nice idea, but i can't find it in (up to date) header file. It looks like that macro is written "only" for Xmegas... 

 

The AVR 0/1 are xmegas. Kind of. The protection mechanism is the same, so the _PROTECTED_WRITE macro inside xmega.h works fine for the AVR 0/1 series, too.

 

You can compare the datasheets:

 

xmega A series:

 

tiny3216:

 

edit: Oh, while I was writing this you tested _PROTECTED_WRITE. Yeah, the code parser in AS7 sometimes has that problem of not highlighting stuff it should, I also noticed it.

Last Edited: Thu. Feb 14, 2019 - 01:34 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Never trust that red underlining thing - that's just a "helper" but sometimes it does not help. The true test of whether something is "there" is to use the function, build it and see if you get "it's not here" errors.

 

(real VS2015 - that AS7 is based on - is much more intelligent with "IntelliSense" and that really does know (by parsing all the #include's) what does/doesn't exist but the thing in AS7 (VassistX is it?) is not quite as good - but at least it's free!)

Last Edited: Thu. Feb 14, 2019 - 01:49 PM