Projects

Touchscreen Function Generator

Rob Bell & Mike Hansell

Issue 8, February 2018

This article includes additional downloadable resources.
Please log in to access.

Log in

Create a useful function generator using a 4D Systems touchscreen display and an Arduino.

BUILD TIME: 1-1.5hrs
DIFFICULTY RATING: intermediate

Function generators are a useful piece of test equipment, often used in circuit testing and development. They provide a selection of outputs (generally sine wave, square wave, saw and triangle waves, and more). These are exceptionally useful to see how circuits behave and respond to different types of input signals commonly found in electronics. It can provide useful debugging if a circuit isn’t behaving properly too. Our function generator also features a random noise generator, which can be useful to test filtering and smoothing capabilities, and more.

It’s important to note that this function generator, while providing some fantastic outputs, is a limited software generator. Generally, it’s not comparable to a hardware-based solution. It’s fast and fairly accurate, but not the same as an expensive hardware-driven solution. It’s also reliant on the limitations of the Arduino IO and clock speed. Due to the restrictions of an Arduino’s processing abilities, we have very hard limits on the minimum and maximum frequencies, as well as the resolution of the steps in the analogue waveform. The beauty of this being a software-based function generator however, is that you can infinitely redevelop and improve the functionality without requiring any significant hardware changes or additions.

THE BROAD OVERVIEW

There are three parts to this circuit: the touch screen interface, which provides very useful touch-screen controls for controlling the waveform; an Arduino (a Nano or UNO is adequate), which does the actual waveform generation; and on the output is a resistor ladder, which converts our binary output from the Arduino to an analogue output.

With our software, we rely on state-changes in loops to virtualise the waveforms. For this reason, we have to be careful we don’t make the Arduino work too hard. What we mean here, is that serial monitoring, interrupt, and other advanced functionality can distort our waveforms. So we’ve taken things to the most basic of levels, to try and reduce the workload and potential interruptions to the wave forms. The result is higher frequency outputs, with better stability.

HOW IT WORKS

There are three distinct pieces of hardware utilised:

  1. The 4D Systems gen4 Touchscreen Display
  2. The Arduino (a Nano or bigger is suitable)
  3. The resistor ladder DAC converting our binary output into analogue signals

THE TOUCHSCREEN DISPLAY

We’ve used a 4.3” gen4 touchscreen display from 4D Systems. It’s 480 x 272 pixels in a TFT display, with 65,000 colours. There are resistive and capacitive touch (and non-touch, but what’s the fun in that?) versions available. This provides us with a stunning UI.

If you haven’t encountered 4D Systems’ displays before, they’re definitely worth getting familiar with. One of the major caveats with Arduino is the lack of display processing. Of course this isn’t a flaw or bug, it’s a design consideration. High quality graphics processing simply isn’t something an Arduino is really designed to do. With such versatility in a tiny device however, we often want to implement a graphic display to control various functions, making our projects easier to use.

Sure, there are solutions that exist. We have regularly used alphanumeric displays such as a 2 x 16 line displays, and they’re great for the functionality they provide. We’ve also used oLED displays which are controlled within the Arduino. They have functional geometry and text capabilities, but they can’t provide complex imagery or functionality without bogging down the Arduino with all the heavy lifting. That’s where these displays come in.

What’s great about the 4D Systems’ units is how powerful they are. They use a MicroSD card and integrated processor to provide rich graphics and functionality.

It’s important to note too, that you don’t actually need an Arduino to put one of these displays to use at all. The processor inside the display (DIablo16) actually has 16 GPIO pins (see our article in this issue, for more on this).

For many applications, this is all you would need to get a project going. And indeed, we took this route with this display initially too.

In response to the user selecting waveform type or frequency the 4D display (remember this is a full microcontroller, graphic processor and display in 1) sets a number of digital pins that are in term read by the Arduino. There are 4 frequency ranges that use 2 pins. 3 pins are required to select 1 of 5 waveforms.

THE ARDUINO

You’re likely to be familiar with the Arduino, so we won’t cover too much about Arduino and the IDE. The main reason we opted to include the Arduino in between the display and the resistor ladder is for better accuracy, and higher frequencies. The display processor is designed just as that, a display processor. The IO work very well, but for higher speed processing, the Arduino will process things faster and more reliably.

There are six connections between the Arduino and the display. There are 10 connections between the Arduino and the resistor ladder to provide the analogue conversion. Just about any Arduino will do for this project, however check for any specific pin changes (especially between UNO and Mega) that may exist.

We also could have used serial communication between the 4D display and the Arduino; however, we had the GPIO available, so a simple binary solution was retained. It’s still only six connections, and provides a very robust way to control the Arduino from the 4D display.

THE RESISTOR LADDER DAC

A resistor ladder is a simple circuit that often forms the basis of a DAC (i.e.,a digital-to-analog converter).

Computers that we typically encounter in the maker community are likely to be digital computers, but analog computers do exist and are still used for specific purposes. The internals of digital computers run on binary signals; they are either on or off. This is very different to analog signals such as the signal feeding a loudspeaker, the AC mains, or the output of a microphone. An analog signal can be completely random (‘noise’), but it could also follow a particular pattern or waveform. The most common types of analog signals are either very complex, multi-frequency signals (noise) or simple waveforms like sine waves (example AC mains) or square waves (anything related to digital electronics).

So, while a digital computer has many uses it generally doesn’t produce analog signals. This is where we can use a DAC built around a resistor ladder, to convert our digital signals into sine waves, sawtooth waves or the like.

The most common type of resistor ladder is known as a “R-2R” ladder. This is because it uses just two resistor values, where one value is twice the value of the other. For the sake of simplicity, many circuits use 10kOhm and 20kOhm. 20kOhm is not a standard 5% resistor value and in any case we want all of the 10k and 20k resistors to be closely matched in their value, so we recommend using 1% tolerance resistors.

HOW DOES THE RESISTOR LADDER WORK?

The resistors are arranged in such a fashion that the voltage from each digital source pin (that voltage being Vcc or 0V, depending on whether the pin is set high or low) contributes to the total output voltage.

The theory of how the resistor ladder works revolves around use of Thevenin’s theorem and superposition. These are beyond the scope of this article and gave me nightmares in my younger days studying electronics, but the theory can be boiled down to a simple equation:

Vout = input 1/2 + input 2/4 + input 3/8 ... input n/n squared

Note: In the above, input 1 refers to the first digital bit applied to the ladder, which would be bit 0 of the digital signal.

Let’s examine a 3-bit circuit. Here the output, (Vout) = bit 0/2 + bit 1/4 + bit 2/8. Bit 0 etc refers to the digital outputs from our Arduino (or RPi). The value of bit 0, bit 1 and bit 2 will always be either 0 or 1.

For a digital value of 000, the equation is: Vout = 0/2 + 0/4 + 0/8, which is 0.

For a digital value of 001, the equation is: Vout = 1/2 + 0/4 + 0/8, or 0.5

If we look at the digital value 111 we see: Vout = 1/2 + 1/4 + 1/8, or 0.875, which is clearly not 1.

The more bits we add, the closer we get to the maximum output value of 1 (i.e., Vcc). So for a 4-bit resistor ladder we could have a maximum of:

Vout = 1/2 + 1/4 + 1/8 + 1/16 or 0.9375.

Here is a breakdown of the maximum possible output for up to an 8-bit ladder:

0 = 0.5bit 0 0.5
1 = 0.25 bit 1 0.75
2 = 0.125 bit 2 0.875
3 = 0.0625 bit 3 0.9375
4 = 0.03125 bit 4 0.96875
5 = 0.015625 bit 5 0.984375
6 = 0.0078125 bit 6 0.99211875
7 = 0.00390625 bit 7 0.996025

THE 3D PRINT

As you’ll notice from the images included, we’ve designed a very functional 3D printed case to house the touchscreen and all electronics. The end result is a rather striking piece of bench equipment. It also makes life easier when trying to mount the 4D systems display. While bezels are available from 4D to help, the display we’re using is flush-mounted into a nice recess created in the 3D print. This helps protect it from damage, and it really looks great.

The print is done in two parts. There’s the main open-top portion of the case, and what is effectively the lid. We have designed it like this for two reasons:

  1. It’s going to be more successful as a 3D print. Regardless of how good your 3D printer is, or how slow you print, 3D prints are always better when you can keep things vertical. Overhangs and vertical irregularities can quickly dial down the likelihood of a successful print. With supports or dissolvable filaments, some of these challenges can be overcome, but they take more time to print, waste material, or use hardware that’s even less common.
  2. The separation of the lid makes it easier to get everything inside the case and mounted up nicely, without squeezing them through restrictions in the case. A few screws then hold everything firmly together. This means the “lid” prints flat on the printer bed for a nice, clean print, but mounts at a 30-degree angle for optimum usability and aesthetics.

The 4D Systems gen4 screen is mounted flush into the recess that’s been modelled into the case. It ensures a great looking finish to the project. The display has self-adhesive backing on the bezel. It fits snug without using the adhesive, but the adhesive provides additional security on the mount - especially if yours doesn’t mount quite as snug as ours did.

case seperated

The model has been made fairly precisely to suit this display. We actually made a model of the screen, which we then used as a negative to provide a recess into our case lid. This process is usually faster and more accurate than trying to design a recess directly. It also means you have the model to use as a negative on another project down the road too!

It's worth noting that 4D Systems do provide CAD files, but we couldn't import them into Tinkercad, so made our own!

THE BUILD

Construction is fairly straight forward as there are only a handful of discrete components required.

Parts Required:JaycarAltronics
1 x 4D Systems gen4 Touchscreen Display - -
1 x 4D Systems gen4 Programming Adaptor - -
1 x Arduino Nano XC4410 Z6280
9 x 20kΩ Resistors* RR0603 R7589
8 x 10kΩ Resistors* RR0596 R7582
1 x 100nF Capacitor RR0596 R2736B
1 x 2-way Screw Terminal HM3167 P2072A
1 x Red Binding Post PT0453 P9252
1 x Black Binding Post PT0454 P9254

* Total shown, may be sold in packs.

You’ll also require standard prototyping hardware such as breadboard and jumper wires.

schematic for build

BUILDING THE CIRCUIT

The resistor ladder is fairly straight forward, and is virtually the same resistor/input pattern repeated eight times, with your output on one end. We have provisioned a screw terminal, which makes things simple to connect to, but you can omit this and use something else if you prefer.

Connect jumpers from the gen4 display to the Arduino, and from the Arduino to the resistor ladder. Note that Arduino pin 8 is dedicated to producing the square wave signal and is connected directly to the resistor ladder. Power is provided via USB to the gen4 display, and we take 5V from the display’s programming adaptor to power the Arduino.

Note: Power requirements for the screen are relatively low so the Arduino can power it. Simply power the Arduino via USB as usual then connect the Arduino’s 5V and GND pins to the 4D PA board. It has a flexible ribbon cable that connects to the display module and carries the 5V to it. Don’t forget to also connect the ground from the Arduino to the resistor ladder board.

THE CODE

Due to the implementation of the 4D Systems’ display, there are two distinct coding steps required. The display is programmed using 4D Systems’ Workshop 4 (WS4) software. The Arduino is, of course, programmed via the Arduino IDE using the usual process. 4D do have some boards which use Arduino compatible chips, and WS4 can facilitate programming of both, but that’s not the case with our own integrations such as this.

A few notes regarding WS4:

  • It’s Windows-only software, however it’s reported to work on MacOS with Parallels or virtualisation software. Here at DIYODE, we actually use MacOS for most things, but have setup a dedicated Windows machine for various purposes. This is what we used for our installation of WS4. If you only have a MacOS or Linux, you’ll need to setup Windows virtualisation to get it to run.
  • You’ll need the Pro version, which attracts a cost to use persistently (worth the money in our opinion). However you can activate PRO features for seven non-consecutive days for free. So there’s no need to purchase the PRO version for this project.

PROGRAMME THE DISPLAY

Using the 4D systems Workshop software is relatively straightforward. From the splash screen click the “Open” button on the left hand side. Select the Function Generator.4DViSi file that you have downloaded from our website. Note that there is an associated Function Generator.ImgData folder which holds the button graphics. When the file is loaded, Workshop will warn you that writing to the SD-card will affect it’s useful lifetime. When you “OK” that, the IDE will open making the code and graphics visible. You can peruse the code or graphics here, and you can modify the code as you wish. You can always revert to the original version if it breaks. You may also wish to modify the button graphics.

Now we want to get the files onto the display module. On the toolbar at the top of the screen on the right hand side you’ll see a button labelled “Comp‘n load”. As the project data will run from the displays SD-card, we need to copy the code and support files to it. A dialog box <<< screen shot -> copy project to sd-card.png >>> will pop up asking you to select the drive letter housing the SD-card. Choose “OK”. If you get an error along the lines of “Open error on COM 6" or "The system cannot find the file specified” this indicates that either your display module is not connected to the PC or you haven’t selected the correct COM port. In this case go to the “Comms” menu and you’ll be able to set the correct port. Uploading the data takes no time and now your display will be running.

Note: If you modify the code or graphics, you’ll need to go through the above process again.

4D have a basic function generator but we found a few areas to improve. Their code has been modified to display approximate output frequencies and to limit the number of steps of the displayed waveform and hence the control signals to the Arduino. There’s very little point having 20 steps in output frequency if it’s limited to 100Hz. We’ve implemented four frequency steps and with the Arduino we’ve achieved significantly faster output frequencies than when using the 4D display electronics alone.

PROGRAMME THE ARDUINO

Load function_generator_v1.ino into your Arduino IDE and compile to your favourite Arduino board. We recommend a Nano or UNO for pinout consistency with our diagrams.

The thing about our software waveform generation is that we have great control over everything. What we do run into problems with however is the resolution of the digital outputs. They have a limited range, and if we push the frequency limits too hard, we lose the nice clean shapes of the waveforms.

If you have an oscilloscope, you may like to try this yourself to see what the results are like. As a result, we've kept the available ranges within the "clean" waveform range.

We won't take you through everything within the code, but here's a few useful snippets to be aware of:

The first is our switch, which rolls through the options to run the appropriate wave function. As you can see, each waveform is created using its own function, making it very easy to follow and expand. Of course, if you want to add additional options you'll need to adjust the UI in the screen using Workshop4 also.

  switch(func)
    {
    case 1:
      sineWave();
      break;
    case 2:
      squareWave();
      break;
    case 3:
      sawtoothWave();
      break;
/// ETC

Each of the wave generation functions basically have a loop, and a frequency selection (control also comes from the touch screen).

void squareWave() {
  byte i;
  int d[] = { 991, 493, 240, 120 }, j;
  for (i = 0; i < 10; i++) { 
    j = d[freq];
    pinMode(8, OUTPUT);
    digitalWrite(8, HIGH);
    delayMicroseconds(j);         
    digitalWrite(8, LOW);
    delayMicroseconds(j);

Each wave form generator follows a similar pattern, giving shape to the wave. Some are, of course, more complex than others. The one with the biggest difference is the random noise generation function.

    void noiseWave()
{
  byte i, j;
  int d[] = { 1000, 500, 200, 100 };
  for (i = 0; i < 30; i++)  // 30 loops
    {
    for (j = 0; j < 255; j++)          
  // cycle thru all 256 values   
      {
      PORTD = aNoise[j];
      delayMicroseconds(25);
      PORTD = 127; // center level b samples
      delayMicroseconds(25);
      }
    }
} // End of noiseWave()  

TESTING

The simplest way to test that everything is working is to use an oscilloscope of some type. Connect your favourite test probes and work through the functions of each waveform and frequency. You may find it interesting to also connect the output (via a pot to reduce the level), to a small stereo and listen to the differences between the waveforms.

The frequency increase/decrease buttons on the 4D display may appear to work backward. The < arrow key which you could expect would lower the output frequency, in fact compresses the displayed waveform, meaning that more cycles are displayed, indicating the frequency is going up. Similarly, the reverse applies for the > button. This is where the text line at the top of the display is useful, as it actually shows the approximate output frequency. Keep this in mind during testing and use.

If you have real trouble and are lost, then you can remove the display and test the Arduino side of things. It’s easy to “preset” a waveform type and frequency in the code. If the Arduino is working correctly then it would indicate that you may have an issue with the 4D display.

WHERE TO FROM HERE?

We have only skimmed the surface of what’s possible with this type of system.

WAVEFORMS: You could expand the code to create more complex waveforms.

DUTY CYCLE: We don’t have any provisions for duty cycle adjustment on the square wave, but then again if we change the duty cycle it won’t be a square wave, it will be a rectangular wave. The ability to change the duty cycle would effectively give you a high level PWM output control, across a variety of frequencies. One way to implement this is via a pot, wired across 5V and ground. Feed the voltage from the pot’s wiper to an analog pin and read it. Use this to vary the duty cycle. In fact, this is a good way to get a user-defined setting into the project. The pot may well give slight variations in its resistance over time but this can be compensated by dividing the range into a number of regions. For example, 0% to 20% for the lowest, 80% to 100% for the highest, and three more in between.

BROADER FREQUENCY RANGE: We use 256 steps for sine, triangle and sawtooth waveforms. Between each step we delay a little (microseconds). Using longer delays gives lower frequencies but also results in an obvious step pattern. To produce higher frequencies we reduce the delays. Oddly, trying a 1uS delay seems to make no difference compared to 2uS. A little side note here: in an attempt to produce sub-microsecond delays and maybe higher output frequencies, we tried floating point multiplication, and other complex math operations, but these made no measurable difference. The FPU in the Arduino must be very powerful indeed. 256 samples per cycle x 2uS = 512us per cycle. That translates to about 2kHz maximum. The only way to get higher frequencies is to reduce the number of samples per cycle, which would produce a more stepped (distorted) waveform, use a faster CPU, which we can’t as we’re using an Arduino; or to let each waveform “free wheel” (i.e., just cycle through the range of sample with no regard to anything else).

To examine this a little closer, consider that we want to get the whole eight digital bits to the resistor ladder in one piece (byte). The Arduino gives us that capability through its internal ports. The Arduino has three ports. Port B uses digital pin 8 to 13, for 6 bits, which is not suitable for our purpose. Port C uses the 6 analog pins, which again is not suitable. Port D uses digital pins 0 to 7, which on the surface is perfect. There are instructions, which can put the 6 or 8-bit value, onto the relevant analog or digital pins in 1 operation. These instructions are used similarly to PORTD = sine[j].

As already mentioned, PORTD seems to be perfect for our use. The problem boils down to fact that the only pins that can be used for interrupts on an Arduino are pin 2 or pin 3, both of which are associated with PORTD, which we use to feed samples to the resistor ladder. Without interrupts we have to poll (i.e., occasionally check) for frequency or waveform changes set by the 4D display. As every microsecond counts this polling action affects the waveform generation process. To minimise this impact, we don’t poll for changes on every cycle but control the number of cycles produced before polling. This allows good response to changes without undue distortion of the waveform.

If you wanted to produce higher frequencies at the cost of resolution (sample rate) then you would also need to modify the 4D systems display software too. It has been modified from the original to show a range of four frequencies and set its digital pins accordingly.

LOW FREQUENCIES: You may have a use for very low frequencies (e.g., 0.1Hz). While these would be difficult to facilitate for most waveforms due to resolution limitations, for a square wave you could slow it down almost infinitely by increasing delays in the code to slow down high/low transitions. Note: extremely low frequencies are easy, but the higher frequencies are not practical when using many samples to give a smooth waveform. Reducing the number of samples would allow a higher frequency at the cost of distortion of the generated waveform. A sine wave could become a stair case, but then again that might be a useful feature for some.

DIFFERENT NOISE PATTERNS: We have provided a random noise generator, but with the resources available with an Arduino it is next to impossible to generate white or pink noise. If these terms are new to you there is a wealth of information on the Internet in relation to types of noise and there purpose.

SERIAL COMMS: You could do away with the binary communication between the display and Arduino altogether. There are some considerations around this, as the CPU overhead for serial communication can reduce frequency, or otherwise distort the waveforms being output. But these could be compromised in favour of more flexibility.

Of course, the 4D gen4 display will do just about anything you want, so from a display perspective the sky really is the limit!