I was reading through the Arduino Forum one day and found a young man who wanted to know how you could use the Arduino to make a sine wave generator. Apparently, he was an engineering student and this was his assignment He was told by some it was not possible, others said it was, but only as fast as 50 Hz. Since our company makes an I2C DAC dev board for the Arduino I thought it would be educational (for me) to make a sine wave generator using the DAC board.
About this ProjectA signal generator usually has various signals that is can generate, such as Sine, Square and triangle. Others have a sweep function and an arbitrary waveform. These are useful tools in the workshop. They can be used to test out audio circuits, op amp circuits and testing circuit response. Most modern function generators can easily put out frequencies up to 1 Mhz.
So, while I did not expect an Arduino based sine wave generator to replace my desktop function generator, I thought it would be interesting to see how to go about designing one and how it would perform.
I decided the easiest way to start would be to create a lookup table of values to be used in my sinewave output. The more values you have in the table the better the output will approximate a true sinewave. It is also very convenient to use powers of 2 when creating a lookup table that you are going to cycle through repeatedly. So, 8 values were not going to work and 128 was probably going to tax the ability of the MCU. I decided that 32 values would be a good place to start.
Next, I needed to decide how much resolution to provide. The SF-5 is based on the MCP4725 DAC which is a 12-bit device. (Note: You can purchase the SF-5 on Tindie, or you can buy the DIP package of the device and put it directly onto your solderless breadboard). So, 12 bits it was. I opened an Excel spreadsheet and wrote down the numbers from 0 to 31. The next column I needed was the angle in radians. That is just the index times 2 times Pi divided by 32. The next column is the normalized amplitude of the signal. I simply took the sine of the angle in the previous column. This created a signal that was 2 units peak to peak and centered on zero. Its maximum was +1 and minimum was -1.
Having a 0 to 5V output range I would need a signal that was centered on 2.5V and had an amplitude of ± 2.5V. Next column just multiplied the signal by 2.5. Then we offset it by 2.5V. The signal showed a maximum of 5V and a minimum of 0V. Perfect!
Now we just needed to convert that into a 12-bit number to put in our table. Since we wanted the value to be 4095 when the voltage was 5V, we multiply by 4095 and divide by 5. To check, look at line 8. The voltage output is 5V and the bit count is 4095.
The table32 2-byte numbers is not a lot of data and could easily be stored in RAM, but this was a learning experience and I wanted to learn how to store the table in FLASH and read it as needed. The advantage is that one day I will have too much data to put in RAM and will need to use the FLASH, so this is a good time to learn. The command is PROGMEM and the statement that stored the data is:
const PROGMEM unsigned int mysine [] = {TABLE}
The codeI wrote a simple looping program that looked for user input on the serial line. In my case when the number 6 was seen, it would jump into the code that dumped the table to the DAC over the I2C bus. Having never used FLASH directly before I first thought I could just index my array and read the data. RTFM – Read the Fine Manual. When reading from FLASH you use a different command –
temp = pgm_read_word_near(mysine + i);
This reads a 16-bit word, or one entry, from the array. I start with i=0 and increase until it is 31 and then set it back to zero again (i = I & 0x1F;). It is nice that we can read from the FLASH in words, but we can only write to the DAC in bytes. We need to do some editing on our data.
The MSB is sent first. The top two bits are the speed, the next two bits are the power down select mode, the next 4 bits are the data. That gets sent off fist using the Wire.write command. Next, we send the lower 8 bits of data, also using the Wire.write command.
How about frequency?When you go into a loop and just continuously send out the 32 table entries, this is the fastest you can go and the highest frequency you will get. The frequency I get is 92Hz. I then wanted to adjust the frequency and I decided to add a delay () after each table entry. That will be 32ms (roughly) per cycle. So, subsequent frequencies are 23Hz, 13Hz, 9Hz. You can go up to a delay of 256. I think that gave me a period of 8 seconds.
Looking at the picture of the raw output you might thing, “What a terrible looking output.” It has a stairstep pattern. That is because those are the 32 voltages we are putting out of the DAC. We could add more entries to the table to make the stairstep pattern become smoother, but that will decrease the maximum frequency. The other thing we can do is to filter out the high frequencies that are making those sharp corners in the waveform when we change voltage levels. I used a simple RC low pass filter; R = 10K, C = 0.1uF for a cutoff frequency of about 160Hz. As you can see it looks much better and is a better approximation of a true sinewave.
I allow the delay to be modified by the user from their keyboard by increasing the delay by one unit when a ‘+’ character is received and decreasing the delay when a ‘-‘ character is received.
I also don’t want to lock the user into an endless loop, so I check to see if the user sends down an ‘e’ or an ‘E’ (because nothing is more annoying than having to worry about case sensitivity). When I get that, I treat it as an exit request and jump back to the main loop. I do so a bit inelegantly, without regard for where I leave the voltage output. But remember, we are just having fun here. If this were a product, I would want to leave the output in a known state, probably 0 volts.
I used a simple user interface that anyone could write on the Arduino. I am sure you can use Python or something on your PC to make a really cool interface. I also did not have a specific reason for doing this, so if you have a project with real requirements, you will likely change the code to suit.
DemoI made a demo video that includes all the options in the menu including the Sinewave output (6) and put it on Youtube. If you skip to the end you can see the demo of the sinewave:
Comments