Hardware components | ||||||
| × | 1 | ||||
| × | 2 | ||||
| × | 69 | ||||
| × | 1 | ||||
| × | 1 | ||||
Hand tools and fabrication machines | ||||||
| ||||||
|
I wanted to check out how well Athe Arduino Uno is suited to generate periodic signals, trying to get the most out of it.
This is the simplest version of the generator.
signal shape: sine-wave, triangular, chop-saw, chop-saw reverse - up to 32.258 kHz signal shape: rectangular - up to 258.065 kHz
(for those interested, I have built a more elaborate sketch that reaches:
signal shape: sine-wave, triangular, chop-saw, chop-saw reverse - up to 333.33 kHz signal shape: rectangular - up to 4 MHz; contact me to share the code)
The signal output is the result of the simplest possible 8bit DAC: A0...A7 are combined with resistors 9.75 Ohm / 19.5 Ohm / 39 Ohm /78 Ohm /... / 1248 Ohm (resulting in a 1 byte DAC), generating 0..5V output with 1/256 accuracy.
In the sketch the signal shape and period (or frequency) can be set manually (using one switch and on potentiometer) to the accuracy of the Arduino: Time resolution of the period length is 1/f(CPU) = 62.5ns. The setting is shown on the LCD.
Number of data points is 256 (0..255).
Interrupts must be disabled.
Attention: As this sketch is dealing with NoOperation-Delays, it must be compiled with the Arduino version 1.7.10 or 1.6.8 to get the proper timing!! It is very unfortunate that some later IDE-versions do not get the NOP-handling properly.
Source code for this signal generator
Arduino/* 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);
}
}
Comments