Function Generator (Sine, Triangular, Chainsaw, 32 kHz Max)
Delivers excellent signals up to 32.258 kHz (sine wave, etc.), up to 258.064 kHz (rectangular). Output: 0..5V, max res 1 byte.
Devices & Components
Resistor 221 ohm
Rotary potentiometer (generic)
Resistor 39 Ohm
Resistor 1k ohm
Alphanumeric LCD, 16 x 2
Hardware & Tools
Soldering iron (generic)
Solder Dispenser, Solder-Mate
Project description
Code
Source code for this signal generator
arduino
Just copy & paste it int Arduino IDE, using Arduino version 1.8.9. (tested with Windows only).
1/* Arduino Starter Kit example 2 Project Wave Generator 3 4 This sketch is written to have a source to tune music instruments and later get to the limits... 5 6 Parts required: 7 1x switch 8 1x 220 ohm resistor 9 A resistor network to create an 8 bit DAC to be connected to the port D 10 2x 10 kilohm potentiometer 11 1x 16x2 LCD screen 12 1x piezo 13 14 Created 21. April 2019 15 by Christian Styger 16 17 Caution: This source code was tailored to the windows compiler version 1.8.9. 18 If other versions are used, the timing might not be correct. 19 >> Use compiler version 1.8.9. (or 1.7.10)! 20*/ 21 22// include the library code for LCD-Display: 23#include <LiquidCrystal.h> 24#define NOOP(n) __builtin_avr_delay_cycles (n) 25 26const double Pi = 3.1415926535; 27const byte Clock_Steps_per_us = 1.0 * F_CPU / 1000000; // 1 Clock_Step is 0.0625us = 1/16us for Arduino Uno with 16 MHz Clock frequency 28 29// initialize the library with the numbers of the interface pins 30LiquidCrystal lcd(8, 9, 10, 11, 12, 13); 31 32const byte Pot_Read_Pin = A5; 33const byte Control_Switch_Pin = A4; 34 35// set up a constant for time delay when a switch was pressed in ms: 36const int Delay_Time = 500; 37 38// Pins 8,9,10,11,12,13 set to OUTPUT for LCD-display 39const byte Port_B_Pinrange_LCD = B00111111; 40 41// Pins A0..A1 set to Input; A2..A5 set for additional DAC channels 42const byte Port_C_Pinrange_Operate = B00111100; 43 44// Pins A0..A5 set for Input (reading the parameters to set generator) 45const byte Port_C_Pinrange_Set = B00000000; 46 47// Pins 7,6,5,4,3,2,1 and 0 [Caution D0 is usually Rx!!!] set to OUTPUT for 8 bit DAC 48const byte Port_D_Pinrange_Operate = B11111111; // Port D, being DAC output, having 8 bits (8 bit resolution) 49 50// Pins 7,6,5,4,3,2,1 set to OUTPUT , and D0 is ready for Rx-funcion to PC 51const byte Port_D_Pinrange_Set = B11111110; 52 53int Pot_Value = 1; 54byte T_or_F = 0; //This switch indicates if T: Time in us or F: Frequency in Hz is being displayed) 55 56const byte Generator_Shape_Range = 5; // 0: Sinewave; 1: Triangular; 2: Chainsaw down; 3: Chainsaw up; 4: Rectangular; 57byte Generator_Shape = 4; // Starting shape is sinewave (=(Generator_Shape+1)% Generator_Shape_Range)... 58byte Generator_Type; // Identification of generator to be used, depending on Tau (length of signal period)... 59const byte Rectangular_Divide = 8; // Shorter cycle times possible due to simplicity of rectanguals signal... 60byte Split, Divide = 1; 61byte L, k; // Number of data points that need to be extended by L_Step; k : number of periods to be mapped in P_D... 62byte Index,s; 63unsigned long NOPs_long; 64unsigned long NOPs_Step, NOPs_t, NOPs_Tau = Clock_Steps_per_us*1E6/1000; // starting value of Tau in NOPs - corresponds to 1000Hz .... 65unsigned long NOPs_Tau_Max = 4294967295; //2^32-1 NOPs = 268'435'455.9375us = 4 min 28.435 s is maximum period, using 256 data points to be read out with max delay 66unsigned int NOPs_Tau_Min = 304; // Minimum Tau ist 19 us (=16*19 NOPs) for signal shape with minimum resolution of 16 points / period (sine, triangular,...). 67byte I_Shift = 0, P_Shift = 0, Scale = 0; // Two parameters to ensure that starting data point of a signal is zero. 68byte Signal_Shape_Memory[256]; // Array containing 256 data points to be written to the 1 byte DAC; 69byte Signal_NOP_Delay_Memory[256]; // Array containing 0 or 255: 0 = No increase by 1 NOP of the data point; 255 = Prolong delay by 1 NOP. 70 71byte rectangular (long Time, long Period) {return 255 *((2*Time/Period)&1);} 72byte sine(long Time, long Period) {return (512 *(0.5*(1+sin((Time*2.0/Period-0.5)*Pi))))/2;} 73byte chainSawUp(long Time, long Period) {return 256.0*(Time-int (Time/Period)*Period)/Period;} 74byte triangular(long Time, long Period) {return 256.0*(abs(1-2.0*((Time)-int((Time)/Period)*Period)/Period));} 75 76void setup() { 77 pinMode(Pot_Read_Pin, INPUT); // declare pin for potentiometer as input 78 pinMode(Control_Switch_Pin, INPUT); // declare the interrupt pin as an input 79 // setting the ports... 80 DDRB |= Port_B_Pinrange_LCD; // Pins 13,12,11,10,9,8 set to OUTPUT for LCD-display 81 DDRC |= Port_C_Pinrange_Set; // Pins A5,A4,A3,A2 set to INPUT for Additional DAC bits 9-12, Pins A1, A0 set to INPUT for prgram control 82 DDRD |= Port_D_Pinrange_Set; // Pins 7,6,5,4,3,2,1 set to OUTPUT, and D0 is ready for Rx-function to PC 83 lcd.begin(16, 2); 84 lcd.clear(); 85} 86 87 88byte PD(byte I) { 89 // Calculates the average level between Func(NOPs_Tau) and Func(NOPs_Tau + NOPs_Step).... 90 byte Index = I; 91 byte Level = 0; 92 s = Pot_Value*k/2; 93 while (s > 0) { 94 if (Index&1 == 1) Level += s; 95 s /= 2; 96 Index /= 2; 97 } 98 long Step; 99 if (L > Level) {Step = NOPs_t+NOPs_Step+1; Signal_NOP_Delay_Memory[I] = 255;} // prolonging data point delay by 1 NOP 100 else {Step = NOPs_t+NOPs_Step ; Signal_NOP_Delay_Memory[I] = 0 ;} // not prolonging data point delay by 1 NOP 101 byte P; 102 if (Generator_Shape == 0) P = sine (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift; 103 else if (Generator_Shape == 1) P = ~triangular (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift; 104 else if (Generator_Shape == 2) P = ~chainSawUp (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift+Scale*((16-I)&15); 105 else if (Generator_Shape == 3) P = chainSawUp (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift+Scale*I; 106 else if (Generator_Shape == 4) P = rectangular(NOPs_t , NOPs_Tau/Divide/k); 107 NOPs_t = Step; 108 Signal_Shape_Memory[I] = P; 109 return (P); 110} 111 112void Show_Parameter () 113 { 114 lcd.setCursor (0, 1); 115 if (T_or_F == 0) { 116 lcd.print ("T: "); 117 lcd.setCursor (3, 1); 118 lcd.print (1.0*NOPs_Tau/Clock_Steps_per_us, 4); 119 lcd.setCursor (14, 1); 120 lcd.print ("us"); 121 } else { 122 lcd.print ("F: "); 123 lcd.setCursor (3, 1); 124 lcd.print (1000000.0/NOPs_Tau*Clock_Steps_per_us, 4); 125 lcd.setCursor (14, 1); 126 lcd.print ("Hz"); 127 } 128 } 129 130void loop() { 131 132 // Select the generator type (sine, rectangular, triangular, etc.), using potentiometer to select & button to confirm... 133 lcd.setCursor(0, 0); 134 lcd.print ("Gen"); 135 while (digitalRead(Control_Switch_Pin) == HIGH) { 136 if (Pot_Value != 0) { 137 if (Pot_Value > 0) s = 1; else s = Generator_Shape_Range-1; 138 Generator_Shape = (Generator_Shape+s)%Generator_Shape_Range; 139 lcd.setCursor(4, 0); 140 if (Generator_Shape == 0) lcd.print ("Sine Wave "); 141 else if (Generator_Shape == 1) lcd.print ("Triangular "); 142 else if (Generator_Shape == 2) lcd.print ("Chainsaw Dn"); 143 else if (Generator_Shape == 3) lcd.print ("Chainsaw Up"); 144 else if (Generator_Shape == 4) lcd.print ("Rectangular"); 145 } 146 delay (2*Delay_Time); 147 Pot_Value = map(analogRead(Pot_Read_Pin), 0, 1023, -5, +5); 148 } 149 if (Generator_Shape == 4) NOPs_Tau_Min /= Rectangular_Divide; 150 151 152 // Set display to either Frequency or Time, using potentiometer to switch & button to confirm... 153 lcd.setCursor(0, 0); 154 lcd.print ("F/T"); 155 Show_Parameter (); 156 while (digitalRead(Control_Switch_Pin) == LOW); 157 delay (Delay_Time); 158 while (digitalRead(Control_Switch_Pin) == HIGH) { 159 if (Pot_Value != 0) { 160 T_or_F = ++T_or_F&1; 161 Show_Parameter (); 162 } 163 delay (Delay_Time); 164 Pot_Value = map(analogRead(Pot_Read_Pin), 0, 1023, -12, +12); 165 } 166 167 168 // Set the Frequency or Time (period length), using potentiometer to sselect & button to confirm... 169 lcd.setCursor(0, 0); 170 lcd.print ("Set"); 171 while (digitalRead(Control_Switch_Pin) == LOW); 172 delay (Delay_Time); 173 while (digitalRead(Control_Switch_Pin) == HIGH) { 174 Pot_Value = map(analogRead(Pot_Read_Pin), 0, 1023, -12, +12); 175 if (T_or_F == 1) Pot_Value = -Pot_Value; 176 NOPs_Step = abs(Pot_Value*Pot_Value*Pot_Value); 177 if (Pot_Value != 0) { 178 s = 1; 179 if (Pot_Value == 12) {NOPs_Step = NOPs_Tau; s = 2*Delay_Time;} 180 else if (Pot_Value == -12) {NOPs_Step = NOPs_Tau/2; s = 2*Delay_Time;} 181 if (Pot_Value < 0) 182 if (NOPs_Tau-NOPs_Tau_Min >= NOPs_Step) 183 NOPs_Tau -= NOPs_Step; 184 else 185 NOPs_Tau = NOPs_Tau_Min; 186 else 187 if (NOPs_Tau_Max-NOPs_Tau >= NOPs_Step) 188 NOPs_Tau += NOPs_Step; 189 else 190 NOPs_Tau = NOPs_Tau_Max; 191 Show_Parameter (); 192 delay (Delay_Time/(Pot_Value*Pot_Value)+s); 193 } 194 } 195 if ((Generator_Shape == 4) && (NOPs_Tau < 16000)) {Divide = Rectangular_Divide; NOPs_Tau *= Rectangular_Divide;} 196 197// Selection of generator_Type... 198 if (NOPs_Tau >= 4864) Pot_Value=256; //304 us: 256 * 19 NOPs = 4864 NOPs (/16 = 304 us = 3.289 kHz ) 199 else if (NOPs_Tau >= 2432) Pot_Value=128; //152 us: 128 * 19 NOPs = 2432 NOPs (/16 = 152 us = 6.578 kHz ) 200 else if (NOPs_Tau >= 1216) Pot_Value= 64; // 76 us: 64 * 19 NOPs = 1216 NOPs (/16 = 76 us = 13.157 kHz ) 201 else if (NOPs_Tau >= 608) Pot_Value= 32; // 38 us: 32 * 19 NOPs = 608 NOPs (/16 = 38 us = 26.315 kHz ) 202 else { Pot_Value= 16; // 19 us: 16 * 19 NOPs = 304 NOPs (/16 = 19 us = 52.631 kHz) 203 Scale=1;} // Scale = 1 increases amplitude of Chainsaw up and down at low resolution. i.e. 16 data points... 204 205 NOPs_Step = NOPs_Tau/Pot_Value; //38...(4'294'967'296-1)/256 [16'777'215] NOPs_Tau_max: 268.435456 sec 206 L = NOPs_Tau%Pot_Value; //L (NOPs_Tau_ long_max): 255 207 k = 256/Pot_Value; 208 209 if (NOPs_Step >= 31) {Generator_Type = 31; Split = (NOPs_Step-31)%6; NOPs_long = (NOPs_Step-31)/6; } 210 else if (NOPs_Step >= 25) {Generator_Type = 25; Split = NOPs_Step-25;} 211 else if (NOPs_Step >= 22) {Generator_Type = 22; Split = NOPs_Step-22;} 212 else {Generator_Type = NOPs_Step;} 213 214 // Setting up parameters to calculate shape... 215 NOPs_Tau *= k; 216 L *= k; 217 218 // Adjust all shapes such theat zero value is starting point (PD_0 = 0)... 219 if (Generator_Shape <= 3) I_Shift=1; 220 NOPs_t = NOPs_Tau-I_Shift*(NOPs_Step); 221 222 P_Shift = PD(0); 223 Signal_Shape_Memory[0] = 0; 224 225 // Calculating & loading the signal shape into array P_D... 226 Index=0; 227 while (Index < 255) Signal_Shape_Memory[++Index] = PD(Index); 228 229 // Starting the appropriate generator according to settings... 230 lcd.setCursor(0, 0); 231 lcd.print ("Run"); 232 233 noInterrupts(); 234 DDRD |= Port_D_Pinrange_Operate; // Pins 7,6,5,4,3,2,1,0 235 236 switch (Generator_Type) { 237 case 19: Loop_256_19 (); break; 238 case 20: Loop_256_20 (); break; 239 case 21: Loop_256_21 (); break; 240 case 22: Loop_256_22_24 (); break; 241 case 25: Loop_256_25_33 (); break; 242 case 31: Loop_256_31_36_6n (); break; 243 } 244} 245 246void Loop_256_31_36_6n () 247{ 248 unsigned long NOPs_6x = long (abs(NOPs_long)); 249 byte Shift = Split+2; 250 byte Index = 0; 251 NOPs_long = NOPs_long+100; // This line is necessary to get the timing right for NOPSx_6x = 0, 1, 2... (I don't know why...) 252 unsigned long Cycle; 253 254 while (true) { 255 ++Index; 256 PORTD = Signal_Shape_Memory[Index]; 257 Cycle = NOPs_6x; 258 while (--Cycle<~0) NOOP(0); 259 if (Shift&1) NOOP(2); 260 if (~Shift&4); 261 else if (Shift&2) NOOP(3); 262 else NOOP(0); 263 if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1); 264 } 265} 266 267void Loop_256_25_33 () 268{ 269 byte Shift = ((Split)/3)*4+5+((Split)%3); 270 byte Index = 0; 271 while (true) { 272 ++Index; 273 PORTD = Signal_Shape_Memory[Index]; 274 if (~Shift&2); 275 else if (Shift&1) NOOP(2); 276 else NOOP(0); 277 if (~Shift&8); 278 else if (Shift&4) NOOP(5); 279 else NOOP(1); 280 if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1); 281 } 282} 283 284void Loop_256_22_24 () 285{ 286 byte Shift = Split+1; 287 byte Index = 0; 288 while (true) { 289 ++Index; 290 PORTD = Signal_Shape_Memory[Index]; 291 if (~Shift&2) NOOP(1); 292 else {NOOP(1); if (Shift&1) NOOP(2); //This is the correct line for compiler Arduino 1.8.9 293 else NOOP(0);} //This is the correct line for compiler Arduino 1.8.9 294// else if (Shift&1) NOOP(2); //This is the correct line for compiler Arduino 1.7.10 295// else NOOP(0); //This is the correct line for compiler Arduino 1.7.10 296 if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1); 297 } 298} 299 300void Loop_256_21 () 301{ 302 byte Index = 0; 303 NOPs_long = NOPs_long+100; // This line is necessary to get the timing right for NOPSx_6x = 0, 1, 2... (I don't know why...) 304 while (true) { 305 ++Index; 306 PORTD = Signal_Shape_Memory[Index]; 307 NOOP(2); 308 if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1); 309 } 310} 311 312void Loop_256_20 () 313{ 314 byte Index = 0; 315 while (true) { 316 NOOP(1); 317 ++Index; 318 NOOP(1); 319 PORTD = Signal_Shape_Memory[Index]; 320 if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1); 321 } 322} 323 324void Loop_256_19 () 325{ 326 byte Index = 0; 327 while (true) { 328 ++Index; 329 PORTD = Signal_Shape_Memory[Index]; 330 if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1); 331 } 332} 333
Downloadable files
Signal Generator (Wiring)
Potentiometer left: Brightness of LCD-display Button: Select signal shape / set display in Hz or us / start generator Potentiometer right: Select frequency / Period Rest button: stop & restart generator
Signal Generator (Wiring)
Signal Generator (Wiring)
Potentiometer left: Brightness of LCD-display Button: Select signal shape / set display in Hz or us / start generator Potentiometer right: Select frequency / Period Rest button: stop & restart generator
Signal Generator (Wiring)
Documentation
Scheme of Signal Generator
Scheme
Scheme of Signal Generator
Scheme of Signal Generator
Scheme
Scheme of Signal Generator
Comments
Only logged in users can leave comments