logo_elektromys.eu

/ AD Převodník III |

/ Free-run |

Třetí díl tutoriálu o AD převodníku věnujeme celý free-run módu. AD převodník v něm měří stále dokola. Tento režim se hodí pro zaznamenávání relativně rychlých dějů. Těmi mohou být například různé zvuky včetně odezvy ultrazvukových senzorů. Spojením free-run s již diskutovaným "Window comparatorem", můžete hlídat stav napětí se slušně nízkou latencí (méně 10us). Nastavení a ovládání free-run módu je vcelku jednoduché. Co už tak jednoduché není je zpracování dat. AD převodník v plné rychlosti může chrlit skoro půl megabajtu dat za sekundu, která buďto dokážete přímo zpracovat a nebo, což bude asi typičtější, je uložíte do RAM. My si v tomto tutoriálu předvedeme tři podobné způsoby využití. V prvním z nich si nastavíme nějakou "nižší" rychlost (84ksps) a data budeme ukládat v rutině přerušení. Díky tomu zůstane jádru čas věnovat se dalším činnostem. Pak si zkusíme ADC trochu přetaktovat a vyždímat z něj co nejvyšší rychlost. Ta nás donutí ukládat data blokujícím způsobem. Obě metody budou hodně náročné na RAM. Náš čip disponuje pouze 256B a protože 10bitové číslo spotřebuje 2B dat, máme v paměti teoreticky místo na něco málo přes 120 vzorků. Ve třetím příkladu si zkusíme zmenšit rozlišení na 8bit a rozšíříme si tím délku záznamu na dvojnásobek. Signál si ve všech případech zrekonstruujeme a podíváme se jak věrně se ho podařilo zachytit. Takže pojďme na věc.

Ve všech příkladech budeme používat pin PB4 (AIN9) jako analogový vstup. Na pin PC2 budeme přivádět spouštěcí signál, který odstartuje záznam. Abychom dobře viděli frekvenci převodů, vyvedeme si na PB2 informaci o průběhu vyčítání dat. Free-run mód se volí nastavením bitu FREERUN, ale samotné jeho nastavení ještě převod nespouští. První převod musíme spustit buď ručně zapsáním STCONV do ADC.COMMAND nebo eventem. Obě metody jsme si již odzkoušeli v předchozích dílech tutoriálu. Od tohoto okamžiku už běží ADC neustále. Po skončení každého převodu (o čemž nás informuje vlajka RESRDY) se automaticky spouští další. Každý převod se stává z volitelného množství cyklů pro vzorkování (o němž už byla řeč). Vzorkování trvá minimálně 2 cylky a jde zvětšit až na 2+15 v registru SAMPCTRL bity (SAMPLEN). Pak probíhá samotný převod který trvá 11 cyklů. Nejkratší doba převodu je tedy 13 cyklů. Časový rozestup mezi jednotlivými převody můžeme zvětšit skupinou bitů SAMPDLY (v registru CTRLD) v rozsahu 0 až 15. Díky tomu můžeme se vzorkovací frekvencí vcelku slušně manipulovat. Jistý komentář si zaslouží také vypínání free-run módu. Errata k naší Attiny416 uvádí že i po vynulování bitu FREERUN může převodník provést ještě jedno měření. Abychom se tohoto problému vyvarovali, nabádá nás datasheet k vypnutí celého ADC převodníku (čehož se držím). Mějte ale na paměti že vypnutí AD převodníku znamená typicky i vypnutí reference. Tu je tedy dobré nastavit tak ať běží vždy bez ohledu na chod ADC. Errata nás také nabádá že pokud chceme manipulovat s bity SAMPDLY (nastavují časový odstup převodů za sebou), měli bychom udržovat SAMPLEN nulové - tedy používat nejkratší vzorkovací čas. Pokud tedy zpracováváte signál s vyšší impedancí a potřebujete delší vzorkovací časy a přitom chcete využívat free-run mód, mějte na paměti že tu na vás čeká nějaký error. A teď to pojďme všechno vyzkoušet.

/ A) ADC Free-run s přerušením |

V prvním příkladu si zkusíme převádět rychlostí 84ksps. Tedy rychlostí dost nízkou abychom stíhali data ukládat v přerušení. Na výsledky si alokujeme pole adc_data[100]. Bitem RESRDY v INTCTRL si povolíme přerušení. clock pro ADC zvolím jako 20MHz/16 = 1.25MHz, vzorkovací čas jako 2+2 a prodlevy nulové. Celkový převod by měl tedy trvat (13+2)/1.25M = 12us (83.3ksps). Akumulaci nezapínáme, protože by nás zdržovala. Každopádně by to ale nemusel být špatný nápad. Taktovat převodník vyšší frekvencí a využít akumulaci k průměrování. V rutině přerušení pomocí PB2 signalizujeme dokončení převodu a uložení dat. Program v jednoduché smyčce čeká na příchod sestupné hrany na PC0 - na trigger a spustí převod. Po nasbírání všech 100 vzorků resetujeme ADC čímž ho zastavíme a pomocí proměnné stop oznámíme hlavní smyčce že je hotovo. Po sběru jedné dávky dat bychom ji mohli nějak zpracovat nebo poslat k analýze do počítače. Protože by to ale značně znepřehlednilo náš výukový kód, využijeme ke kontrole dat debugger. Program zastavíme na řádku stop = 0;. Využít k tomu můžeme breakpoint nebo příkaz Run to cursor. V okně Watch (viz obrázek pod zdrojákem) si zobrazím obsah pole. Označím všechny jeho prvky (Click na začátek a SHIFT+Click na konec) a pravým tlačítkem vyberu Copy value. Pak můžu data vložit do textové souboru, nebo tabulkového procesoru nebo kamkoli chci a zpracovat je.

/* tutorial TinyAVR 1-Series
* ADC3 - free-running mód 10bit 83ksps s IRQ
* aplikace čeká na vnější trigger (sestupná hrana na PC0)
* pak spustí převod s frekvencí 83.3ksps (T=12us)
* v přerušení od dokončeného převodu ukládá data do pole
* po získání 100 vzorků se zastaví a čeká na další trigger
* data by mohla vypsat na UART (kdo chce, může dodělat)
* nám jako důkaz stačí, prohlédnout si data pomocí debuggeru
*/

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

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

volatile uint16_t adc_data[100]; // tady ukládáme výsledky převodu
volatile uint8_t idx=0; // index v poli s výsledky
volatile uint8_t stop=0; // indikuje dokončení sběru dat

int main(void){
 clock_20MHz(); // taktujeme na plný výkon
 PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // PB4 vstup pro ADC (vypnu vstupní buffer)
 PORTB.DIRSET = PIN2_bm; // PB2 indikuje rutinu přerušení od ADC
 PORTC.DIRCLR = PIN0_bm; // PC0 vstup pro trigger signál
 PORTC.PIN0CTRL = PORT_PULLUPEN_bm; // PC0 může být i odpojený... takže pullup
 init_adc(); // konfigurovat ADC a referenci
 sei(); // globální povolní přerušení

 while (1){ 
  while(!(PORTC.IN & PIN0_bm)){} // čekej dokud je na triggeru log.0
  while(PORTC.IN & PIN0_bm){} // čekej dokud je na triggeru log.1
  // přišla sestupná hrana, spusť první převod (další se už spouští samy)
  ADC0.COMMAND = ADC_STCONV_bm;
  while(!stop){} // čekej na dokončení převodu
   stop = 0; // tady si můžeme aplikaci zastavit (breakpoint) a prohlédnout si data
 }
}


ISR(ADC0_RESRDY_vect){
 PORTB.OUTSET = PIN2_bm; // indikuje vstup do rutiny přerušení
 // pokud máme ještě kam dávat data
 if(idx<sizeof(adc_data)/sizeof(adc_data[0])){
  adc_data[idx] = ADC0.RES; // tak je uložíme
  idx++; // inkrementujeme počítadlo dat
 }
 else{ // jinak je konec sběru dat a ...
  ADC0.CTRLA &=~ ADC_ENABLE_bm; // ... zastavíme ADC
  ADC0.INTFLAGS = ADC_RESRDY_bm;  // vyčistíme vlajku
  idx=0; // vynulujeme počítadlo dat
  stop=1; // dáme "mainu" vědět, že jsme ukončili sběr
  ADC0.CTRLA |= ADC_ENABLE_bm; // zapneme převodník (zatím nepřevádí, čeká na STCONV)
 }
 PORTB.OUTCLR = PIN2_bm; // indikujeme konec práce v rutině přerušení
}

void init_adc(void){
 VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc; // vybrat referenci pro ADC
 VREF.CTRLB |= VREF_ADC0REFEN_bm; // nevypínat referenci s vypnutým ADC 
 ADC0.CTRLA = ADC_RESSEL_10BIT_gc  | ADC_FREERUN_bm; // rozlišení 10bit, freerunning mod
 ADC0.CTRLB = ADC_SAMPNUM_ACC1_gc; // bez akumulace
 // vzorkovací kondenzátor 5pF, prescaler 20M/16 = 1.25MHz, použít vnitřní referenci
 ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV16_gc | ADC_REFSEL_INTREF_gc;
 ADC0.MUXPOS = ADC_MUXPOS_AIN9_gc; // zvolit vstup pro ADC (PB4)
 ADC0.SAMPCTRL = 2; // 2+2 = 4 periody 3.2us) vzorkovací čas
 ADC0.INTCTRL = ADC_RESRDY_bm; // povolit přerušení (od dokončení převodu) 
 ADC0.CTRLA |= ADC_ENABLE_bm; // spustit ADC (převod ještě neběží)
 _delay_us(25); // počkat na stabilizaci reference
}

// Nastaví clock 20MHz (z interního 20MHz bez děličky)
void clock_20MHz(void){
 // v případě potřeby zde dočasně vypněte přerušení
 _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc); // vybírá 20MHz oscilátor
 _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0);  // vypne prescaler (děličku)
}
Ukázka jak lze pomocí debuggeru zkopírovat obsah pole do počítače
Celkový pohled na průběh měření
TRIG - spouštěcí signál z generátoru (PC0)
IRQ - informace o zpracování dat (PB2)
modrá - měřený signál z generátoru (PB4)
Detail z něhož je patrné trvání rutiny přerušení i rychlost vzorkování
Rekonstruovaný signál. Z nedostatku času a hloupé roztržitosti jsem si nestáhnul původní data z osciloskopu a nemohu tedy nabídnout srovnání.

Na další serii obrázků si můžete prohlédnout průběh našeho pokusu. Na kanálu TRIG je příchozí trigrovací signál (z generátoru). Linka IRQ ukazuje vstupy do rutiny přerušení a na druhém oscilogramu můžeme vidět že rychlost je předpokládaných 84ksps. Všimněte si také že čtení a ukládání dat nezabere víc jak 1.7us. Zdá se tedy že je program vytížen jen málo. Vzpomeňte si ale na tutoriál o externím přerušení. Tam jsme si ukázali že samotný vstup do rutiny přerušení sebere další mikrosekundu, kterou takhle snadno nevidíme. Skutečné vytížení je tedy vyšší než napovídá oscilogram. Měřený průběh (modrý) pochází také z generátoru a je to kus funkce sin(x)/x (alias "sync"). Poslední obrázek nám potvrdí že měření proběhlo úspěšně, rekonstruovaná data (jednoduchým skriptem v octave) odpovídají původním. Až teď mě napadlo že jsem mohl do grafu přiložit i data získaná osciloskopem. Srovnání by pak ukázalo jak moc úspěšní jsme byli. Bohužel, času se nedostává ...

/ B) ADC Free-run 10bit 192ksps |

Ve druhém příkladu zkusíme z převodníku vyždímat co nejvyšší rychlost. Datasheet uvádí, že maximální vzorkovací frekvence pro 10bit převod je 115ksps (s referencí 0.55V je citelně menší 20ksps). Maximální clock pro ADC je pro stejnou situaci 1.5MHz. Takže našemu převodníku pustíme clock 2.5MHz a vzorkovací frekvenci nasadíme na maximum tedy 2.5MHz/13 = 192ksps. S čipem taktovaným na 20MHz se už nedá jít výš. Další možný clock pro ADC je totiž 5MHz a při takovém přetaktování už nepracuje. Připojením externího clocku, případně "rozkalibrováním" 20MHz RC oscilátoru bude nejspíš možné rychlost ještě zvýšit. Takové tempo (192ksps) generuje 384kB/s dat a pokud máme v paměti místo jen na 100 vzorků (200B), můžeme uložit časový záznam odpovídající nanejvýš 520us. Tady se katastrofálně projevuje nedostatek paměti naší Tiny416 (nadále tedy visí ve vzduchu otázka proč není na kitu třeba Tiny3216). Pokud bychom chtěli k takové frekvenci využít předchozí příklad asi bychom narazili. Na zpracování každého vzorku máme jen 5us a jen marže pro vstup a výstup z rutiny přerušení by nás stála desítky procent výpočetního času ! Přerušení tedy nepoužijeme a data budeme ukládat blokujícím způsobem. Budeme čekat ve smyčce na dokončení převodu. Jinak se náš příklad v podstatě neliší.

/* tutorial TinyAVR 1-Series
* ADC3 - free-running mód 10bit 192ksps (overclocking)
* aplikace čeká na vnější trigger (sestupná hrana na PC0)
* pak spustí převod s frekvencí 192ksps (T=5.2us)
* v blokující smyčce čeká na dokončení převodu a ukládá výsledky do pole
* po získání 100 vzorků se zastaví a čeká na další trigger
* data by mohla vypsat na UART (kdo chce, může dodělat)
* nám jako důkaz stačí, prohlédnout si data pomocí debuggeru
*/

/* 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]; // tady ukládáme výsledky převodu
volatile uint8_t idx=0; // index v poli s výsledky

int main(void){
 clock_20MHz(); // taktujeme na plný výkon
 PORTB.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // PB4 vstup pro ADC (vypnu vstupní buffer)
 PORTB.DIRSET = PIN2_bm; // PB2 indikuje rutinu přerušení od ADC
 PORTC.DIRCLR = PIN0_bm; // PC0 vstup pro trigger signál
 PORTC.PIN0CTRL = PORT_PULLUPEN_bm; // PC0 může být i odpojený... takže pullup
 init_adc(); // konfigurovat ADC a referenci

 while (1){ 
  while(!(PORTC.IN & PIN0_bm)){} // čekej dokud je na triggeru log.0
  while(PORTC.IN & PIN0_bm){} // čekej dokud je na triggeru lo.1
  // přišla sestupná hrana, spusť první převod (další se už spouští samy)
  ADC0.COMMAND = ADC_STCONV_bm;
    // dokud je místo v poli, čti a ukládej výsledky převodu
  for(idx=0;idx<sizeof(adc_data)/sizeof(adc_data[0]);idx++){
   while(!(ADC0.INTFLAGS & ADC_RESRDY_bm)){} // čekej na dokončení převodu
   PORTB.OUTSET = PIN2_bm; // dej vědět že zpracováváš data
   adc_data[idx] = ADC0.RES; // ulož výsledek převodu
   PORTB.OUTCLR = PIN2_bm; // dej vědět že jsi data zpracoval
   }
    // až uložíš všech 100 vzorků, skonči     
  ADC0.CTRLA &=~ ADC_ENABLE_bm; // ... zastavíme ADC
  ADC0.INTFLAGS = ADC_RESRDY_bm;  // vyčistíme vlajku
  ADC0.CTRLA |= ADC_ENABLE_bm; // zapneme ADC (zatím nepřevádí, čeká na STCONV)
 }
}

void init_adc(void){
 VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc; // vybrat referenci pro ADC,
 VREF.CTRLB |= VREF_ADC0REFEN_bm; // nevypínat referenci s vypnutým ADC 
 ADC0.CTRLA = ADC_RESSEL_10BIT_gc  | ADC_FREERUN_bm; // rozlišení 10bit, freerunning mod
 ADC0.CTRLB = ADC_SAMPNUM_ACC1_gc; // bez akumulace
 // vzorkovací kondenzátor 5pF, vnitřní reference, clock 20M/8 = 2.5MHz (limit je 1.5MHz !)
 ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV8_gc | ADC_REFSEL_INTREF_gc;
 ADC0.CTRLD = 0; // delay mezi měřením 0 (snažíme se o nejvyšší rychlost)
 ADC0.MUXPOS = ADC_MUXPOS_AIN9_gc; // zvolit vstup pro ADC
 ADC0.SAMPCTRL = 0; // 2+0 = 2 periody (0.8us) vzorkovací čas (nejkratší možný)
 ADC0.CTRLA |= ADC_ENABLE_bm; // spustit ADC
 _delay_us(25); // počkat na stabilizaci reference
}

// Nastaví clock 20MHz (z interního 20MHz bez děličky)
void clock_20MHz(void){
 // v případě potřeby zde dočasně vypněte přerušení
 _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc); // vybírá 20MHz oscilátor
 _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0);  // vypne prescaler (děličku)
}

Oscilogram opět dokládá že vzorkovací frekvence je předpokládaných 192ksps. A rekonstruovaný signál se zdá být v pořádku. Dá se tedy říct že nám přetaktování vyšlo. Je otázkou zda by šlo všechno tak hladce i za ztížených podmínek jako třeba nižším napájecím napětí nebo při vyšších teplotách.

Celkový pohled na průběh měření při přetaktování na 192ksps
TRIG - spouštěcí signál z generátoru (PC0)
IRQ - informace o zpracování dat (PB2)
modrá - měřený signál z generátoru (PB4)
Rekonstruovaný signál z pokusu se 192ksps. (opět mě mrzí absence srovnání s daty z osciloskopu)

/ C) ADC Free-run 8bit 192ksps |

I třetí, možná poněkud nadbytečný, příklad bude variací na stejné téma. Vyjdeme ze druhého příkladu a snížíme rozlišení na 8bit (zdrojový kód). Díky tomu můžeme provést záznam o dvojnásobné délce. Průběh vstupního signálu si můžete prohlédnou na oscilogramu. Na rekonstrukci vás správně zarazí "uříznutá" špička prvního pulzu. Během jejího velmi krátkého trvání nestihl proběhnout ani jeden převod. To je jeden z praktických důsledků porušování vzorkovacího teorému. Zkoumaný signál obsahuje ve svém spektru vysokofrekvenční složky (zodpovědné za rychlé změny v signále - například tyto špičky), my ale vzorkujeme tak pomalu, že tyto složky nemůžeme spolehlivě zachytit. I přes to že druhá špička vypadá správná, vůbec být nemusí (a nejspíš není), nemáme žádnou záruku že vzorkování AD převodu proběhlo na jejím vrcholu. Na tomto místě mě opět mrzí že jsem si neschoval osciloskopická data abych je vynesl do rekonstruovaného grafu.

Celkový pohled na průběh měření při přetaktování na 192ksps
TRIG - spouštěcí signál z generátoru (PC0)
IRQ - informace o zpracování dat (PB2)
modrá - měřený signál z generátoru (PB4)
Patrná vzorkovací frekvence 192ksps. Zpracování dat je díky 8bitové šířce o něco rychlejší jak v předchozím příkladě.
Na rekonstruovaném signálu jsou patrné hrubé nedostatky plynoucí z "nízké" vzorkovací frekvence. Nebo naopak z příliš rychlého signálu. Až na výjimky není možné takový signál naším ADC spolehlivě měřit.
Výjimkou může být chytré měření mnoha period a skládání obrazu "na sebe" (což je jen domněnka !)

| Závěr /

Jsem rád že jste tento "rozvláčný" díl dočetli až sem a seznámili se s několika způsoby jak lze využít free-run mód AD převodníku. Kdyby někdo z vás měl chuť experimentovat, přetaktoval ADC ještě na vyšší frekvence (např zmíněným rozladěním RC oscilátoru nebo s pomocí externího clocku) a seznámil nás s výsledky, rád je zde zveřejním.

| Odkazy /

Home
| V1.01 20.2.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /