/* Frequenz/Spannung-Wandler mit ATtiny25 Die Eingangsfrequenz an F_EINGANG (hier PB.2 mit aktivem Pullup) wird gemessen und skaliert auf eine maximale Frequenz (hier 50 Hz) als PWM mit Timer0 an PWM_AUSGANG (PB.0) ausgegeben. Die PWM-Frequenz beträgt 31 kHz und muß mit einem RC-Glied gefiltert werden, um das Gleichspannungssignal zu erhalten: 10 kOhm und 10 µF reichen. Die Frequenzmessung erfolgt reziprok, wobei Timer1 zur Zeitmessung verwendet wird. Mangels eines Capture-Eingangs werden die Eingangsimpulse per PCINT erfaßt, was hinreichend genau und stabil ist. Der ATtiny25 läuft mit internem Takt, was die Genauigkeit im abgeglichenen Zustand auf 1 % einschränkt. Höhere Genauigkeit wäre mit einem ext. Taktgeber erreichbar. Die Ausgangsspannung ist relativ zur Versogungsspannung und mit 1/256 hinreichend aufgelöst: 0,1 V / Hz @ Vcc = 5 V Die MIN_MESSZEIT ist auf 20 ms eingestellt, was max. 50 Messungen/s zuläßt. Zudem wird zu jedem Eingangsimpuls ein ca. 8 ms Impuls an BLINK_LED ausgegeben. Zum einen dient dies der optischen Kontrolle und zum anderen wird während diese 8 ms weitere Eingangsflanken ignoriert. Diese Totzeit kann angepaßt oder auch abgeschaltet (ausmaskiert) werden. Alle Angaben ohne Gewähr! Michael Nowak www.mino-elektronik.de 2020-04-25 */ #include #include #include #define F_CPU 8000000L // ggf. auf genaue Frequenz einstellen #define T1_TEILER 312 // für 10ms Intervalle bei 8MHz #define DAC_MAX 256 // 8 Bit #define FIN_MAX 50 // 50 Hz #define MIN_MESSZEIT 2 // in 10ms, etwa max. 50 Messungen/Sekunde #define TIMEOUT 200 // ab 2 Sekunden oder <= 0,5 Hz #define BIT(x) (1< DAC_MAX) wert = DAC_MAX; // begrenzen if(wert == 0) { TCCR0A = TCCR0B = 0; // Timer0 + PWM abschalten TCNT0 = 0; } else { OCR0A = wert-1; // da PWM ist immer +1 zu hoch TCCR0A |= BIT(COM0A1) | // init PWM BIT(WGM01) | BIT(WGM00); // mit fast PWM TCCR0B = 0x01; // Timer0 ohne Vorteiler starten } } // diverse Initialisierungen void init_timer_io(void) { TIFR = BIT(TOV1); // flags loeschen TIMSK |= BIT(TOV1); // und ints freigeben TCCR1 = 1; // T1 freilaufend mit CPU-Takt PORTB |= BIT(F_EINGANG); // pullup für Messeingang PCMSK |= BIT(F_EINGANG); // Zählimpulse per Int erfassen GIFR = BIT(PCIF); // flag löschen GIMSK |= BIT(PCIE); // PCINT PB zulassen DDRB |= BIT(BLINK_LED); // als Anzeige DDRB |= BIT(PWM_AUSGANG); // für DA-Wandler } // Interrupt der Eingangsimpulse ISR (PCINT0_vect) { uint8_t T1_temp; uint32_t temp_ueberlauf; if(!(PINB & BIT(F_EINGANG))) { // fallende flanke if(blinkzeit) return; // Prellen unterdrücken, Totzeit abwarten T1_temp = TCNT1; eingangs_impulse++; temp_ueberlauf = ueberlauf; if(TIFR & BIT(TOV1) && (T1_temp < 0x80)) temp_ueberlauf++; zeitpunkt = temp_ueberlauf * 0x100 + T1_temp; blinkzeit = 255; } GIFR = BIT(PCIF); // erneute Flanken löschen } ISR (TIMER1_OVF_vect) // mit 31,25 kHz { static uint16_t delay; ueberlauf++; // grobe Zeitmessung mit T1 sei(); // PCINT nicht blockieren if(blinkzeit) { PORTB |= BIT(BLINK_LED); // LED einschalten blinkzeit--; if(!blinkzeit) PORTB &= ~BIT(BLINK_LED); // wieder abschalten } delay++; if(delay > T1_TEILER) { delay = 0; mess_dauer++; // jede 10ms } } // Beispielprogramm int16_t main(void) { uint8_t gueltige_messung=0; // diverse Variablen zur Messung uint16_t auswerte_impulse, temp_auswerte_impulse, letzte_impulse = 0, messzeit; uint32_t auswerte_zeit, temp_auswerte_zeit, letzte_zeit = 0; uint32_t f_ref; float f; CLKPR = 0x80; CLKPR = 0x0; // 8MHz CPU-Takt init_timer_io(); // und Timer / Ports sei(); f_ref = F_CPU; // kann ggf. angepasst werden while(1) { cli(); // aktuelle Werte ohne Unterbrechung lesen messzeit = mess_dauer; temp_auswerte_impulse = eingangs_impulse; temp_auswerte_zeit = zeitpunkt; sei(); if(messzeit >= TIMEOUT) { // timeout bei 2s dac_ausgabe(0); // 0 V ausgeben gueltige_messung = 0; // nächstes Ergebnis verwerfen cli(); mess_dauer = 0; // und Messdauer erneut abwarten sei(); } if(messzeit > MIN_MESSZEIT) { // mindestens 50 ms abwarten auswerte_impulse = temp_auswerte_impulse - letzte_impulse; // eff. Werte errechnen auswerte_zeit = temp_auswerte_zeit - letzte_zeit; if(auswerte_impulse) { // mindestens ein Eingangsimpuls if(gueltige_messung) { // ungültige Messwerte werden nicht bewertet f = (float) f_ref * (float) auswerte_impulse / (float) auswerte_zeit; if(f > FIN_MAX) f = FIN_MAX; // auf FIN_MAX begrenzen f = f * DAC_MAX / FIN_MAX + 0.5; // auf vollen Ausgabewert skalieren und aufrunden dac_ausgabe(f); } gueltige_messung = 1; // nächste Messung wird gut sein letzte_impulse = temp_auswerte_impulse; // alte Endwerte -> neue Startwerte letzte_zeit = temp_auswerte_zeit; // auch für die Zeit cli(); mess_dauer = 0; // erneut abwarten sei(); } } } return(0); }