Maker.io main logo

A Simple Square-Wave Function Generator with an Arduino

2020-12-09 | By Maker.io Staff

License: See Original Project Potentiometers

Many makers rarely use the analog outputs on their Arduino, even though they can be useful in a wide variety of applications - such as dimming an LED. Most people have done that at least once, and countless tutorials use an analog I/O pin to drive an LED. This project shows how to connect multiple simple I/O components to an Arduino to create an uncomplicated function generator.

BOM

Choosing the Right Arduino

Most Arduino boards can easily convert an analog voltage level to a digital value with the help of a built-in analog-to-digital converter (ADC). This is the value that displays when performing an analogRead operation on one of the dedicated analog I/O pins on an Arduino.

However, what many people don’t know is that this doesn’t necessarily mean that you can also output an analog value in the same way. Instead, the software will automatically transform an analogWrite call to a pulse-width modulated digital signal instead of an appropriate voltage value.

signal_2

The mentioned technique is fine for many applications, like dimming an LED or controlling the speed of a motor. For this example, however, that simple technique is not good enough because the Arduino needs to produce voltage values somewhere between LOW and HIGH.

Therefore, we will use an Arduino with a built-in DAC, like the Arduino Due or the MKR Vidor 4000 board. Note that it’s also possible to use an external DAC if necessary.

The Basics of the Circuit

As mentioned above, a few simple physical input and output components control the signal that the Arduino generates. For that purpose, this project utilized two 20K Ohm potentiometers and two 10K resistors to build a voltage divider that will reduce the voltage from five volts to 3.3 V. Note that this may not be necessary on all Arduinos, as most of them should be 5 V tolerant. The middle pins of the potentiometers were then connected to two separate analog input ports on the Arduino. Finally, two pushbutton switches were added: one for resetting the circuit, and one that allows the user to change the generated waveform and cycle through a few settings.

board_3

(Image source: Created by author)

The buttons above are used in combination with appropriate pull-down resistors, along with an LED that indicates that the circuit is on.

A more detailed schematic of the entire project can be seen below:

schematic_4

Learn more by looking at the schematic for the project on Scheme-It.

The Importance of the Software

Like many other Arduino-based projects, the software is a vital aspect of this build. In this case, the code reads the user input and detects when the state of one of the two potentiometers changes, or when a button gets pressed. As stated above, the generated waveform can be changed with one of the two push-buttons. The software then generates the output, depending on the state that the circuit is in:

Copy Code
// IMPORTANT!
// Change these according to your Arduino!
// FUN_PIN and ACC_PIN must be interrupt pins
// Please refer to the documentation: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
#define LED_PIN 7
#define FUN_PIN 0
#define ACC_PIN 1
#define OUT_PIN A0
#define HTIME A5
#define LTIME A4

// The maximum value that the analog inputs will read
// 680 = 3.3V on a 5V Arduino
const int LIMIT = 680;

int l = 100; // Low time of the output waveform
int h = 100; // High time
int ch = 0; // Counter variable
int cl = 0; // Counter variable
int incline = 0; // Counter for the sawtooth
float step = 0;
float angle = 0;
int state = 0; // The state describes what the output waveform will look like (0 = square, 1 = sine, 2 = sawtooth)

// De-bounce variables
unsigned long lastChange = 0;
unsigned long debounceDelay = 50;

void setup()
{
Serial.begin(9600);

// Set up all the pins
pinMode(LED_PIN, OUTPUT);
pinMode(OUT_PIN, OUTPUT);
pinMode(FUN_PIN, INPUT);
pinMode(ACC_PIN, INPUT);
pinMode(HTIME, INPUT);
pinMode(LTIME, INPUT);

// Attach interrupts to the pins that the buttons are connected to
attachInterrupt(digitalPinToInterrupt(ACC_PIN), acceptChange, RISING);
attachInterrupt(digitalPinToInterrupt(FUN_PIN), changeFunction, RISING);

// Turn the LED on (It's just a simple power-on indicator)
digitalWrite(LED_PIN, HIGH);

// Reset the values
acceptChange();
}

/**
*
* Changes the waveform of the output if the appropriate button gets pressed.
* Note: If you call this function, you'll have to wait for the debounceDelay (in ms)
* to pass before you can call it again.
*
**/
void changeFunction()
{
if ((millis() - lastChange) > debounceDelay)
{
// Switch to the next state
state = state + 1;

if(state > 2)
state = 0;

lastChange = millis();
acceptChange();
}
}

/**
*
* Resets the counter variables and reads the state of the potentiometers
*
**/
void acceptChange()
{
h = clamp(analogRead(HTIME), 1, LIMIT);
l = clamp(analogRead(LTIME), 1, LIMIT);
cl = 0;
ch = 0;
step = 360.0f / h;
incline = 1024 / h;
angle = 0;
}

int clamp(int value, int min, int max)
{
if(value < min)
return min;

if(value > max)
return max;

return value;
}

/**
*
* Outputs a square wave on the output pin
*
**/
void doSquareWave()
{
// This is a very basic function that doesn't use the delay functions
// Instead, it relies on counter variables that it increases with every call.
// When both counters are 0, the method outputs a HIGH state. It remains in this
// state, until ch (the counter variable for the high time) is greater than
// the specified high time (which can be changed with the potentiometer).
// If that's the case, the function pulls the output pin LOW.
// It then remains in that state until cl is greater than l.
// Then, the function resets the two counters and everything starts over

if(ch == 0 && cl == 0)
{
analogWrite(OUT_PIN, 1023);
ch = 1;
}

if(ch <= h && cl == 0)
ch = ch + 1;

if(ch > h && cl == 0)
{
analogWrite(OUT_PIN, 0);
cl = 1;
}

if(ch > h && cl <= l)
cl = cl + 1;

if(ch > h && cl > l)
{
ch = 0;
cl = 0;
}
}

/**
*
* Outputs a sine wave on the output pin
*
**/
void doSineWave()
{
// This essentially works the same as the square wave function.
// However, instead of creating a digital output, the counter
// variable is as an angle and passed to the sine function.
// Then, the counter is increased by a step until it reaches 360

float rads = (angle * 71) / 4068;
int val = 512 + (512.0 * sin(rads));

analogWrite(OUT_PIN, val);
angle = (angle + step);

if(angle >= 360.0)
angle = 0;
}

/**
*
* Outputs a triangle wave on the output pin
*
**/
void doSawTooth()
{
int val = ch % 1024;

analogWrite(OUT_PIN, val);
ch = ch + incline;

if(ch >= 1024)
ch = 0;
}

void loop()
{
analogWriteResolution(11);

// Read the potentiometer states to detect changes
int htemp = clamp(analogRead(HTIME), 1, LIMIT);
int ltemp = clamp(analogRead(LTIME), 1, LIMIT);

// If the user changed the values, reset the circuit
if(ltemp != l || htemp != h)
acceptChange();

// Output a waveform depending on the current state
switch(state)
{
case 0:
doSquareWave();
break;

case 1:
doSineWave();
break;

case 2:
doSawTooth();
break;

default:
doSquareWave();
break;
}
}

Note that this code targets Arduinos with a built-in DAC with a minimum resolution of 10 bits. If you’re using a different DAC, make sure to adjust the values accordingly!

Using an Arduino without a Built-In DAC

Connecting all components to a compatible Arduino as described above means that everything should be ready to go once everything is plugged in. Users without a built-in DAC for their Arduino, however, will have to add an external DAC of some sort. There are many different methods that you can utilize to receive a true analog output, but one easy recommendation is using a simple external I2C digital-to-analog converter module.

connecting_5

Learn more by looking at the schematic for the project on Scheme-It.

Note that you’ll also have to update the code accordingly. For this particular DAC module, the most obvious choice is to download and use the official Adafruit library that comes with it. Then, instead of doing the analogWrite operations, send the digital value to the DAC:

Copy Code
// Replace this:
analogWrite(OUT_PIN, val);
// with this:
dac.setVoltage(val, false);

Furthermore, add the following line to the setup method:

Copy Code
dac.begin(0x62);

For additional support, take a look at the official documentation.

Some Final Thoughts for your Project

When attempting to build this project, keep in mind that the resulting function generator will not be very accurate due to how the signal gets generated. In this project, the maximum frequency of the finished product will heavily depend on the Arduino you use and its internal clock speed. Even with all this in mind, the project can teach a lot about digital inputs and analog I/O in general, as well as loops and states in a program.

This project demonstrates how you can attach multiple physical inputs to an Arduino to allow a user to tweak how the finished product operates. For that purpose, the Arduino acts as a very basic function generator that can continuously output one of three waveforms:

waveforms_6

Make sure you use an Arduino with a built-in DAC. If you don't have one, you can add an external DAC of some sort, which will then generate a true analog output. Furthermore, you should keep in mind that this is a basic function generator. The output can’t go above +5 V, and it also can’t go below zero Volts. Additionally, the frequency and accuracy are limited and depend on the Arduino and its clock speed.

制造商零件编号 A000062
ARDUINO DUE ATSAM3X8E EVAL BRD
Arduino
制造商零件编号 935
EVAL BOARD FOR MCP4725
Adafruit Industries LLC
制造商零件编号 FIT0096
BREADBRD TERM STRIP 3.20X2.00"
DFRobot
制造商零件编号 P160KN-0QC15B20K
POT 20K OHM 1/5W PLASTIC LINEAR
TT Electronics/BI
制造商零件编号 OEJA-50-3-7
KNOB KNURLED 0.236" METAL
Kilo International
制造商零件编号 MJTP1230
SWITCH TACTILE SPST-NO 0.05A 12V
APEM Inc.
制造商零件编号 C503B-RAN-CZ0C0AA1
LED RED CLEAR 5MM ROUND T/H
Cree LED
制造商零件编号 CF14JT10K0
RES 10K OHM 5% 1/4W AXIAL
Stackpole Electronics Inc
制造商零件编号 CFM14JT220R
RES 220 OHM 5% 1/4W AXIAL
Stackpole Electronics Inc
制造商零件编号 PRT-12795
JUMPER WIRE M/M 6" 20PCS
SparkFun Electronics
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum