Spørgsmål:
ATmega AVR frekvensmåling med timer / tæller
Robbe Elsas
2017-05-14 20:20:08 UTC
view on stackexchange narkive permalink

Jeg har genopvasket nogle eksisterende biblioteker, jeg fandt på nogle uklare fora for at måle en frekvens på digital pin 5 ved hjælp af ATmega328P / ATmega2560 Timer / Counter-moduler og en afbrydelsesbaseret tilgang for at kunne måle frekvenser op til 4MHz (-ish). Men ved hjælp af koden beskrevet nedenfor er resultaterne slukket med en faktor på nøjagtigt 100. Jeg brugte en frekvensgenerator til at udføre disse aflæsninger. Her er et eksempel på, hvad jeg har fået tilbage, når jeg genererer en 5000Hz firkantbølge (50% arbejdscyklus):

enter image description here

Kan nogen skal du påpege, hvad der kan være galt. Jeg har læst ATmega328P datablad flere gange nu, og det skal bare fungere! Dette er meget mærkeligt.

Rediger: Da det viser sig, at AWG, jeg bruger, har en blantant softwarefejl, der får den til at generere en bølge med en periode, der er 100 gange for længe (se billedet nedenfor). Ikke desto mindre, som Edgar påpegede, lavede jeg nogle ret store fejl ved ikke at tage højde for race betingelser. Derfor er nedenstående kode blevet opdateret. Testprogrammet er også opdateret til at generere en 50% duty cycle firkantbølge @ 5000Hz til testformål som foreslået af Edgar.

AWG software bug

Dette er headerfilen:

  #ifndef FreqCounter_h # define FreqCounter_h #include <avr / interrupt.h> # hvis defineret (ARDUINO) && ARDUINO > = 100 # inkluderer "Arduino.h" # else # inkluderer "WProgram.h" #endifnamespace FreqCounter; ekstern flygtig usigneret char f_ready; ekstern flygtig usigneret char f_mlt; ekstern flygtig usigneret int f_tics; ekstern flygtig usigneret int f_period; ugyldig start (int ms);} # endif  

Dette er kildefilen:

  #include <FreqCounter.h>volatile usigneret lang FreqCounter :: f_freq; flygtig usigneret char FreqCounter :: f_ready; flygtig usigneret char FreqCounter :: f_mlt;
flygtig usigneret int FreqCounter :: f_tics; flygtig usigneret int FreqCounter :: f_period; ugyldig FreqCounter :: start (int ms) {f_period = ms; GTCCR = (1<<TSM) | (1<<PSRASY) | (1<<PSRSYNC); // standse alle timere // sæt TC1 til normal tilstand = > TOV1-flag indstillet til TOP = 0xFFFF TCCR1A & = 0x00; // nulstil TC1 kontrolregister A TCCR1B & = 0x00; // nulstil TC1-kontrolregister B // indstil TC1 til at bruge ekstern urkilde, klokket på stigende kant TCCR1B | = (1<<CS10); TCCR1B | = (1<<CS11); TCCR1B | = (1<<CS12); // nulstil TC2 TCCR2A & = 0x00; // nulstil TC2-kontrolregister A TCCR2B & = 0x00; // nulstil TC2-kontrolregister B // sæt TC2 til CTC-tilstand med TOP = OCR2A = >-tæller ryddet, når TOP TCCR2A er nået | = (1<<WGM21); // TC2 er i CTC-tilstand = >-indstillingsværdi for OCR2A = TOP OCR2A = 124; // TOP nås hvert 125 tæller (startende fra 0) // indstil TC2 til at bruge en forudskaler på 1 = > 16MHz / 128 = 125kHz TCCR2B | = (1<<CS20); TCCR2B | = (1<<CS22); f_ready = 0; // nulstil klar flag f_tics = 0; // nulstil TC2-afbrydertæller // synkroniser værdien af ​​begge timere TCNT2 = 0; TCNT1 = 0; // ryd afbrydelsesflag TIFR2 | = (1<<OCF2A); TIFR1 | = (1<<TOV1); TIMSK2 | = (1<<OCIE2A); // aktiver TC2 Output Sammenlign En afbrydelse = > udløst ved at nå TOP = OCR2A TIMSK1 | = (1<<TOIE1); // aktiver TC1 overløbsinterrupt = > udløst ved at nå TOP = MAX = 0xFFFF GTCCR & = 0x00; // genstart alle timere = > start optælling} / * * Denne ISR udløses af TC2 hver 1ms, fordi * TC2 trin med en frekvens på 16MHz / 128 = 125KHz
* og når TOP hvert 125 tæller * = > trigger frekvens = 1kHz * = > trigger periode = 1ms * * Denne ISR håndterer Gate Time generation * / ISR (TIMER2_COMPA_vect) {// trin antal TC2 interrupts (udløser når TOP = OCR2A) FreqCounter :: f_tics ++; / * * Hvis gren evalueres som sand, hvis givet gate-tid (f_period) er lig med * mængden af ​​millisekunder TC2 er talt (= f_tics) * / if (FreqCounter :: f_tics > = FreqCounter :: f_period) {// slutning af gate tid = > måling klar TCCR1B & = ~ 0x07; // sæt TC1 til ingen clk-kilde = > TC1 holder op med at tælle TIMSK2 & = ~ (1<<OCIE2A); // deaktiver TC2 Output Sammenlign En afbrydelse TIMSK1 & = ~ (1<<TOIE1); // deaktiver TC1 overløb afbryd FreqCounter :: f_ready = 1; // sæt softwareflag til slutningen af ​​gatingperioden // inkrement ved overløb af TCNT1 hvis (bit_is_set (TIFR1, TOV1)) FreqCounter :: f_mlt ++; // beregne frekvens FreqCounter :: f_freq = 65536 * FreqCounter :: f_mlt; FreqCounter :: f_freq + = TCNT1; // tilføj værdi af TC1 FreqCounter :: f_freq = (lang) (FreqCounter :: f_freq * 1000.0 / (dobbelt) FreqCounter :: f_tics); FreqCounter :: f_mlt = 0; }} ISR (TIMER1_OVF_vect) {FreqCounter :: f_mlt ++;}  

Dette er testprogrammet:

  #include <FreqCounter.h>long frq; ugyldig opsætning ( ) {// generere en firkantbølge på 5 kHz på OC0B = digital pin 5 = T1 DDRD | = _BV (PD5); // PD5 = OC0B som output TCCR0B = 0; // stop-timer TIMSK0 = 0; // deaktiver overløbsafbrydelse OCR0A = 50 - 1; // periode = 50 * 64 cyklusser OCR0B = 50 / 2-1; // 50% arbejdscyklus TCCR0A = _BV (COM0B1) // ikke-inverterende PWM på OC0B | _BV (WGM00) // hurtig PWM, TOP = OCR0A | _BV (WGM01); //...ditto TCCR0B = _BV (WGM02) //...ditto | _BV (CS00) // ur ved F_CPU / 64 | _BV (CS01); //...ditto
Serial.begin (57600); Serial.println ("Frequency Counter");} ugyldig loop () {FreqCounter :: start (1000); mens (FreqCounter :: f_ready == 0); frq = FreqCounter :: f_freq; Serial.println (frq);}  

PS: Dette er mit første indlæg nogensinde på dette forum. Du er velkommen til at kommentere indholdet af mit spørgsmål. Tips til, hvordan jeg kan forbedre fremtidige spørgsmål, er altid velkomne. Hav en dejlig dag!

Prøv at bytte rækkefølgen af ​​operationer til `FreqCounter :: f_freq * 1000 / FreqCounter :: f_tics`
@Gerben desværre ændrede det ikke nogen forskel, og hvis det gjorde, ville det have været helt irrationelt. Tak alligevel!
Ikke helt irrationel. Variablerne er heltal, men du ønsker, at beregningen skal udføres som flydende punkt. At bytte nogle dele rundt vil ophæve behovet for flyder alt sammen som vist. Alt for dårligt, det hjalp ikke her. Har du prøvet at måle forskellige frekvenser? Er det hele med en faktor 100?
@Gerben du har faktisk ret. Og for at besvare dit spørgsmål: ja det har jeg. Alle andre frekvensmålinger er også slået fra med en faktor 100. Det er det, der faktisk er mærkeligt for mig. Dette får mig til at tro, at der muligvis er noget standard prescaler-underligt, jeg ikke er opmærksom på. Jeg er vant til at arbejde med STM'er og har tidligere arbejdet med XMEGA-enheder, men siden en Arduino har nogle ret irriterende standardindstillinger, har jeg stødt på disse slags ting hele tiden for nylig. Jeg er desværre forpligtet til at bruge Arduino-platformen til dette projekt.
Koden ser okay ud for mig. Meget underligt. Hvad sker der, hvis du ændrer perioden (f.eks. `FreqCounter :: start (100);`)
@Gerben det samme problem med en anden periode. Af grovhed ændres præcisionen også, men det kan forventes. En periode på 100 ms giver en præcision på 1000Hz, men den skal være 10Hz.
Ikke kun ser din kode fint ud, den fungerer perfekt her på mit Arduino Uno-kompatible kort (ATmega328P). Du sender den firkantede bølge ind i pin 5, ikke?
Bor du et sted i verden med 50 Hz strøm? Hvad er de lave og høje spændinger på din firkantbølge? Deler din frekvensgenerator en fælles grund med din Arduino? Sidder det tæt på et sort huls begivenhedshorisont?
@EdgarBonet Positiv på 50 Hz lysnettet, og jeg er ikke sikker, men jeg lever muligvis på kanten af ​​et sort hul, fordi jeg konstant spekulerer på, hvordan tiden kan gå så langsomt, især når jeg prøver at ordne sh * t det skal bare fungere;). De har fælles grunde, og jeg bruger bestemt pin 5. Jeg vil kontrollere frekvensen af ​​den genererede puls med mit oscilloskop (som jeg allerede skulle have gjort). Jeg genererer også en bølge på TTL-niveauer.
En svar:
Edgar Bonet
2017-05-15 14:43:26 UTC
view on stackexchange narkive permalink

Jeg sender dette som et svar, bare fordi det ikke passer som en kommentar. Men mere end et svar er det meningen at være en fejlfindingshjælp.

Først vil jeg gerne påpege, at du kan bruge den samme Arduino til at generere testsignalet og læse det. Dette kan gøres ved at konfigurere Timer 0 (den sidste, du har tilbage ...) for at udsende 5 kHzsquare-bølgen på OC0B (digital pin 5), og led dette signal til pin T1, som er ... også digital 5. Ingen ledninger nødvendige derefter! Du kan gøre dette ved at tilføje følgende til dit testprograms setup():

  // Generer en 5 kHz firkantbølge på OC0B = digital pin 5.DDRD | = _BV (PD5); // PD5 = OC0B som outputTCCR0B = 0; // stop timerTIMSK0 = 0; // deaktiver overløb interruptOCR0A = 50 - 1; // periode = 50 * 64 cyklusserOCR0B = 50/2 - 1; // 50% duty cycleTCCR0A = _BV (COM0B1) // ikke-inverterende PWM på OC0B | _BV (WGM00) // hurtig PWM, TOP = OCR0A | _BV (WGM01); // ... dittoTCCR0B = _BV (WGM02) // ... ditto | _BV (CS00) // ur ved F_CPU / 64 | _BV (CS01); // ... ditto  

Så vil jeg gerne påpege et par fejl i biblioteket, du bruger. For det første er der en off-by-one-fejl: programmet tæller en million sekunder for meget. Dette kan løses ved at flytte linjerne

  // stigningstallet for TC2-afbrydelser (udløser når TOP = OCR2A nås) FreqCounter :: f_tics ++;  

fra bunden til toppen, hvis ISR(TIMER2_COMPA_vect).

Dernæst er der en løbetilstand mellem ISR'erne. Race betingelser er uhyggelige bugs, fordi de er svære at reproducere, og det meste af tiden ser du dem ikke. Og så mislykkes programmet bare på et tilfældigt tidspunkt, efter at mange tests viste, at det fungerer fint.

Her fungerer det som følger: Lad os sige, at efter den krævede tid er bortfaldet, udløses TIMER2_COMPA og lige efter det TIMER1_OVF firesalso. Denne anden afbrydelse er sat på hold (det er en afventer afbrydelse ) mens TIMER2_COMPA_vect løber gennem sin prolog. Derefter beregner du frekvensen baseret på FreqCounter :: f_mlt (som ikke er opdateret til at tage højde for overløbet) og TCNT1 (som allerede er overløbet til nul). Således får du et forkert resultat. Ikke kun det, overflowflag forbliver indstillet, så ISR kører med det samme så snart afbrydelsen aktiveres igen ved dit næste opkald til FreqCounter :: start () . igen får du et forkert resultat.

Fixingen er to gange: først skal du registrere den afventende afbrydelsesbetingelse i ISR (TIMER2_COMPA_vect) : efter stop af timerne og inden computing FreqCounter :: f_freq , du har brug for dette:

  // Savnede vi et overløb? Hvis (bit_is_set (TIFR1, TOV1)) FreqCounter :: f_mlt ++;  

Derefter skal du rydde afbrydelsesflagget. Dette er normalt doneright, før du aktiverer afbrydelsen, og bare for at være sikker gør du det for evigt afbryder du aktiverer. I FreqCounter :: start () , lige før du trykker på TIMSK2 og TIMSK1 , ville du således:

  // Ryd afbrydelsesflag. TIFR2 = _BV (OCF2A); TIFR1 = _BV (TOV1);  

Der er et par andre mindre problemer, som udsagnet TCCR2A & = ~ (1<<WGM22); hvilket ikke giver mening (biten tilhører et andet register). Men i betragtning af at dette, som et par andre udsagn, ikke har nogen som helst virkning, bør det ikke påvirke programmets funktion. Det afslører dog sjusket programmering.

Wow, jeg kan ikke tro niveauet af noobishness, jeg viste ved at forsømme at tænke på race forhold. Hvis min tidligere professor i datalogi vidste om dette, ville han kvæle mig med sine bare hænder. Jeg bør også virkelig kontrollere min kode, før jeg kører til forumtråde. Jeg vil foretage de foreslåede rettelser med det samme og rapportere tilbage til dig. Hvis det er rettet, redigerer jeg mit indlæg. Tak på forhånd.


Denne spørgsmål og svar blev automatisk oversat fra det engelske sprog.Det originale indhold er tilgængeligt på stackexchange, som vi takker for den cc by-sa 3.0-licens, den distribueres under.
Loading...