Hardwarecounter für Frequenzmessung

Hi,
ich will mit dem Arduino Uno eine Frequenz messen.
Gibt es die Möglichkeit mit dem Arduino einen der Hardwarecounter des ATmega zu benutzten?

Kommt auf die Frequenz an. Aber prinzipiell ja, solange Du unter 1 MHz bleibst geht das sogar ziemlich gut.

Schön ...? aber wie komme ich an die Hardwarecounter ran?

Z.B. so: Power Grid Monitor 2 | Blinkenlight. Die Details für alle Möglichkeiten die die Counter bieten stehen im Datenblatt.

Ansonsten gibt es noch das http://interface.khm.de/index.php/lab/experiments/arduino-frequency-counter-library/ und das http://www.avdweb.nl/arduino/hardware-interfacing/frequency-period-counter.html

danke! mehr wollte ich nicht wissen.

Hier ist ein einfaches Frequenzmessgerät für Arduino bis 33kHz.
Da nicht jeder einen Frequenzgenerator hat, ist dieser zu Testen bereits eingebaut :slight_smile:
Dann muss Pin 3 und Pin 4 verbunden werden.

// ______________________________________________________________________________________________
// ______________________________________________________________________________________________
//
// Frequenzgenerator   +   Frequenzmessgerät bis 33 kHz
//
// (C) Helmut Weber 2/2015
//
//
// Für Fortgeschrittene:
//
// An Pin 3 wird die Zeit  / Frequenz eines externen Rechtecksignals gemessen
// Da nicht jeder über einen Frequenzgenerator verfügt, wird in einer vom Timer-1 angesteuerten
// Interrupt Routine eine Rechteckfrequenz an Pin 4 erzeugt.
// Es müssen dann Pin 4 (Sender) und Pin 3 (Empfänger) verbunden werden.
//
// Natürlich können an Pin 3 auch externe Frequenzquellen angschlossen werden
//
// Ergebnis: Periodendauer / Frequenzmesser 20 Hz - 33 kHz
// ______________________________________________________________________________________________
// ______________________________________________________________________________________________


// Anzahl der Messungen für Genauigkeit
// Für Frequenzen unter 1000 Hz entsprechend herabsetzen:
#define SAMPLES 1000


//                             Makros zum Setzen, Löschen / Testen von Bits
// ______________________________________________________________________________________________
// set bit
static inline void BIT_SET(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_SET(volatile uint8_t *target, uint8_t bit){
	*target |= (1<<bit);
};
 
// clear bit
static inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit){
	*target &= ~(1<<bit);
};

// test bit
static inline bool BIT_TEST(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline bool BIT_TEST(volatile uint8_t *target, uint8_t bit){
	return(*target & (1<<bit));
};


// ______________________________________________________________________________________________
// Globale Variable

bool lastPin3=true;               // Grundzustand: Taste an Pin 3 NICHT gedrückt
unsigned long lastChange3Time=micros();
unsigned long Period=micros();

// ______________________________________________________________________________________________
//Timer Interrupt Routine
// Wird entsprechend der eingestellten Zeit in SETUP
// zyklisch aufgerufen und toggelt Ausgabe an Pin 4
// Es ist gewissermaßen ein eigenes Programm.

ISR(TIMER1_COMPA_vect,ISR_NAKED)
{
  //digitalWrite(4, !digitalRead(4));
  if (BIT_TEST(&PORTD,4)) BIT_CLEAR(&PORTD,4);
  else                    BIT_SET  (&PORTD,4);
 asm(" reti");
}


// ______________________________________________________________________________________________
// Interrupt Routine:
// Wird bei jedem Wechsel an Pin 3 aufgerufen, egal was das Hauptprogramm (LOOP) gerade
// macht. Es ist sozusagen ein eigenes Programm.
unsigned long m;
unsigned int lp;
unsigned long per;

ISR(  PCINT2_vect) {            // wird bei jeder Änderung am Pin 3 aufgerufen
  //SAVE_CONTEXT;               // bei kleinen Programmen organisiert der Compiler das
  BIT_SET(&PORTB,1);
  m=micros();                   // Aktuelle Zeit in Mikrosekunden
  per+=(m - lastChange3Time);
  lp++;
  if (lp==SAMPLES) {
    Period=per;                 // SAMPLES-fache Periodendauer
    lp=0; per=0;
  }
  
  lastChange3Time=m;            // Sichern von dieser Zeit
  BIT_CLEAR(&PORTB,1);
  //RESTORE_CONTEXT;
  //asm("reti");
} 


// ______________________________________________________________________________________________
// Aufruf einmalig am Programm-Start:

void setup() {
  Serial.begin(115200);
  pinMode(4,OUTPUT);            // Für Frequenzgenerator
  pinMode(9,OUTPUT);
  pinMode(3,INPUT_PULLUP);
  
// ============================================
// Timer 1 einrichten = Frequenz-Generator:
// Setup Timer1 IRQ     für Pulse an Pin 4
// ============================================
  cli();
  
  TIMSK1 = (1 << OCIE1A);
  TCCR1A = 0;
  TCCR1B = (1 << WGM12 ) ;                           // Configure timer 1 for CTC mode
  TCCR1B |= (( 1 << CS11  ));                        // Prescaler=8 => 500ns
  
  // 20 Hz
  //OCR1A = 49999;                                   // 20 Hz
  
  // 1000 Hz
  //OCR1A =   999;                                   //  1000 Hz
  
  // 5000 Hz
  //OCR1A =   199;                                   //  5000 Hz
  
  // 10000 Hz
  //OCR1A =  99;                                     //  10000 Hz (alle 50us 1 Interrupt!)  
  
  // 20000 Hz
  OCR1A =  49;                                     //  20000 Hz (alle 25us 1 Interrupt!)  
  
  // 20000 Hz
  //OCR1A =  29;                                     //  33333 Hz (alle 15us 1 Interrupt!)  
  
  sei();
// ============================================


  // Eingang Pin 3 Interrupt einrichten
  cli();                    // Interrupt verbieten
  PCICR  |= 1<<  PCIE2;     // Interrupt für Pins an Port D erlauben
  PCMSK2 |= 1<<  PCINT19;   // zusätzlich Pins auswählen: Interrupt für Pin 3 erlauben
  sei();                    // Interrupt erlauben
  Serial.println("\n\nStart:");
}


// ______________________________________________________________________________________________
// Wird immer wieder neu aufgerufen = Hauptprogramm
float Freq;

void loop() {
unsigned long p;
float fp;
  p=Period;
  if (p>0) {
    Serial.print("Perioden-Dauer(us): ");
    fp=(float) 2.0*p/SAMPLES;
    Serial.print(fp);
    Freq=(1000000.0 / fp);
    Serial.print(" Frequenz: ");
    Serial.println(Freq);
  }
  Period=0;
  delay(1000);
}

... und hier ist mein Arduino-Frequenzmessgerät bis 6 MHz !

Es kann auch als Zeit-Messgerät zur Bestimmung von ausführungszeiten im us-Bereich genutzt werden.

// ______________________________________________________________________________________________
// ______________________________________________________________________________________________
//
//
// (C) Helmut Weber 2/2015
//
// Frequenzmessgerät 1kHz bis 6MHz
//
// Für Fortgeschrittene:
//
// An Pin 5 werden positive Pulse per Hardware gezählt
// Innerhalb einer vorgegebenen Zeit gezählte Pulse werden
// dann als Frequenz ausgegeben
// 
// ______________________________________________________________________________________________
// ______________________________________________________________________________________________




//                             Makros zum Setzen, Löschen / Testen von Bits
// ______________________________________________________________________________________________
// set bit
static inline void BIT_SET(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_SET(volatile uint8_t *target, uint8_t bit){
	*target |= (1<<bit);
};
 
// clear bit
static inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit){
	*target &= ~(1<<bit);
};

// test bit
static inline bool BIT_TEST(volatile uint8_t *target, uint8_t bit) __attribute__((always_inline));
static inline bool BIT_TEST(volatile uint8_t *target, uint8_t bit){
	return(*target & (1<<bit));
};



unsigned int TIM16_ReadTCNT1( void )
{
unsigned char sreg;
unsigned int i;
/* Save global interrupt flag */
sreg = SREG;
/* Disable interrupts */
cli();
/* Read TCNT1 into i */
i = TCNT1;
/* Restore global interrupt flag */
SREG = sreg;
return i;
}

void TIM16_ClearTCNT1( void )
{
unsigned char sreg;
unsigned int i;
/* Save global interrupt flag */
sreg = SREG;
/* Disable interrupts */
cli();
/* Read TCNT1 into i */
TCNT1=0;;
/* Restore global interrupt flag */
SREG = sreg;
}




void setup() {
  Serial.begin(115200);
  pinMode(2,OUTPUT);
  
  
  // Timer 1 wird zum Zähler von Pulsen an Pin 5 (TO1)
  TCCR1A =0;
  TCCR1B =0; 
  TCCR1B |=( (1<<CS10) | (1<<CS11) | (1<<CS12)  );  // Counter: external clock rising edge 
  TCNT1 =0;
  

}


/*
// Messung von Befehlsausführungszeiten
void loop() {
unsigned long m;
unsigned int cnt, tim;
char buf[20];
unsigned long i;
  cnt=TIM16_ReadTCNT1();
  tim=1000000/cnt;
  Serial.println(cnt);
  Serial.print("pro loop: ");  
  sprintf(buf,"%d.%d us\n",tim/10,tim%10);
  Serial.print(buf);
  m=micros();
  m+=100000;                            // Messung für 100000us
  TIM16_ClearTCNT1(); 
  // Messung in 100 ms = 10000us
  // Die Pulse werden hier selbst erzeugt.
  while (micros()<m) {
    //BIT_SET(&PORTD,2);                  // 3.5us / loop
    //BIT_CLEAR(&PORTD,2);
    //    oder
    digitalWrite(2,HIGH);               // 13.5 us 
    digitalWrite(2,LOW); 
  }
  delay(100);
}
*/
// Messung von Frequenzen

unsigned int x=1;

void loop() {
unsigned long m;
unsigned int cnt, tim;
char buf[20];
unsigned long i;

  cnt=TIM16_ReadTCNT1();
  delay(100);
  dtostrf( (float)cnt/(float)x, 8,3,buf);
  Serial.print(buf); Serial.println(" kHz  ");  //Serial.print(cnt); Serial.print("  "); Serial.println(x);
  if (cnt<3000)   { if (x<1000) x*=10; }
  if (cnt>30000) { if (x>1) x/=10; }
  m=micros();
  
  if(TIFR1&(1<<TOV1)) { if (x>1) x/=10; TIFR1 |= (1<<TOV1); }
  // 100KHz -6MHz
  //m+=10000;
 
 m+=(unsigned long)x*(unsigned long)1000; 
  
  TIM16_ClearTCNT1(); 
  // Messung in 100 ms = 10000us
  // Die Pulse werden hier selbst erzeugt.
  while (micros()<m) {
  }
}

@heweb
Super Code-Beispiel! Bringt mich der Lösung meiner Aufgabe weiter. Möchte die
Ansteuerung eines Tachos mit einem Arduino machen. Benötige hierzu eine
Frequenzmessung (20Hz...2kHz), da ich das Signal vom ABS-pickup - Sensor
abnehme (40 Imp / Umdrehung). Dieses Signal muss dann durch 5,5 geteilt an
einem Ausgang des Arduino als Rechtecksignal an den Tacho gegeben werden.

Funktioniert der Code auch auf einem Arduino-Pro-Micro?

(deleted)

Ich habe diese Software hier mal ausprobiert und bin begeistert wie gut das mit dem UNO und dem NANO läuft. Nur am MEGA läuft damit gar nichts. Ist mir noch nicht klar warum nicht. Ich habe aber auch noch nicht raus, auf welcher Grundlage das mit diesem einfachen Code läuft. Deshalb kann ich nauch noch nicht kapieren warum es auf dem MEGA nicht geht. :confused:

Franz

Franz54:
.....Nur am MEGA läuft damit gar nichts. Ist mir noch nicht klar warum nicht. Ich habe aber auch noch nicht raus, auf welcher Grundlage das mit diesem einfachen Code läuft. Deshalb kann ich nauch noch nicht kapieren warum es auf dem MEGA nicht geht. :confused:

Hallo Franz,

dann sieh dir mal die Library an. Da werden sicher Timer oder direkte Zugriffe verwendet, die beim Mega anders sind. Es steht auch in ROT für Atmega328 !

Der ATMega2560 ist schlicht nicht vorgesehen!

#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__)

combie:
Der ATMega2560 ist schlicht nicht vorgesehen!

Ich könnte mir gut vorstellen, das es möglicherweise nur an der PIN-Zuordnung T01 zu Arduino D5 - welcher beim MEGA T0 auf D38 lautet nicht rausgeführt ist - liegt.

@Franz54
Es müsste in der .cpp noch der MEGA ergänzt (*) werden und dann an Stelle von D5 als Eingang,mit T0/D38 auf dem langen Header als Eingang experimentiert werden.

Probieren geht immer.

(*) eigentlich geht auch fürs kleine testen nur das auskommentieren des if defined.

Danke euch. Ich möchte mir das heute Abend mal anschauen. Wäre ja schön wenn man das mit dem Mega auch nutzen kann. Auch muss ich mir mal anschauen, was die Lib alles verändert, so dass andere Dinge nicht mehr korrekt laufen. Da werde ich mich wohl noch ein wenig damit beschätigen müßen :slight_smile:

Bis heute Abend dann.

Danke, Franz

Franz54:
Auch muss ich mir mal anschauen, was die Lib alles verändert,
Bis heute Abend dann.

Zumindest geht millis und delay() nicht mehr.
TIMSK0 &=~(1<<TOIE0); // disable Timer0 //disable millis and delay

Und bitte beachte meine Ergänzung oben - ich hab mir mal die DaBla und die PIN-Konfigs rausgesucht und bin enttäuscht. Beim 2650 ist der Chip-PIN 49 nicht rausgeführt...

Es sind beim Arduino Mega noch viel mehr Pins nicht herausgeführt.....
z.B. gehen so auch die Synchronen USART Modi nicht
Ein kleines Drama!

Der inhaos mega 2560 core schafft Abhilfe

combie:
Der inhaos mega 2560 core schafft Abhilfe

Ach schau. Die "Mega pro mini" sind somit Kastraten von dem :wink:

Gut zu Wissen, das es das auch anders gibt.
+

my_xy_projekt:
Und bitte beachte meine Ergänzung oben - ich hab mir mal die DaBla und die PIN-Konfigs rausgesucht und bin enttäuscht. Beim 2650 ist der Chip-PIN 49 nicht rausgeführt...

Das ist kein Drama. Nimmste Timer 5.

Hab meine Applikation erfolgreich auf einen pro Mini laden können, sie funktioniert tadellos. Ist eine Kombination aus dem Frequenzmesser und einem Frequenzgenerator mit einer modifizierten tone.lib

Hat ein ESP8266 auch Hardwarecounter? Ich bräuchte 3 Stück. Momentan frage ich einfach im Loop 3 GPIOs ab. Aber vielleicht geht das auch eleganter?

Lieben Gruß,
Chris