/* 100MHz Frequency Meter - see http://www.technoblogy.com/show?20B4 David Johnson-Davies - www.technoblogy.com - 16th March 2021 ATtiny414 @ 20 MHz (internal oscillator; BOD disabled) CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ #include // OLED I2C 128 x 32 monochrome display ********************************************** const int OLEDAddress = 0x3C; // Initialisation sequence for OLED module int const InitLen = 15; const unsigned char Init[InitLen] PROGMEM = { 0xA8, // Set multiplex 0x1F, // for 32 rows 0x8D, // Charge pump 0x14, 0x20, // Memory mode 0x01, // Vertical addressing 0xA1, // 0xA0/0xA1 flip horizontally 0xC8, // 0xC0/0xC8 flip vertically 0xDA, // Set comp ins 0x02, 0xD9, // Set pre charge 0xF1, 0xDB, // Set vcom deselect 0x40, 0xAF // Display on }; const int data = 0x40; const int single = 0x80; const int command = 0x00; void InitDisplay () { Wire.beginTransmission(OLEDAddress); Wire.write(command); for (uint8_t c=0; c=0; i--) { for (int j=1; j<3; j++) { if (((col0>>i & 0b11) == (3-j)) && ((col1>>i & 0b11) == j)) { col0R = col0R | 1<<((i*2)+j); col1L = col1L | 1<<((i*2)+3-j); } } } } Wire.write(col0L); Wire.write(col0L>>8); Wire.write(col0R); Wire.write(col0R>>8); col0L = col1L; col0R = col1R; } col0 = col1; Wire.endTransmission(); } Wire.beginTransmission(OLEDAddress); Wire.write(data); if (Scale == 1) Wire.write(col0); else { Wire.write(col0L); Wire.write(col0L>>8); Wire.write(col0R); Wire.write(col0R>>8); } Wire.endTransmission(); } // Plot a 9-digit integer void PlotInt (uint32_t value, int line, int column) { boolean suppress = true; for (uint32_t d=100000000; d>0; d = d/10) { if (d == 100000 || d == 100) { PlotChar((suppress ? ' ' : ','), line, column); column = column + Scale*5; } char c = value/d % 10 +'0'; if (value == 0 && d<=100) c = '-'; // Zero shown as "---" else if (c == '0' && suppress && d != 1) c = ' '; else suppress = false; PlotChar(c, line, column); column = column + Scale*6; } } // Real-Time Clock ********************************************** volatile uint16_t MSByte; volatile uint32_t Counter; void RTCSetup () { uint8_t temp; // Initialize 32.768kHz Oscillator: // Disable oscillator: temp = CLKCTRL.XOSC32KCTRLA & ~CLKCTRL_ENABLE_bm; // Enable writing to protected register CPU_CCP = CCP_IOREG_gc; CLKCTRL.XOSC32KCTRLA = temp; while (CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm); // Wait until XOSC32KS is 0 temp = CLKCTRL.XOSC32KCTRLA & ~CLKCTRL_SEL_bm; // Use External Crystal // Enable writing to protected register CPU_CCP = CCP_IOREG_gc; CLKCTRL.XOSC32KCTRLA = temp; temp = CLKCTRL.XOSC32KCTRLA | CLKCTRL_ENABLE_bm; // Enable oscillator // Enable writing to protected register CPU_CCP = CCP_IOREG_gc; CLKCTRL.XOSC32KCTRLA = temp; // Initialize RTC while (RTC.STATUS > 0); // Wait until registers synchronized RTC.PER = 1023; // Set period 1 second RTC.CLKSEL = RTC_CLKSEL_TOSC32K_gc; // 32.768kHz External Crystal Oscillator RTC.CTRLA = RTC_PRESCALER_DIV32_gc | RTC_RTCEN_bm;// Prescaler /32 and enable } // Timer/Counter TCD0 ********************************************** volatile boolean Ready = false; // New reading ready? void TCDSetup () { TCD0.CTRLB = TCD_WGMODE_ONERAMP_gc; // Set one ramp waveform mode TCD0.CMPBCLR = 0xFFF; // Count up to maximum TCD0.INPUTCTRLB = TCD_INPUTMODE_EDGETRIG_gc; // Capture and reset counter TCD0.EVCTRLB = TCD_CFG_ASYNC_gc | TCD_ACTION_bm | TCD_TRIGEI_bm; // Enable event TCD0.INTCTRL = TCD_OVF_bm | TCD_TRIGB_bm; // Enable interrupts // Ensure ENRDY bit is set while(!(TCD0.STATUS & TCD_ENRDY_bm)); // External clock, no prescaler, enable timer TCD0.CTRLA = TCD_CLKSEL_EXTCLK_gc | TCD_CNTPRES_DIV1_gc | TCD_ENABLE_bm; } // Timer/Counter TCD0 overflow interrupt counts MSByte ISR (TCD0_OVF_vect) { TCD0.INTFLAGS = TCD_OVF_bm; // Clear overflow interrupt flag MSByte++; } // Timer/Counter TCD0 capture interrupt ISR (TCD0_TRIG_vect) { PORTA.IN = PIN4_bm; // Toggle LED on TCD0.INTFLAGS = TCD_TRIGB_bm; // Clear capture interrupt flag Counter = TCD0.CAPTUREB; Counter = (uint32_t)MSByte<<12 | Counter; MSByte = 0; Ready = true; PORTA.IN = PIN4_bm; // Toggle LED off TCD0.INTFLAGS = TCD_OVF_bm; // Clear overflow interrupt flag } // Event System ********************************************** void EvsysSetup (void) { EVSYS.ASYNCCH1 = EVSYS_ASYNCCH1_RTC_OVF_gc; // Event generated from RTC OVF EVSYS.ASYNCUSER7 = EVSYS_ASYNCUSER7_ASYNCCH1_gc; // Event causes a TCD0 capture EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_PORTA_PIN1_gc; // PA1 is an event generator EVSYS.ASYNCUSER8 = EVSYS_ASYNCUSER8_ASYNCCH0_gc; // ASYNCUSER8 is EVOUT0 (PA2) PORTMUX.CTRLA = PORTMUX_EVOUT0_bm; // Enable EVOUT0 PORTA.PIN1CTRL = PORT_INVEN_bm; // Invert input } // Setup ********************************************** void setup() { PORTA.DIRSET = PIN4_bm; // Make LED on PA4 an output PORTA.PIN4CTRL = PORT_INVEN_bm; // Invert output Wire.begin(); InitDisplay(); ClearDisplay(); TCDSetup(); RTCSetup(); EvsysSetup(); } void loop() { uint32_t temp; unsigned long start = millis(); while (!Ready) { if (millis() - start > 1000) { Counter = 0; break; } } Ready = false; cli(); temp = Counter; sei(); PlotInt(temp, 1, 0); }