Sida 1 av 1

En simpel frekvensräknare baserad på en Arduino Nano 3.x

Postat: 22 mars 2022, 08:10:51
av Fransson
Bara några bilder, kod och kommentarer kring mitt senaste projekt.

Bilderna 1 och 2 är två simuleringar i LT-Spice av den analoga delen av ingången, med schema.
R1 är ställbar för att kunna trimma in arbetspunkten. Mät likspänningen på utgången och justera R1.
Efter den analoga ingångsförstärkaren kommer en 74LS14 schmitt trigger inverterare. Där efter kopplas signalen till T1/PD5/D5 ingången på Arduinon.
Simmulering1.png
Simmulering2.png

Koden med mina kommentarer på engelska.

Kod: Markera allt

/*
  #####################################################
  # Frequency counter.                                #
  # A class that implements a frequency counter,      #
  # using only Timer/Counter1 and Timer/Counter2.     #
  # (Leaving the Timer/Counter0 for other use.)       #
  #####################################################

  #####################################################################
  # Some prerequisites.                                               #
  # • External flanks on the T1 pin (PD5/D5) are counted by Counter1. #
  # • A Timer1 Overflow Interrupt (TIMER1_OVF) implements             #
  #   a software counter to extend the bit depth of the counter.      #
  # • Timer/Couner2 and its prescaler sets a fixed (2mS) time that    #
  #   is used to generate the desired “Gate time”.                    #
  # • A Timer2 Compare mode interrupt (TIMER2_COMPA) does:            #
  #   1. Implements a software counter to extend the gate time.       #
  #   2. Stops the counters and sums up the result when the gate time #
  #      is up/out.                                                   #
  #####################################################################

  ############################################################
  # Methods.                                                 #
  # Settings – Sets up the timers and interrupts.            #
  # StartCount – Starts a count with a given gate time.      #
  # GetResult – Retrieves and scales the result depending on #
  #     current gate time.                                   #
  # IsRunning – Detects if a measurement is ongoing.         #
  # ISR (TIMER1_OVF_vect)                                    #
  # ISR (TIMER2_COMPA_vect)                                   #
  ############################################################
*/

//-----------------------------------------------------------------------------------
class FrequencyCounter{
  public:



    static int8_t Settings(void){
/*-----------------------------------------------------------------------------------
  #################################################################################
  # Configures Timer2 to generate gate time interrupts at 2mS intervals. (=500Hz) #
  #################################################################################
 */

      pinMode(11,OUTPUT);     // Sets OC2A (PB3/D11) to output for testing. (Not needed in final version.)
      
      TCCR2A = SetTCCR2A;     // SetTCCR2A is a constant set else ware in the code.
      TCCR2B = SetTCCR2Boff;  // SetTCCR2Boff is a constant set else ware in the code.
      OCR2A = 249;            // OCR2A – Output Compare Register A: 249 (Dec)
      TIMSK2 = B00000010;     // Bit 1 – OCIE2A: Enables Timer/Counter2 Output Compare Match A Interrupt.
//-----------------------------------------------------------------------------------




/*-----------------------------------------------------------------------------------
  #########################################################################
  # Configures Timer1 to count external clockpulses on the T1 pin.(PD5/D5)#
  # And generate interupt at 'owerflow', to extend the count in software. #
  #########################################################################
 */
      TCCR1A = SetTCCR1A;     // SetTCCR0A is a constant set else ware in the code.
      TCCR1B = SetTCCR1Boff;  // SetTCCR0Boff is a constant set else ware in the code.
      TIMSK1 = B00000001;     // Bit 0 – TOIE1: Enables the Timer/Counter1 Overflow Interrupt (1).
//-----------------------------------------------------------------------------------




//-----------------------------------------------------------------------------------
      sei();                  // Allow interrupts
//-----------------------------------------------------------------------------------
      return(0);
    }


/*-----------------------------------------------------------------------------------
############################################################
# StartCount method.                                       #
# Clears all relevant variables and registers.             #
# And then starts both timers as synchronized as possible. #
############################################################
*/
    static int8_t StartCount(uint16_t GateTime){
      int8_t Runs=IsRunning();
      if(Runs==0){
        GateCount = 0 ;
        FCount = 0 ;
        FResult = 0 ;
        FlagDone = 0 ;
        TCNT1 = 0 ;             // Clear Timer1
        TCNT2 = 0 ;             // Clear Timer2
        GateMax = GateTime>>1;  // Divide by 2. (2mS / tick.)
        if (GateMax < 1){ GateMax = 1 ;}
        GTCCR  = B00000010;     // Clear Timer2 prescaler!
        TCCR2B = SetTCCR2Bon;   // Start Timer2! (Selects "clkT2S/128 (From prescaler)".)
        TCCR1B = SetTCCR1Bon;   // Start Timer0! (Selects "rising edge".)
      }
      return(Runs);
    }
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
 ##########################################################
 # GetResult Method.                                      #
 # Method to retreve new frequency counter measurements.  #
 # Returns zero if no new measurement is available.       #
 ##########################################################
 */
    static int8_t GetResult(uint32_t& Frequency){
      uint8_t Flag = 0;
      if(FlagDone)
      {
        Frequency = (uint64_t((double(FResult)*1000.0)/double(GateMax))+1L)>>1;
        Flag = 1;
        FlagDone = 0;
      }
      else
      {
        Frequency = 0;
        Flag = 0;
      }
      return Flag;
    }
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
#####################################################
# Frequency counting interrupt routine.             #
# (Timer/Counter 0 Overflow interrupt.)             #
# Counts the number of overflows in order to extend #
# the bit depths of the frequency counter.          #
#####################################################
*/
    static void ISR_TIMER1_OVF_vect(){
      ++ FCount ;
    }
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
######################################################################
# Gate time interrupt.                                               #
# (Timer/Counter 2 Compare mode interrupt.)                          #
# Counts the number of “compare equal to”, 2mS interrupts needed     #
# to make up the finale gate time. Also stops the counter when the   #
# time is up. And calculates the correct frequency and present it to #
# the main routine.                                                  #
######################################################################
*/
    static void ISR_TIMER2_COMPA_vect(){
      if (++ GateCount >= GateMax) {
        TCCR1B = SetTCCR1Boff;    // Stop Timer0!
        TCCR2B = SetTCCR2Boff;    // Stop Timer2!
        if ((TIFR1 & B00000001)){++FResult;}
        TIFR1 = B00000111;
        TIFR2 = B00000111;
        FResult = (FCount << 16) + TCNT1 ;
        FlagDone = 1 ;
      }
    }
//-----------------------------------------------------------------------------------


  private:


/*-----------------------------------------------------------------------------------
######################################################
# Defines a handful of variables that are altered    #
# and/or used in more than one place. (Usually       #
# in at least one interrupt routine and also in main #
# and/or one more interrupt routine.                 #
######################################################
*/
    static volatile uint16_t GateCount;   // Counts the (2mS) gate time interrupt (Timer2) ticks. (Ads up to finale gate time.)
    static volatile uint16_t GateMax;     // Holds the number of Timer2 ticks needed to reach the finale gate time. (500 * 2mS = 1000mS)
    static volatile uint32_t FCount;      // Counts the frequency counter (Timer1) overflow interrupts. (Increases the total bit depths of the count.) 
    static volatile uint32_t FResult;     // Used to calculate and report the "finale" frequency.
    static volatile uint8_t FlagDone;     // Signals to the main routine that a new measurement is present.
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
  #################################################################################
  # Configures Timer2 to generate gate time interrupts at 2mS intervals. (=500Hz) #
  #################################################################################
 */
    static const int8_t SetCOM2A = B01<<6; // Sets the OC2A (Compare Match Output A) output pin (PB3/D11) to toggle for testing. (Set to 00 in final version.)
    static const int8_t SetCOM2B = B00<<4; // Turns of the OC2B (Compare Match Output B).
    static const int8_t SetWGM2 = B010;    // Sets the Waveform Generation Mode to CTC. (Mode 2 or B010).
    static const int8_t SetTCCR2A = SetCOM2A|SetCOM2B|(SetWGM2&B00000011);
    
    static const int8_t SetFOC2A = 0<<7;   // Sets Force Output Compare A to Zero.
    static const int8_t SetFOC2B = 0<<6;   // Sets Force Output Compare B to Zero.
    static const int8_t SetCS2on = B101;   // Configures the "Clock Select" to "clkT2S/128 (From prescaler)".
    static const int8_t SetCS2off = 0;     // Configures the "Clock Select" to "No clock source (Timer/Counter stopped)".
    static const int8_t SetTCCR2Bon = SetFOC2A|SetFOC2B|((SetWGM2&B00000100)<<1)|SetCS2on;
    static const int8_t SetTCCR2Boff = SetFOC2A|SetFOC2B|((SetWGM2&B00000100)<<1)|SetCS2off;
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
  #########################################################################
  # Configures Timer1 to count external clockpulses on the T1 pin.(PD5/D5)#
  # And generate interupt at 'owerflow', to extend the count in software. #
  #########################################################################
 */
      static const int8_t SetCOM1A = B00<<6;    // Compare Match Output A Mode - Off.
      static const int8_t SetCOM1B = B00<<4;    // Compare Match Output B Mode - Off.
      static const int8_t SetWGM1 = B0000;       // Sets Waveform Generation Mode to "Normal" (0/0000).
      static const int8_t SetTCCR1A = SetCOM1A|SetCOM1B|(SetWGM1&B00000011);
      
      static const int8_t SetICNC1 = B0<<7;     // Input Capture Noise Canceler.
      static const int8_t SetICES1 = B0<<6;     // Input Capture Edge Select.
      static const int8_t SetCS1on = B111<<0;   // Configures the "Clock Select" to "External clock source on T0 pin. Clock on rising edge".
      static const int8_t SetCS1off = B000<<0;  // Configures the "Clock Select" to "No clock source (Timer/Counter stopped)".
      static const int8_t SetTCCR1Bon = SetICNC1|SetICES1|((SetWGM1&B00001100)<<1)|SetCS1on;
      static const int8_t SetTCCR1Boff = SetICNC1|SetICES1|((SetWGM1&B00001100)<<1)|SetCS1off;
      
      static const int8_t SetFOC1A = B0<<7;     // Force Output Compare A - Off.
      static const int8_t SetFOC1B = B0<<6;     // Force Output Compare B - Off.
      static const int8_t SetTCCR1C = SetFOC1A|SetFOC1B;
//-----------------------------------------------------------------------------------



//-----------------------------------------------------------------------------------
    static int8_t IsRunning(void){
      if(TCCR1B!=SetTCCR1Boff||TCCR2B!=SetTCCR2Boff)
      {
        return 1;
      }
      else
      {
        return 0;
      }
    }
};
//-----------------------------------------------------------------------------------




/*-----------------------------------------------------------------------------------
######################################################
# Defines a handful of variables that are altered    #
# and/or used in more than one place. (Usually       #
# in at least one interrupt routine and also in main #
# and/or one more interrupt routine.                 #
######################################################
*/
// This are still local to class and can have default value here
volatile uint16_t FrequencyCounter::GateCount = 0;    // Counts the (2mS) gate time interrupt (Timer2) ticks. (Ads up to finale gate time.)
volatile uint16_t FrequencyCounter::GateMax = 500;    // Holds the number of Timer2 ticks needed to reach the finale gate time. (500 * 2mS = 1000mS)
volatile uint32_t FrequencyCounter::FCount = 0;       // Counts the frequency counter (Timer1) overflow interrupts. (Increases the total bit depths of the count.) 
volatile uint32_t FrequencyCounter::FResult = 0;      // Used to calculate and report the "finale" frequency.
volatile uint8_t  FrequencyCounter::FlagDone = 0;     // Signals to the main routine that a new measurement is present.
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
#####################################################
# Frequency counting interrupt routine.             #
# (Timer/Counter 1 Overflow interrupt.)             #
# Counts the number of overflows in order to extend #
# the bit depths of the frequency counter.          #
#####################################################
*/
ISR(TIMER1_OVF_vect){
  FrequencyCounter::ISR_TIMER1_OVF_vect();
}
//-----------------------------------------------------------------------------------



/*-----------------------------------------------------------------------------------
######################################################################
# Gate time interrupt.                                               #
# (Timer/Counter 2 Compare mode interrupt.)                          #
# Counts the number of “compare equal to”, 2mS interrupts needed     #
# to make up the finale gate time. Also stops the counter when the   #
# time is up. And calculates the correct frequency and present it to #
# the main routine.                                                  #
######################################################################
*/
ISR(TIMER2_COMPA_vect){
  FrequencyCounter::ISR_TIMER2_COMPA_vect();
}
//-----------------------------------------------------------------------------------



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


  uint32_t Count = 0;
  uint32_t Frequency = 0;
  uint8_t Flag = 0;
  uint8_t Ok;

#include <LiquidCrystal.h>

const int rs = 19, en = 18, d4 = 17, d5 = 16, d6 = 15, d7 = 14;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
  
void setup() {
  // put your setup code here, to run once:
  
  Ok=FrequencyCounter::Settings();
  Ok=FrequencyCounter::StartCount(1000);        // Gate time in mS.
  if (Ok){delay(1);}                            // Just to "use" -Ok- to eliminate a warning about unused variable.
  Serial.begin(57600);

  lcd.begin(16, 4);
  lcd.setCursor(0, 0);
  lcd.print("Frekvensraknare V0.0");
}

void loop() {
  // put your main code here, to run repeatedly:
  
    Flag = FrequencyCounter::GetResult(Frequency);
    if (Flag) {
      Serial.print(Count, DEC);
      Serial.print("; F=");
      Serial.print(Frequency, DEC);
      Serial.println(" Hz.");
      
      lcd.setCursor(0, 1);
      lcd.print(Frequency);
      lcd.print(" Hz.        ");
      
      ++ Count ;
      Ok=FrequencyCounter::StartCount(1000);        // Gate time in mS.
    }

}


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



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

Framsidan utan LCD.
Lyft på JCDn.jpg

Baksidan.
Ormboet.jpg

Användning.
Mätresultatet.jpg

Just nu får räknaren vila medan jag fortsätter med andra projekt. Men om jag inte har hunnit köpa en ”professionell” frekvensräknare tills dess kan jag komma att göra en uppdatering av den om ett par tre år.

Med vänliga hälsningar Magnus Fransson.

Re: En simpel frekvensräknare baserad på en Arduino Nano 3.x

Postat: 22 mars 2022, 11:59:12
av rvl
Ser bra ut. Återkoppling i ingångsförstärkartråden vore kanske inte heller fel.

Re: En simpel frekvensräknare baserad på en Arduino Nano 3.x

Postat: 22 mars 2022, 17:57:19
av Mindmapper
Bra redovisning!
Är lite nyfiken var du mätt kristallfrekvensen! På kristallen eller efter någon buffer?
Om du mätt på kristallen kan du se någon frekvensförändring?

Re: En simpel frekvensräknare baserad på en Arduino Nano 3.x

Postat: 22 mars 2022, 18:11:48
av Fransson
Jag mätte på LM1889N:s ben 17, Croma osc output.

Med tanke på min tillit (inte) till att Arduinons kristall oscillator håller exakt 16 MHz, så tolkar jag mätvärdet som att jag ska söka efter felet någon annanstans.

I en ny tråd som jag jobbar på berättar jag var jag hittade felet och frågar om hjälp att åtgärda det.

Med vänliga hälsningar Magnus Fransson.

Re: En simpel frekvensräknare baserad på en Arduino Nano 3.x

Postat: 22 mars 2022, 18:51:13
av rvl
Om det finns tillgång till nån pålitlig källa, så borde det gå att kalibrera Arduinon (de har säkert inte satsat många cent på kristallen, eller är det rent av en keramisk resonator?) under rådande temperatur.

Re: En simpel frekvensräknare baserad på en Arduino Nano 3.x

Postat: 22 mars 2022, 21:10:50
av Fransson
Här finns den utlovade fortsatta felsökningen.
Färgbalk-/Testmönstergenerator för (PAL) färg-TV.