/* Harmonic Function Generator - see http://www.technoblogy.com/show?22KY David Johnson-Davies - www.technoblogy.com - 4th April 2018 ATtiny85 @ 8 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 #define NOINIT __attribute__ ((section (".noinit"))) const int Harmonics = 10; int Scale = 2; // 2 for big characters // Don't initialise these on reset boolean Mode NOINIT; unsigned int Freq NOINIT; int Harmonic[Harmonics] NOINIT; // Direct Digital Synthesis ********************************************** int8_t Waveform[256]; volatile unsigned int Acc, Jump; volatile signed int X, Y; volatile int Tick, Mean; void SetupDDS () { // Enable 64 MHz PLL and use as source for Timer1 PLLCSR = 1< 1) scale = scale + 2048/(h+1); } scale = scale>>5; for (int i=0; i<256; i++) Waveform[i] = 0; for (int h=1; h<=Harmonics; h++) { int X=0, Y=8186; for (int i=0; i<256; i++) { for (int j=0; j>8] + 128; } // OLED I2C 128 x 32 monochrome display ********************************************** const int OLEDAddress = 0x3C; // Initialisation sequence for OLED module int const InitLen = 24; const unsigned char Init[InitLen] PROGMEM = { 0xAE, // Display off 0xD5, // Set display clock 0x80, // Recommended value 0xA8, // Set multiplex 0x1F, 0xD3, // Set display offset 0x00, 0x40, // Zero start line 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, 0x81, // Set contrast 0x7F, // 0x00 to 0xFF 0xD9, // Set pre charge 0xF1, 0xDB, // Set vcom detect 0x40, 0xA6, // Normal (0xA7=Inverse) 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>8); } } } Wire.endTransmission(); } uint8_t DigitChar (unsigned int number, unsigned int divisor) { return (number/divisor) % 10; } // Display icon void PlotIcon (int line, int column) { PlotChar(Icon, line, column); column = column + Scale*6; PlotChar(Icon+1, line, column); } void PlotBar (int h, int value) { int bar = value ? ((-32768 >> (11-value)) & 0x7FFF) >> 5 : 0; int column = h * 12 - 9; PlotChar(value ? value : Dash, 3, column); Wire.beginTransmission(OLEDAddress); Wire.write(command); // Set column address range Wire.write(0x21); Wire.write(column); Wire.write(column + 5); // Set page address range Wire.write(0x22); Wire.write(0); Wire.write(2); Wire.endTransmission(); Wire.beginTransmission(OLEDAddress); Wire.write(data); for (uint8_t col = 0 ; col < 6; col++) { int bits = bar; for (int i=3; i--;) { uint8_t d = Stretch(bits & 0x0F); Wire.write(d); bits = bits>>4; } } Wire.endTransmission(); } void PlotCursor (int column, int data1, int data2) { Wire.beginTransmission(OLEDAddress); Wire.write(command); // Set column address range Wire.write(0x21); Wire.write(column); Wire.write(column + 1); // Set page address range Wire.write(0x22); Wire.write(3); Wire.write(3); Wire.endTransmission(); Wire.beginTransmission(OLEDAddress); Wire.write(data); Wire.write(data1); Wire.write(data2); Wire.endTransmission(); } // Display a 5-digit frequency starting at line, column void PlotFreq (unsigned int freq, int line, int column) { boolean dig = false; for (unsigned int d=10000; d>0; d=d/10) { char c = DigitChar(freq, d); if (c == 0 && !dig) c = Space; else dig = true; PlotChar(c, line, column); column = column + Scale*6; } PlotChar(Hz, line, column); column = column + Scale*6; PlotChar(Hz+1, line, column); } // Rotary encoder ********************************************** const int EncoderA = 3; const int EncoderB = 4; const int MinFreq = 1; // Hz const int MaxFreq = 1500; // Hz volatile int a0; volatile int c0; volatile int Cursor = 0; volatile boolean Click; void SetupRotaryEncoder () { pinMode(EncoderA, INPUT_PULLUP); pinMode(EncoderB, INPUT_PULLUP); PCMSK = 1<= 1000) step = 100; else if (Freq >=100) step = 10; Freq = max(min((Freq + (Up ? step : -step)), MaxFreq), MinFreq); PlotFreq(Freq, 1, 42); Jump = Freq*4; } void ChangeHarmonic (bool Up) { Click = !Click; // Ignore alternate pulses if (!Click) return; Harmonic[Cursor] = (Harmonic[Cursor] + (Up ? 10 : 1)) % 11; PlotBar(Cursor+1, Harmonic[Cursor]); } // Pin change interrupt service routine ISR (PCINT0_vect) { int a = PINB>>EncoderA & 1; int b = PINB>>EncoderB & 1; if (a != a0) { // A changed a0 = a; if (b != c0) { c0 = b; int up = (a == b); if (Mode) ChangeHarmonic(up); else ChangeFreq(up); } } } void Mydelay (unsigned long millis) { for (volatile unsigned long i = 23*millis; i!=0; i--); } // Setup ********************************************** void setup() { Wire.begin(); // Is it a power-on reset? if (MCUSR & 1) { Mode = false; Freq = 100; // Start with 100Hz for (int h=1; h<=10; h++) Harmonic[h-1] = h; InitDisplay(); } else Mode = !Mode; MCUSR = 0; ClearDisplay(); SetupDDS(); SetupRotaryEncoder(); if (Mode) { // Design mode Scale = 1; Cursor = 0; // Display graph for (int h=1; h<=Harmonics; h++) PlotBar(h, Harmonic[h-1]); PlotCursor(Cursor*12, 0x7F, 0x1C); PlotCursor(Cursor*12+10, 0x1C, 0x7F); } else { Jump = Freq*4; CalculateWave(); Scale = 2; // Big text PlotIcon(1, 0); PlotFreq(Freq, 1, 42); } } // Everything done by interrupts void loop() { if (Mode) { int a = analogRead(0); if (a<922) { // Button press PlotCursor(Cursor*12, 0, 0); PlotCursor(Cursor*12+10, 0, 0); // Remove old cursor if (a>717) Cursor = (Cursor+9)%10; // Down else Cursor = (Cursor+1)%10; // Up PlotCursor(Cursor*12, 0x7F, 0x1C); PlotCursor(Cursor*12+10, 0x1C, 0x7F); // Draw new cursor while (analogRead(0) < 922); // Wait for key up Mydelay(100); // Debounce the pushbuttons } } }