Jump to content
ELFORUM - Forumul electronistilor

Numarator de impulsuri cu atmega16


Guest aandrei

Recommended Posts

Guest aandrei

Salut prieteni!

 

Sunt incepator in ATmega16 si va cer ajutorul intr-o problema la care stagnez de ~2 saptamani.

Cerinta este sa se programeze un uC Atmega16 (4MHz) ca sa numere impulsurile primite in intervalul 10-10.000 N/0.1 secunde,

si sa se trimita pe seriala in formatul XX.XX K_IMP (kilo impulsuri).

 

Problema ar parea una banala, si chiar este pana la un anumit punct.

O sa va explic ce am facut pana acum si daca m-ati putea ajuta v-as fi recunoscator.

Consider ca rezolvarea acestei probleme ar fi utila si pentru ceilalti utilizatori ai forumului.

 

- 10-10.000 N/0.1 secunde => frecventa de intrare de 100Hz-100kHz

Pentru crearea bazei de timp de 0.1 secunde (100 ms) folosesc Counter0 in mod CTC (clear timer on compare match), cu prescaler de 64 si valoarea registrului de comparare OCR0=249.

Astfel, frecventa de intrare de 4MHz este divizata hardware+software la o frecventa de iesire de 10Hz (cat avem nevoie pentru baza de timp de 100ms):

- hardware cu ajutorul prescalerului: 4000000(Hz)/64=62500(Hz)

- software prin incrementarea unei variabile (var) in intreruperea de compare match a Counterului 0, care se va apela cand se numara de la 0 la OCR0=249, dupa care counterul porneste de la 0 (adica 250 pasi): 62500(Hz)/250=250(Hz) => var=250

- frecventa de 10 Hz se obtine atunci cand var==25.

 

- Pentru numarare am folosit Timer1 in Normal Mode, cu clock de la semnalul de intrare (provenit de la generatorul de semnal de la care introduc semnalul) pe front crescator pe pinul T1;

 

- Inceperea numararii de catre timer1 trebuie sincronizata cu baza de timp; pentru asta imi activez intreruperile externe pe pinul INT1, pe care trimit acelasi semnal de masurat, care se vor activa pe frontul crescator; in momentul executarii primei intreruperi, pe primul front crescator, resetez si pornesc counter0 si timer1 (timer1 este setat sa numere tot pe frontul crescator), sincronizand numararea cu baza de timp la inceputul intervalului de 0.1 sec; in acelasi timp imi dezactivez intreruperile externe;

 

- Citirea valorii din timer1 se face in functia de tratare a evenimentului de compare match din counter0 (TCNT0=OCR0), atunci cand incrementez var si verific daca am obtinut 0.1 secunde.

 

Pana aici totul bine si frumos... problema este la expirarea timpului de 0.1 secunde, cand resetez Counter0 pentru o noua numaratoare: in acest moment, in 99.99% din cazuri ma voi afla intre 2 fronturi crescatoare (pe care numar in timer1), ceea ce inseamna ca imi ramane un rest dintr-un impuls pe care nu il voi numara. Resturile astea sunt foarte mari la frecvente mici, cand perioadele de clock sunt mari, si putin mai mici la frecvente mari.

 

Problema este ca nu pot numara exact numarul de impulsuri, de ex. pentru fIN=100Hz imi numara 98 impulsuri (in loc de 10), samd. Alta consecinta a faptului ca impulsurile nu sunt numarate precis este faptul ca numarul asta oscileaza (ex. 97-98).

 

In programul principal fac in permanenta polling sa vad daca am citit valoarea din timer1 (numarul de impulsuri, semnalat printr-un flag), caz in care preiau numarul (variabila globala), il formatez si il trimit pe seriala. Tot codul asta se executa asincron fata de celelalte operatii si dureaza destul de putin, de ordinul a zece microsecunde; se incadreaza in intervalul de 100ms, deci asta nu ar fi o problema.

 

Atasez in continuare codul sursa si daca nu am explicat prea bine va rog sa-mi mai cereti detalii.

#include <iom16.h>#include <inavr.h>#include <intrinsics.h>// definire frecventa oscilator#define FOSC 4000000// definire rata de transfer pe seriala#define BAUD 19200// calcul valoare pt. registrul UBRR#define MYUBRR FOSC/16/BAUD-1// variabila globala pt. numararea situatiilor de "Compare Match" din Counter0unsigned int compareMatches;// flag pentru semnalarea obtinerii numarului de impulsuri din timer1unsigned int usartReady;// variabila globala ce va contine numarul de impulsuri masuratunsigned int pulses;// initializare a transmisiei serialevoid USART_Init( unsigned int ubrr ){	// setare rata de transfer (baud rate) - bps	UBRRH = (unsigned char)(ubrr>>8);	UBRRL = (unsigned char)ubrr;	// activare transmisie pe seriala	UCSRB = (1<<TXEN);	// setare format cadru (frame): 8 biti de date, 1 bit de stop, fara paritate	// URSEL - Register Select - trebuie sa fie 1 cand se scrie in UCSRC	UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);	// Setarea directiei pinului TXD ca iesire	DDRD |= (1<<PD1);}// functie pentru transmisia pe serialavoid USART_Transmit( unsigned char data ){	// asteapta pana cand bufferul de transmisie este gol	while ( !( UCSRA & (1<<UDRE)) )		;	// adaugare date in registru si trimitere pe seriala	UDR = data;}// functie asociata intreruperii de Compare Match a Counterului 0#pragma vector = TIMER0_COMP_vect__interrupt void TIMER0_COMP_interrupt(void){	// incrementez numarul de egalitati la comparare	compareMatches++;	// daca au trecut 0.1 secunde	if(compareMatches==25)	{		// citesc valoarea din timer1		pulses = TCNT1;		// anunt ca numarul de impulsuri a fost citit		usartReady = 1;		// resetez numarul de comparatii		compareMatches=0;		// validez intreruperile pe INT1		GICR |= (1<<INT1);		GIFR |= (1<<INTF1);	}}// functie asociata intreruperii provocate de un front crescator// pe pinul INT1#pragma vector = INT1_vect__interrupt void INT1_interrupt(void){	// URMEAZA SINCRONIZAREA TIMER1 CU TIMER0	// resetez timer1	TCNT1 = 0;	// resetez timer0	TCNT0 = 0;	// invalidez intreruperile pe INT1	GICR &= ~(1<<INT1);	GIFR|=(1<<INTF1);}// initializare Timer0 in mod Countervoid Timer0_Init(){	// setare mod CTC - Clear Timer on Compare Match	TCCR0 |= (1<<WGM01);	// setare clock de la prescaler -> clkIO/64	TCCR0 |= (1<<CS01)|(1<<CS00);	// setare registru de comparare	OCR0=249;	// activare intreruperi de Compare Match	TIMSK |= (1<<OCIE0);}// initializare Timer1void Timer1_Init(){	// pt. mod counter - WGM13:0 = 0	// setare sursa de clock externa pe pinul T1 cu clock pe frontul crescator	TCCR1B |= (1<<CS12) | (1<<CS11) | (1<<CS10);}// initializare intreruperi externe pe INT1void ExternalInterrupts_Init(){	// setare directie pin pentru intreruperi externe INT1	DDRD &= ~(1 << PD3);	// Setare registru de control MCU pentru activarea	// Frontul crescator pe INT1 va genera o cerere de intrerupere	MCUCR |= (1 << ISC11)||(1 << ISC10);	// Activare intrerupere pe INT1	// GICR = General Interrupt Control Register	GICR |= (1 << INT1);}void main(void){	// dezactivare intreruperi inainte de intrarea in regiunea critiaca	__disable_interrupt();	// initializare unitate usart	USART_Init(MYUBRR);		// initializare timer0	Timer0_Init();  	// initializare timer1	Timer1_Init();	// resetare numar de egalitati la comparare	compareMatches=0;	// resetare flag pentru semnalarea obtinerii numarului din timer1	usartReady = 0;	// activare si initializare intreruperi externe	ExternalInterrupts_Init();	// activare intreruperi la iesirea din regiunea critica	__enable_interrupt();	// bucla infinita	while(1)	{		// daca numarul de impulsuri a fost citit din timer1		if(usartReady==1)		{			// resetez flagul			usartReady=0;						// formatarea si trimiterea pe seriala a numarului de impulsuri			// in formatul XX.XX K_IMP			USART_Transmit(pulses/10000+'0');			pulses%=10000;			USART_Transmit(pulses/1000+'0');			pulses%=1000;			USART_Transmit('.');			USART_Transmit(pulses/100+'0');			pulses%=100;			USART_Transmit(pulses/10+'0');			pulses%=10;			USART_Transmit(' ');			USART_Transmit('K');			USART_Transmit('_');			USART_Transmit('I');			USART_Transmit('M');			USART_Transmit('P');			// se reiau pasii de mai sus la fiecare 100 ms					}	}}
Link to comment
  • Replies 16
  • Created
  • Last Reply

Top Posters In This Topic

oscilarea aia se cheamă jitter și e din cauză că baza de timp a sursei tale de impulsuri și clockul principal al atmega-ului, divizat apoi în soft pentru numărare, nu sunt, și nu le vei putea obține niciodată perfect sincronizate. De unde provin impulsurile, apropo?

 

A doua problemă e că transmiți datele aiurea. Când vrei precizie, de obicei unitatea de achiziție (în cazul ăsta, atmega-ul) trimite brut locațiile din memorie, iar afișarea în formatul dorit ("00.00") e făcută de un PC, care are putere de calcul gârlă.

 

Inceperea numararii de catre timer1 trebuie sincronizata cu baza de timp;

Nu. Pur și simplu trebuie să trimiți la intervale constante - câte pulsuri s-au acumulat per unitatea de timp, după care resetezi acumulatorul. Varianta elegantă este să măsori lungimea unei perioade, astfel încât ai frecvența relativă a ultimului puls față de penultimul. Practic, se lasă un timer liber, și când apare o întrerupere de puls, se trimite cumva valoarea timerului, și se resetează timerul. Evident că pe serial nu va merge decât la frecvențe mici ale sursei

Link to comment
Guest aandrei
oscilarea aia se cheamă jitter și e din cauză că baza de timp a sursei tale de impulsuri și clockul principal al atmega-ului, divizat apoi în soft pentru numărare, nu sunt, și nu le vei putea obține niciodată perfect sincronizate. De unde provin impulsurile, apropo?

 

A doua problemă e că transmiți datele aiurea. Când vrei precizie, de obicei unitatea de achiziție (în cazul ăsta, atmega-ul) trimite brut locațiile din memorie, iar afișarea în formatul dorit ("00.00") e făcută de un PC, care are putere de calcul gârlă.

 

Inceperea numararii de catre timer1 trebuie sincronizata cu baza de timp;

Nu. Pur și simplu trebuie să trimiți la intervale constante - câte pulsuri s-au acumulat per unitatea de timp, după care resetezi acumulatorul. Varianta elegantă este să măsori lungimea unei perioade, astfel încât ai frecvența relativă a ultimului puls față de penultimul. Practic, se lasă un timer liber, și când apare o întrerupere de puls, se trimite cumva valoarea timerului, și se resetează timerul. Evident că pe serial nu va merge decât la frecvențe mici ale sursei

 

 

Sa inteleg ca sincronizarea incercata de mine nu este corecta? Resetez counter0 pe frontul crescator al semnalului, chiar in momentul in care pornesc si timer1 sa numere impulsurile, deci cred ca s-ar pierde cateva clockuri pentru instructiuni. Gresesc?

 

De unde provin impulsurile, apropo?

 

De la un generator de semnal, frecventa semnalului de intrare fiind cuprinsa intre 100Hz (10 impulsuri in 0.1 sec) si 100kHz (10.000 impulsuri in 0.1 sec).

 

Când vrei precizie, de obicei unitatea de achiziție (în cazul ăsta, atmega-ul) trimite brut locațiile din memorie, iar afișarea în formatul dorit ("00.00") e făcută de un PC, care are putere de calcul gârlă.

 

Ma intereseaza ca numarul de impulsuri sa fie numarat corect, adica la o frecventa de intrare de 1320 Hz, voi numara 132 impulsuri in 0.1 secunde si voi trimite pe seriala "00.13 K_IMP", ceea ce este corect din punct de vedere al problemei de fata. Partea proasta e ca, pentru 1390 Hz de ex., imi va trimite "00.14 K_IMP", ceea ce inseamna ca undeva am erori.

Link to comment

Nu înțeleg exact de ce vrei să te sincronizezi la modul ăsta, pentru că, ți-am zis, erori tot o să ai. Dacă vrei să vezi fix 100KHz, atunci pune generatorul de semnal la 4MHz, care îl folosești pentru clock la AVR, și separat împarți semnalul la 40 (cu un divizor la N sau ceva), și semnalul ăsta îl bagi ca semnal extern (evident, când vrei să schimbi forma semnalul, schimbi doar rata de divizare, și nu frecvența principală, că o va lua AVRul mai repede sau mai încet, și tot 100K îți va arăta).Problema aici e că nu ai cum să obții semnalele sincronizate, chiar dacă tu te sincronizezi cu un semnal de 100KHz, și AVR-ul merge la 4MHz, ele nu vor avea _exact_ valorile astea. Tu te sincronizezi pe un puls (care să zicem că ar fi primul, și da, orice instrucțiune îți mânâncă cod și mărește bucla de procesare), dar al 10000-lea puls, care va trebui achiziționat după fix 400 000 cicli va fi achiziționat fie mai repede, fie mai încet, tocmai din cauză că acel decalaj al clockurilor față de rata ideală se va aduna. Ca sugestie, dacă vrei precizie mai bună, treci la viteza maximă suportată de AVR, pune un cristal extern și ai grijă să fie decuplat corespunzător. Dacă optimizezi porțiunile importante în asm, s-ar putea să ajugi la o precizie chiar mai mare. Uite-te prin lss și vezi dacă vezi chestii ciudate. O primă optimizare, fără asm, ar fi să forțezi toate variabilele în regiștrii procesorului (și evident să te asiguri că ai mai puțin de 16).

Link to comment
Guest aandrei

Am incercat sa vad unde gresesc, dar se pare ca orice as face, in intervalul de 100ms nu pot numara fix un numar de impulsuri, clockurile nefiind sincronizate, si de aceea mi se pare normal ca numarul sa oscileze intre 2 valori.O alta varianta la care m-am gandit ar fi sa numar cu timerul 1 clockurile de la fOSC cat timp am un impuls, si astfel sa determin frecventa semnalului de intrare stiind ca in 1 secunda am 4.000.000 clockuri. Deocamdata intampin ceva probleme cu timer1 pentru ca nu vrea sa numere, dar credeti ca as avea rezultate mai bune folosind metoda asta?

Link to comment

O alta varianta la care m-am gandit ar fi sa numar cu timerul 1 clockurile de la fOSC cat timp am un impuls, si astfel sa determin frecventa semnalului de intrare stiind ca in 1 secunda am 4.000.000 clockuri. Deocamdata intampin ceva probleme cu timer1 pentru ca nu vrea sa numere, dar credeti ca as avea rezultate mai bune folosind metoda asta?

Merge, eventual poți aplica un prescaler ca să nu faci operații pe 16 biți. Asta ți-am sugera și eu inițial. Nu te aștepta să trimiți datele pe serial, pentru că o să ai un byte (sau 2, + biții de încapsulare) la fiecare puls.Chestia e că nu trebuie să numeri cât timp ai un impuls, ci durata palierului de 0 (de la ultima trecere de la 1->0 la următoarea trecere de la 0 la 1).
Link to comment
Guest aandrei

Chestia e că nu trebuie să numeri cât timp ai un impuls, ci durata palierului de 0 (de la ultima trecere de la 1->0 la următoarea trecere de la 0 la 1).

Am inteles, desi e cam acelasi lucru cu a numara intre fronturile crescatoare. Problema pe care o intampin acum este ca timer1 imi numara cativa cicli in plus pe impuls, si asta se intampla in special spre limita superioara de 100kHz. E posibil sa fie un numar de cicli masina pe care ii executa in plus la intrarea intrerupere? O sa incerc sa-l adaug ca o constanta in program, dar nu stiu cum sa-i numar.
Link to comment

Chestia e că nu trebuie să numeri cât timp ai un impuls, ci durata palierului de 0 (de la ultima trecere de la 1->0 la următoarea trecere de la 0 la 1).

Am inteles, desi e cam acelasi lucru cu a numara intre fronturile crescatoare. Problema pe care o intampin acum este ca timer1 imi numara cativa cicli in plus pe impuls, si asta se intampla in special spre limita superioara de 100kHz. E posibil sa fie un numar de cicli masina pe care ii executa in plus la intrarea intrerupere? O sa incerc sa-l adaug ca o constanta in program, dar nu stiu cum sa-i numar.
Da, sau poți număra între fronturi. Cam același lucru.Câtiva cicli în plus la 100000 înseamnă o precizie de x/100000, adică 0.000ceva %. La trei de zero, eu zic că e bine. Plus că de jitter nu ai cum să scapi complet, dacă nu semnalul și clockul atmegaului din aceeași sursă, ca să nu mai zic control complet pe ramurile de execuție în soft (chestie care nu prea o obții dintr-un HLL ca C, ci doar în asamblare).
Link to comment
Guest aandrei

Am constatat ca desi aveam 100Hz la intrare, programul meu imi numara doar 9 impulsuri la 100ms, si cum ajungeam la 100.2 Hz imi numara 10. Asa ca m-am gandit ca baza de timp nu este foarte exacta si am lasat counter0 (cel care imi crea baza de timp) sa mai execute niste instructiuni pana ajung cu adevarat la 100ms (numarul de instructiuni l-am aproximat). Se pare ca metoda a functionat, impreuna cu un mecanism de stabilizare in caz ca numarul de impulsuri oscileaza.Multumesc pentru ajutor!

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now



×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.Terms of Use si Guidelines