/* ATmega8-based frequency counter. Adam Sampson A 74ALS869 is used as a prescaler. QA-QE are connected to PB0-4, QF is connected to PD5 (which is T1), S0 and S1 are both connected to PD2, and ~ENP is connected to PD3. ~ENT is tied low, and the input is applied to CLK. (Mutter, grumble, at the ATmega8's designers not making the prescaler available on external inputs... the PICs in the same size range can do it!) The ATmega8 datasheet says that the maximum frequency of an external clock source should be less than F_CPU/2.5, which is 6.4MHz; however, the '869 is only rated at 35MHz anyway, so we are safely below that. For compatibility with Arduino firmware, an LED is connected to PB5, and a serial port on PD0-1. An HD44780-based LCD display is connected to PC0-PC5, pins 23-28. */ #include #include #include #include #include #include "lcd.h" #include "serial.h" // One second, in units of 16 CPU clocks. // In an ideal world this'd be 1000000 exactly, but no crystal is perfect... // To figure out this value for yours, apply 20MHz to the input and pull RXD // low, and it'll adjust it until it reads the right value. const uint32_t ONE_SECOND = 1000091; // The frequency to calibrate to. const uint32_t CALIBRATE_TO = 20000000; int main(void) { serial_stdout_init(19200); // Disable interrupts cli(); // Timer 1 increments on falling edge of T1 TCCR1A = 0; TCCR1B = (6 << CS10); // LED pin is output; LED off DDRB = _BV(DDB5); PORTB = 0; // S0-1 and ENP are outputs; disable count, reset counter DDRD = _BV(DDD2) | _BV(DDD3); PORTD = _BV(PD3); // Pullup on PD0 (RX) PORTD = _BV(PD0); lcd_init(); lcd_puts("Starting up..."); uint32_t cycles = ONE_SECOND; while (true) { // Reset count uint16_t count_top = 0; TCNT1 = 0; // FIXME: wait for the '869 to actually reset // LED on PORTB = _BV(PB5); uint8_t portd_start = _BV(PD2); uint8_t portd_stop = _BV(PD2) | _BV(PD3); uint8_t tifr_reset = _BV(TOV1); uint8_t n0 = cycles >> 24; uint8_t n1 = (cycles >> 16) & 0xFF; uint8_t n2 = (cycles >> 8) & 0xFF; uint8_t n3 = cycles & 0xFF; __asm__ __volatile__ ( "\tout 88-0x20,%[reset]\n" // TIFR: reset "\tout 50-0x20,%[start]\n" // PORTD: start counting // This will count for (cycles * 16) clock ticks. // A F T "1:" "\tin __tmp_reg__,88-0x20\n" // 1 TIFR "\tsbrs __tmp_reg__,2\n" // 1 2 TOV1 -- has T1 overflowed? "\trjmp 2f\n" // 2 "\tadiw %[top],1\n" // 2 "\tout 88-0x20,%[reset]\n" // 1 TIFR: reset "\trjmp 3f\n" // 2 "2:" "\tnop\n" // 1 "\tnop\n" // 1 "\tnop\n" // 1 "\tnop\n" // 1 "3:" "\tnop\n" // 1 Padding to make this 16 cycles "\tnop\n" // 1 "\tsubi %[n3],1\n" // 1 "\tsbci %[n2],0\n" // 1 "\tsbci %[n1],0\n" // 1 "\tsbci %[n0],0\n" // 1 "\tbrne 1b\n" // 1 2 "\tout 50-0x20,%[stop]\n" // 1 PORTD: stop counting, don't reset yet : [top] "+w" (count_top), [n0] "+g" (n0), [n1] "+g" (n1), [n2] "+g" (n2), [n3] "+g" (n3), [start] "+g" (portd_start), [stop] "+g" (portd_stop), [reset] "+g" (tifr_reset)); // Put together all the bits of the count: // As much of count_top as will fit uint32_t count = count_top; // 16 bits from TCNT1 count <<= 16; uint16_t tcnt1 = TCNT1; count |= tcnt1; // 1 bit from PD5 ('869 QF) count <<= 1; count |= ((PIND >> PD5) & 1); // 5 bits from PB0-4 ('869 QA-QE) count <<= 5; count |= (PINB & 0x1F); // Reset the '869 (or, rather, cause it to be reset the next time it's clocked) PORTD = _BV(PD3); // LED off PORTB = 0; uint32_t freq = count; char buf[41]; snprintf(buf, sizeof buf, "Frequency: %10ld Hz", freq); lcd_move(0, 0); lcd_puts(buf); snprintf(buf, sizeof buf, "Count: %08lx", count); lcd_move(0, 1); lcd_puts(buf); if ((PIND & _BV(PD0)) == 0) { snprintf(buf, sizeof buf, "Cal: %ld", cycles); lcd_move(20, 1); lcd_puts(buf); if (freq > CALIBRATE_TO) { --cycles; } else if (freq < CALIBRATE_TO) { ++cycles; } } } }