/* Digital Signal Generator - see http://www.technoblogy.com/show?5Q7F David Johnson-Davies - www.technoblogy.com - 2nd July 2026 AVR16EB28 @ 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 // Crystal Oscillator ******************************************************* void SetupCrystal () { PORTC.EVGENCTRLA = PORT_EVGEN0SEL_PIN3_gc; // Use PC3 as EVGEN0 EVSYS.CHANNEL2 = EVSYS_CHANNEL_PORTC_EV0_gc; // Link EVGEN0 to CHANNEL2 EVSYS.USEREVSYSEVOUTC = EVSYS_USER_CHANNEL2_gc; // Link CHANNEL2 to PC2 PORTC.PIN3CTRL = PORT_INVEN_bm; // Invert input PC3 } // Phase-Locked Loop ******************************************************* void SetupPLL () { uint8_t pllctrla = CLKCTRL_SOURCE_EXTCLK_gc | CLKCTRL_MULFAC_16X_gc; _PROTECTED_WRITE(CLKCTRL.PLLCTRLA, pllctrla); // EXTCLK and x16 } // Numerically Controlled Oscillator ******************************************************* volatile long Input; void SetupNCO () { PORTMUX.TCFROUTEA = PORTMUX_TCF0_ALT1_gc; // WO0,WO1 on PA6,PA7 // Enable WO0 and WO1 and invert WO1 polarity while((TCF0.STATUS & TCF_CTRLABUSY_bm) != 0); TCF0.CTRLC = TCF_WO0EN_bm | TCF_WO1EN_bm | TCF_WO1POL_bm; // Configure NCO Pulse-Frequency mode TCF0.CTRLB = TCF_CLKSEL_PLL_gc | TCF_WGMODE_NCOFDC_gc; // Use PLL as clock } void SetFrequency () { while((TCF0.STATUS & TCF_CMP0BUSY_bm) != 0); TCF0.CMP = Input>>1; } void StartNCO () { while((TCF0.STATUS & TCF_CTRLABUSY_bm) != 0); TCF0.CTRLA |= TCF_ENABLE_bm; } void StopNCO () { while((TCF0.STATUS & TCF_CTRLABUSY_bm) != 0); TCF0.CTRLA &= ~TCF_ENABLE_bm; } // Numeric keypad ******************************************************* const int Keypad = PIN_PD6; const int Star = 10; const int Hash = 11; const int SmallestGap = 40; int Values[] = {1023,680,640,590,547,507,464,411,351,273,180,133, 0,-100}; int Buttons[] = { -1, 11, 9, 6, 3, 0, 10, 8, 7, 5, 4, 2, 1}; // Returns the keypad character or -1 if no button pressed int ReadKeypad () { int val, lastval=0, count = 0; do { val = analogRead(Keypad); if (abs(val-lastval)<2) count++; else { lastval = val; count = 0; } } while (count < 3); int i = 0; val = val - SmallestGap/2; while (val < Values[i]) { i++; } return Buttons[i - 1]; } // 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 }; 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>8); Wire.write(right); Wire.write(right>>8); } // Plots a character; line = 0 to 2; column = 0 to 21 void PlotChar(int c, int line, int column) { Wire.beginTransmission(OLEDAddress); Wire.write(command); // Set column address range Wire.write(0x21); Wire.write(column*6); Wire.write(column*6 + Scale*6 - 1); // Set page address range Wire.write(0x22); Wire.write(line); Wire.write(line + Scale - 1); Wire.endTransmission(); Wire.beginTransmission(OLEDAddress); Wire.write(data); uint8_t col0 = pgm_read_byte(&CharMap[c][0]); int col0L, col0R, col1L, col1R; col0L = Stretch(col0); col0R = col0L; for (uint8_t col = 1 ; col < 5; col++) { uint8_t col1 = pgm_read_byte(&CharMap[c][col]); col1L = Stretch(col1); col1R = col1L; if (Scale == 1) Wire.write(col0); // Smoothing else { for (int i=6; i>=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); } } } WriteColumns(col0L, col0R); col0L = col1L; col0R = col1R; } col0 = col1; } if (Scale == 1) Wire.write(col0); else WriteColumns(col0L, col0R); Wire.endTransmission(); } uint8_t DigitChar (unsigned int number, unsigned int divisor) { return (number/divisor) % 10; } // Display a 8-digit frequency starting at line, column void PlotFreq (long freq, int line, int column) { boolean dig = false; for (long d=10000000; d>0; d=d/10) { char c = freq/d % 10; if (c == 0 && !dig && d>1) c = Space; else dig = true; PlotChar(c, line, column); column = column + Scale; } } void PlotHz (int line, int column) { PlotChar(Hz, line, column); column = column + Scale; PlotChar(Hz+1, line, column); } // Rotary encoder ********************************************** const int EncoderA = 0; // Positions in PORTD const int EncoderB = 2; volatile int a0; volatile int c0; void SetupEncoder () { PORTD.PIN0CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; // Pullup and pin change on PD0 PORTD.PIN2CTRL = PORT_PULLUPEN_bm; // Pullup on PD2 PORTD.DIRSET = PIN1_bm; // PD1 output low. } // Called when encoder value changes void ChangeValue (bool Up) { Input = Input + (Up ? 2 : -2); ClampFrequency(); PlotFreq(Input, 0, 0); SetFrequency(); } // Pin change interrupt service routine called on both edges of PD0 ISR (PORTD_PORT_vect) { uint8_t port = PORTD.IN; int a = port>>EncoderA & 1; int b = port>>EncoderB & 1; if (a != a0) { // A changed a0 = a; if (b != c0) { c0 = b; ChangeValue(a == b); } } PORTD.INTFLAGS = PIN0_bm; // Clear interrupt flag } void ClampFrequency () { if (Input < 2) Input = 2; // Minimum = 2 if (Input > 33554430) Input = 33554430; // Maximum = 33554430 Input &= ~1L; // Make multiple of 2 } // Setup ********************************************** int Mode = 0; // 0 = enter frequency, 1 = generate output void setup() { Wire.begin(); delay(250); InitDisplay(); delay(250); ClearDisplay(); DisplayOn(); SetupEncoder(); SetupCrystal(); SetupPLL(); SetupNCO(); PlotFreq(Input, 0, 0); } void loop() { int key, parameters; // Wait for key do { key = ReadKeypad(); } while (key == -1); if (key <= 9 && Input < 99999999) { if (Mode) { PlotChar(Space, 0, 8*Scale); PlotChar(Space, 0, 9*Scale); Input = 0; Mode = 0; StopNCO(); } Input = Input*10 + key; } else if (key == Hash) { PlotChar(Space, 0, 8*Scale); PlotChar(Space, 0, 9*Scale); Input = 0; Mode = 0; StopNCO(); } else if (key == Star) { Mode = 1; PlotHz(0, 8*Scale); ClampFrequency(); SetFrequency(); StartNCO(); } PlotFreq(Input, 0, 0); // Wait for key up do { key = ReadKeypad(); delay(10); } while (key != -1); }