cisy
Published © GPL3+

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.

AdvancedShowcase (no instructions)7,359
Function Generator (Sine, Triangular, Chainsaw, 32 kHz Max)

Things used in this project

Hardware components

Alphanumeric LCD, 16 x 2
Alphanumeric LCD, 16 x 2
To display the settings of the generator
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
One to set brightness of display and one to set the shape and the frequency.
×2
Resistor 39 Ohm
These resistors were used to build the DAC: (Ohm: 9.75/19.5/39/78/156/312/624/1248). I did not use 32x39Ohm resistors in line to get to 1248 Ohm...)
×69
Resistor 1k ohm
Resistor 1k ohm
For the switch
×1
Resistor 221 ohm
Resistor 221 ohm
For the LCD-display
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
To solder the DAC (soldering the resistors)
Solder Dispenser, Solder-Mate
Solder Dispenser, Solder-Mate

Story

Read more

Custom parts and enclosures

Scheme of Signal Generator

Scheme

Schematics

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

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).
/*  Arduino Starter Kit example    
  Project Wave Generator

  This sketch is written to have a source to tune music instruments and later get to the limits...

  Parts required:
  1x switch
  1x 220 ohm resistor
  A resistor network to create an 8 bit DAC to be connected to the port D
  2x 10 kilohm potentiometer
  1x 16x2 LCD screen
  1x piezo

  Created 21. April 2019
  by Christian Styger

  Caution: This source code was tailored to the windows compiler version 1.8.9.
  If other versions are used, the timing might not be correct. 
  >> Use compiler version 1.8.9. (or 1.7.10)!
*/

// include the library code for LCD-Display:
#include <LiquidCrystal.h>
#define NOOP(n) __builtin_avr_delay_cycles (n)

const double Pi = 3.1415926535;
const 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

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);

const byte Pot_Read_Pin = A5;
const byte Control_Switch_Pin = A4;

// set up a constant for time delay when a switch was pressed in ms:
const int Delay_Time = 500;

// Pins 8,9,10,11,12,13 set to OUTPUT for LCD-display
const byte Port_B_Pinrange_LCD     = B00111111;

// Pins A0..A1 set to Input; A2..A5 set for additional DAC channels
const byte Port_C_Pinrange_Operate = B00111100;

// Pins A0..A5 set for Input (reading the parameters to set generator)
const byte Port_C_Pinrange_Set     = B00000000;

// Pins 7,6,5,4,3,2,1 and 0 [Caution D0 is usually Rx!!!] set to OUTPUT for 8 bit DAC
const byte Port_D_Pinrange_Operate = B11111111; // Port D, being DAC output, having 8 bits (8 bit resolution)

// Pins 7,6,5,4,3,2,1 set to OUTPUT , and D0 is ready for Rx-funcion to PC
const byte Port_D_Pinrange_Set     = B11111110;

int Pot_Value = 1;
byte T_or_F = 0;  //This switch indicates if T: Time in us or F: Frequency in Hz is being displayed)

const byte Generator_Shape_Range = 5;           // 0: Sinewave; 1: Triangular; 2: Chainsaw down; 3: Chainsaw up; 4: Rectangular; 
byte Generator_Shape = 4;                       // Starting shape is sinewave (=(Generator_Shape+1)% Generator_Shape_Range)...
byte Generator_Type;                            // Identification of generator to be used, depending on Tau (length of signal period)...
const byte Rectangular_Divide = 8;              // Shorter cycle times possible due to simplicity of rectanguals signal...
byte Split, Divide = 1;              
byte L, k;                                      // Number of data points that need to be extended by L_Step; k : number of periods to be mapped in P_D...
byte Index,s;
unsigned long NOPs_long; 
unsigned long NOPs_Step, NOPs_t, NOPs_Tau = Clock_Steps_per_us*1E6/1000; //  starting value of Tau in NOPs - corresponds to 1000Hz ....
unsigned 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
unsigned 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,...). 
byte I_Shift = 0, P_Shift = 0, Scale = 0;       // Two parameters to ensure that starting data point of a signal is zero. 
byte Signal_Shape_Memory[256];                  // Array containing 256 data points to be written to the 1 byte DAC; 
byte 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.  

byte rectangular (long Time, long Period) {return  255  *((2*Time/Period)&1);}
byte sine(long Time, long Period)         {return (512  *(0.5*(1+sin((Time*2.0/Period-0.5)*Pi))))/2;}
byte chainSawUp(long Time, long Period)   {return  256.0*(Time-int (Time/Period)*Period)/Period;}
byte triangular(long Time, long Period)   {return  256.0*(abs(1-2.0*((Time)-int((Time)/Period)*Period)/Period));}

void setup() {
  pinMode(Pot_Read_Pin, INPUT);         // declare pin for potentiometer as input
  pinMode(Control_Switch_Pin, INPUT);   // declare the interrupt pin as an input
  // setting the ports...
  DDRB |= Port_B_Pinrange_LCD;          // Pins 13,12,11,10,9,8 set to OUTPUT for LCD-display
  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
  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
  lcd.begin(16, 2);
  lcd.clear();
}


byte PD(byte I) {
  // Calculates the average level between Func(NOPs_Tau) and Func(NOPs_Tau + NOPs_Step)....
  byte Index = I;
  byte Level = 0;
  s = Pot_Value*k/2;
  while (s > 0) {
    if (Index&1 == 1) Level += s;
    s /= 2;
    Index /= 2;
  }
  long Step;
  if (L > Level) {Step = NOPs_t+NOPs_Step+1; Signal_NOP_Delay_Memory[I] = 255;}  //     prolonging data point delay by 1 NOP
  else           {Step = NOPs_t+NOPs_Step  ; Signal_NOP_Delay_Memory[I] = 0  ;}  // not prolonging data point delay by 1 NOP
  byte P;
  if      (Generator_Shape == 0) P =  sine       (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift;
  else if (Generator_Shape == 1) P = ~triangular (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift;
  else if (Generator_Shape == 2) P = ~chainSawUp (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift+Scale*((16-I)&15);
  else if (Generator_Shape == 3) P =  chainSawUp (NOPs_t+Step,2*NOPs_Tau/k)-P_Shift+Scale*I;
  else if (Generator_Shape == 4) P =  rectangular(NOPs_t     ,  NOPs_Tau/Divide/k);
  NOPs_t = Step;
  Signal_Shape_Memory[I] = P;
  return (P);
}

void Show_Parameter ()
  {
    lcd.setCursor (0, 1);
    if (T_or_F == 0) {
      lcd.print ("T:              ");
      lcd.setCursor (3, 1);
      lcd.print (1.0*NOPs_Tau/Clock_Steps_per_us, 4);
      lcd.setCursor (14, 1);
      lcd.print ("us");
    } else {
      lcd.print ("F:              ");
      lcd.setCursor (3, 1);
      lcd.print (1000000.0/NOPs_Tau*Clock_Steps_per_us, 4);
      lcd.setCursor (14, 1);
      lcd.print ("Hz");
    } 
  }

void loop() {
  
  // Select the generator type (sine, rectangular, triangular, etc.), using potentiometer to select & button to confirm...
  lcd.setCursor(0, 0);
  lcd.print ("Gen");      
  while (digitalRead(Control_Switch_Pin) == HIGH) {
    if (Pot_Value != 0) {
      if (Pot_Value > 0) s = 1; else s = Generator_Shape_Range-1; 
      Generator_Shape = (Generator_Shape+s)%Generator_Shape_Range;
      lcd.setCursor(4, 0);  
      if      (Generator_Shape == 0) lcd.print ("Sine Wave  ");
      else if (Generator_Shape == 1) lcd.print ("Triangular ");
      else if (Generator_Shape == 2) lcd.print ("Chainsaw Dn");
      else if (Generator_Shape == 3) lcd.print ("Chainsaw Up");
      else if (Generator_Shape == 4) lcd.print ("Rectangular");
    }
    delay (2*Delay_Time);  
    Pot_Value = map(analogRead(Pot_Read_Pin), 0, 1023, -5, +5);      
  }
  if (Generator_Shape == 4) NOPs_Tau_Min /= Rectangular_Divide;


  // Set display to either Frequency or Time, using potentiometer to switch & button to confirm...
  lcd.setCursor(0, 0);
  lcd.print ("F/T");
  Show_Parameter ();
  while (digitalRead(Control_Switch_Pin) == LOW);
  delay (Delay_Time);  
  while (digitalRead(Control_Switch_Pin) == HIGH) {
    if (Pot_Value != 0) {
      T_or_F = ++T_or_F&1;
      Show_Parameter ();
    }
    delay (Delay_Time);  
    Pot_Value = map(analogRead(Pot_Read_Pin), 0, 1023, -12, +12);      
  }


  // Set the Frequency or Time (period length), using potentiometer to sselect & button to confirm...
  lcd.setCursor(0, 0);
  lcd.print ("Set");
  while (digitalRead(Control_Switch_Pin) == LOW);
  delay (Delay_Time);  
  while (digitalRead(Control_Switch_Pin) == HIGH) {
    Pot_Value = map(analogRead(Pot_Read_Pin), 0, 1023, -12, +12);
    if (T_or_F == 1) Pot_Value = -Pot_Value; 
    NOPs_Step = abs(Pot_Value*Pot_Value*Pot_Value);
    if (Pot_Value != 0) {
      s = 1;
      if      (Pot_Value ==  12) {NOPs_Step =  NOPs_Tau;   s = 2*Delay_Time;} 
      else if (Pot_Value == -12) {NOPs_Step =  NOPs_Tau/2; s = 2*Delay_Time;}
      if (Pot_Value < 0)
        if (NOPs_Tau-NOPs_Tau_Min >= NOPs_Step) 
          NOPs_Tau -= NOPs_Step; 
        else
          NOPs_Tau = NOPs_Tau_Min;     
      else   
        if (NOPs_Tau_Max-NOPs_Tau >= NOPs_Step) 
          NOPs_Tau += NOPs_Step; 
        else
          NOPs_Tau = NOPs_Tau_Max;  
      Show_Parameter ();
      delay (Delay_Time/(Pot_Value*Pot_Value)+s);
    }
  }
  if ((Generator_Shape == 4) && (NOPs_Tau < 16000)) {Divide = Rectangular_Divide; NOPs_Tau *= Rectangular_Divide;} 

// Selection of generator_Type...
  if      (NOPs_Tau >= 4864) Pot_Value=256;  //304 us: 256 * 19 NOPs = 4864 NOPs  (/16 = 304 us =  3.289 kHz )   
  else if (NOPs_Tau >= 2432) Pot_Value=128;  //152 us: 128 * 19 NOPs = 2432 NOPs  (/16 = 152 us =  6.578 kHz )
  else if (NOPs_Tau >= 1216) Pot_Value= 64;  // 76 us:  64 * 19 NOPs = 1216 NOPs  (/16 =  76 us = 13.157 kHz )
  else if (NOPs_Tau >=  608) Pot_Value= 32;  // 38 us:  32 * 19 NOPs =  608 NOPs  (/16 =  38 us = 26.315 kHz )
  else    {                  Pot_Value= 16;  // 19 us:  16 * 19 NOPs =  304 NOPs  (/16 =  19 us = 52.631 kHz)  
                             Scale=1;}       // Scale = 1 increases amplitude of Chainsaw up and down at low resolution. i.e. 16 data points... 

  NOPs_Step = NOPs_Tau/Pot_Value;            //38...(4'294'967'296-1)/256 [16'777'215] NOPs_Tau_max: 268.435456 sec
  L = NOPs_Tau%Pot_Value;                    //L (NOPs_Tau_ long_max): 255
  k = 256/Pot_Value;

  if      (NOPs_Step >= 31) {Generator_Type =  31; Split = (NOPs_Step-31)%6; NOPs_long = (NOPs_Step-31)/6; }  
  else if (NOPs_Step >= 25) {Generator_Type =  25; Split =  NOPs_Step-25;}
  else if (NOPs_Step >= 22) {Generator_Type =  22; Split =  NOPs_Step-22;}
  else                      {Generator_Type = NOPs_Step;}
  
  // Setting up parameters to calculate shape...
  NOPs_Tau *= k;
  L *= k;
  
  // Adjust all shapes such theat zero value is starting point (PD_0 = 0)... 
  if (Generator_Shape <= 3) I_Shift=1;
  NOPs_t = NOPs_Tau-I_Shift*(NOPs_Step);   
  
  P_Shift = PD(0);
  Signal_Shape_Memory[0] = 0;

  // Calculating & loading the signal shape into array P_D... 
  Index=0;
  while (Index < 255) Signal_Shape_Memory[++Index] = PD(Index);    

  // Starting the appropriate generator according to settings...
  lcd.setCursor(0, 0);
  lcd.print ("Run");

  noInterrupts();
  DDRD |= Port_D_Pinrange_Operate;      // Pins 7,6,5,4,3,2,1,0
  
  switch (Generator_Type)  {
    case 19: Loop_256_19       (); break;  
    case 20: Loop_256_20       (); break;
    case 21: Loop_256_21       (); break;
    case 22: Loop_256_22_24    (); break;
    case 25: Loop_256_25_33    (); break;
    case 31: Loop_256_31_36_6n (); break;
  }
}

void Loop_256_31_36_6n ()
{
  unsigned long NOPs_6x = long (abs(NOPs_long));
  byte Shift = Split+2;
  byte Index = 0;
  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...)
  unsigned long Cycle;
  
  while (true) {
    ++Index; 
    PORTD = Signal_Shape_Memory[Index]; 
    Cycle = NOPs_6x; 
    while (--Cycle<~0) NOOP(0); 
    if (Shift&1) NOOP(2); 
    if (~Shift&4); 
      else if (Shift&2) NOOP(3); 
        else NOOP(0); 
    if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1);
  }
}

void Loop_256_25_33 ()
{
  byte Shift = ((Split)/3)*4+5+((Split)%3);
  byte Index = 0;
  while (true) {
    ++Index;
    PORTD = Signal_Shape_Memory[Index];
    if (~Shift&2);
    else if (Shift&1) NOOP(2);
      else NOOP(0); 
    if (~Shift&8);
      else if (Shift&4) NOOP(5);
      else NOOP(1);
      if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1);
  }
}

void Loop_256_22_24 ()
{
  byte Shift = Split+1;
  byte Index = 0;
  while (true) {
    ++Index;
    PORTD = Signal_Shape_Memory[Index];
    if (~Shift&2) NOOP(1); 
      else {NOOP(1); if (Shift&1) NOOP(2);  //This is the correct line for compiler Arduino 1.8.9  
        else NOOP(0);}                      //This is the correct line for compiler Arduino 1.8.9  
//      else if (Shift&1) NOOP(2);          //This is the correct line for compiler Arduino 1.7.10 
//        else NOOP(0);                     //This is the correct line for compiler Arduino 1.7.10 
    if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1);
  }
}

void Loop_256_21 ()
{
  byte Index = 0;
  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...)
  while (true) {
    ++Index;
    PORTD = Signal_Shape_Memory[Index];
    NOOP(2);
    if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1);
  }
}

void Loop_256_20 ()
{
  byte Index = 0;
  while (true) {
    NOOP(1);
    ++Index;
    NOOP(1);
    PORTD = Signal_Shape_Memory[Index];
    if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1);
  }
}

void Loop_256_19 ()
{
  byte Index = 0;
  while (true) {
    ++Index;
    PORTD = Signal_Shape_Memory[Index];
  if (Signal_NOP_Delay_Memory[Index]) NOOP(3); else NOOP(1);
  }
}

Credits

cisy

cisy

0 projects • 0 followers

Comments