Speeding up sine wave signal generation with YL-40 and Arduino Uno

I am not sure if I write in a right forum, so please move it if necessary.

I am a beginner in electronics and serial interfaces. So maybe the questions in this message are trivial but I don't have an idea how to solve them.
I am trying to generate a sinusoidal wave signal using Arduino Uno and YL-40 DAC. The problem is that maximal frequency I achieved is only about 12Hz (checked by an oscilloscope). I made a global array sinVals with precalculated sine values in order to speed up the program by releasing Arduino Uno CPU from calculating sine values which is an expensive operation. I have two questions:

  • When I put sinVals in flash (using PROGMEM keyword) the generated wave is not sine but some random-looking one. In contrast, when sinVals is in dynamic memory the sine wave looks as expected. Why putting the array in the flash memory does not work as expected and how to fix it?
  • How to achieve higher frequency of generated signal? What can be the maximal frequency using the YL-40 DAC?

Waking up YL-40 for sending only one byte at time is inefficient. Sending the whole sinVals array at once by Wire.write(sinVals,255) makes new problems: even though the function returns 255 (so the whole wave is sent) my oscilloscope shows that only a small initial part of sine wave is generated, though the frequency of the cut-off wave is then ~240Hz.

Here is the code:
[/list]

#include "Wire.h"

#define PCF8591 (0x90 >> 1) // I2C bus address

//When byte const PROGMEM sinVals[] is in the line below the generated wave is random, not the sinusoidal one as the precalculated values represent

byte const sinVals[] = {64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 66, 66, 67, 67, 68,
68, 69, 70, 70, 71, 72, 73, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
86, 87, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 101, 103, 104, 106, 107,
109, 110, 112, 113, 115, 116, 118, 119, 121, 123, 124, 126, 127, 128, 130,
131, 133, 134, 136, 137, 139, 141, 142, 144, 145, 147, 148, 150, 151, 152,
154, 155, 157, 158, 159, 161, 162, 163, 165, 166, 167, 168, 169, 171, 172,
173, 174, 175, 176, 177, 178, 179, 180, 181, 181, 182, 183, 184, 184, 185,
186, 186, 187, 187, 188, 188, 189, 189, 189, 190, 190, 190, 190, 190, 190,
190, 190, 190, 190, 190, 190, 190, 190, 190, 189, 189, 189, 188, 188, 187,
187, 186, 186, 185, 184, 184, 183, 182, 181, 181, 180, 179, 178, 177, 176,
175, 174, 173, 172, 171, 170, 168, 167, 166, 165, 163, 162, 161, 159, 158,
157, 155, 154, 153, 151, 150, 148, 147, 145, 144, 142, 141, 139, 138, 136,
134, 133, 131, 130, 128, 127, 126, 124, 123, 121, 120, 118, 117, 115, 114,
112, 111, 109, 108, 106, 105, 103, 102, 100, 99, 97, 96, 95, 93, 92, 91,
89, 88, 87, 86, 85, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 74, 73, 72, 71,
70, 70, 69, 68, 68, 67, 67, 66, 66, 65, 65, 65, 64, 64, 64, 64, 64, 64, 64, 64};

void setup()
{
  Wire.begin();
}

void loop()
{
 for (int i=0; i<256; i++)
 {
   Wire.beginTransmission(PCF8591); // wake up PCF8591
   Wire.write(0x40); // control byte - turn on DAC (binary 1000000)
   Wire.write(sinVals[i]); // value to send to DAC
   //the line above when replaced by int nTransfered=Wire.write(sinVals,255) and "for" is removed, the sine wave is not complete
  //even though returned value nTransfered is 255
   Wire.endTransmission(); // end tranmission
 }
}

Use pgm_read_byte() (or word) to retrieve constants from flash memory.

Wire.write(sinVals,255) makes new problems

The Wire buffer is 32 bytes long. I'm not sure off the top of my head what it does when you try to queue more, but the code is open source.

Edit: it looks like Wire.write() transmits 32 bytes and then quits with an error indication (perhaps returning the value -1, which would explain the 255 you saw).

That is a very limited DAC, and I2C is slow. For much higher speed you will need a faster DAC and use SPI.

Can you post your signal from the oscilloscope and you circuit?

Remember to generate a sine wave all you need is a little bit more than two samples per interval/wave (Nyquist–Shannon sampling theorem). But when you create the signal with your DAC you will see a rectangular wave signal. To get the final wave form you need to filter the signal with a low pass filter. The simplest is a RC filter.

The following page look like a good starting point to calculate the values for the filter.

RC Low-pass Filter Design Tool

If you want to know more about Nyquist–Shannon

Wikipedia Nyquist–Shannon sampling theorem

This should allow you to create higher frequencies.

jremington,

taking into account the size of buffer (32 bytes) the following code gives the full sine wave:

   for(int i=0;i<8;i++)
   {
     Wire.beginTransmission(PCF8591);
     Wire.write(0x40);
     Wire.write(sinVals+i*32,32);
     Wire.endTransmission();
   }

The frequency is ~37Hz. Well, it's still very slow but as I understood you there is no another way to speed up YL-40.
Anyway, that's just for learning purpose.

Thanks for pgm_read_byte() !

Klaus,

Thanks for the tips and links. I was wandering if YL-40 DAC can make a higher frequency sine wave signal without additional circuitry. The issue with cut-off wave was in size of buffer (32 bytes) whereas I was sending 255 bytes at once. Sending 32 bytes at once solves the issue. Unfortunately the frequency is only ~37Hz which seems to be the limit of YL-40. I thought it could be a few kHz at least.

It is possible to further speed up the signal generation by increasing SCL frequency using either the Wire.setClock() function or changing internal register TWBR (Faster I2C on Atmega328? - Networking, Protocols, and Devices - Arduino Forum). The maximal frequency of sine wave that I achieved is ~150Hz by setting TWBR=5 corresponding to SCL>~600kHz. This is significantly faster than default SCL of 100kHz.

popsi12:
I was wandering if YL-40 DAC can make a higher frequency sine wave signal without additional circuitry.

It depends on what you mean by sine wave. A DAC will never be able to create a sine wave without filtering if you look close enough.
Additionally, when you use the signal the higher frequency components might cause issues. A pure sine wave signal has a single frequency component. Any other signal has additional sine wave components with higher frequency added to your base sine wave.

What do you want to use the sine wave signal for?

popsi12:
YL-40. I thought it could be a few kHz at least.

Can you post a link to the datasheet on the manufacturer website?

The PCF8951 DAC conversion frequency is 11 kHz, which means you could output at most about 11 points per cycle of a 1 kHz sine wave.

But, you have to get the data into it. The I2C bus is slow, with the standard speed 100 kBPS, fast 400 kBPS, and high speed mode about 3.4 MBPS. I don't believe that the Arduino Uno supports high speed mode.

However, the data sheet specification for the PCF8591 is SCL=100 kHz maximum, so I'm surprised that it works at 600 kHz.

If you want to create audio, you will need a much faster DAC and SPI data transfer.

Or just make your own parallel port 8-bit DAC with an R/2R ladder. Be sure to use 0.1% precision resistors or better.

Klaus,

What do you want to use the sine wave signal for?

I am playing with the DAC and other sensors/ICs/elements just for fun and learning purpose. Have been in isolation due to covid last months, working from home, so I have a bit more free time to spend, hence I've bought Arduino UNO, a few sensors and ICs, the ultra cheap DIY oscilloscope DSO138. Just to learn a little bit something new. I am a theoretical physicist, doing atomistic simulations of new materials (probably you heard about graphene) for applications in FETs, batteries, photovoltaics, etc. But these are all simulations at the lowest atomistic level, e.g. of a single FET at most, so I want a bit to widen my horizons. At this moment I don't have any particular project in electronics in my mind to pursue, but maybe I will do some stuff in future. I chose the sine wave because it is the simplest one. Then made triangular and square waves, experiment how RC filters affect them, etc. YL-40 uses I2C so I can learn about this protocol too.

Can you post a link to the datasheet on the manufacturer website?

I can't find datasheet of YL-40.
The scheme of YL-40 can be found here: PCF8591 YL-40 AD DA Module Review | The BrainFyre Blog
It uses PCF8951 DAC: Aurélien Jarno
Can you suggest me a good book or site with practical common electric circuits? I am reading "The art of electronics", it is a good one.

jremington,

Thanks a lot for R/2R ladder circuit! I haven't seen it before.

The PCF8951 DAC conversion frequency is 11 kHz, which means you could output at most about 11 points per cycle of a 1 kHz sine wave.

Then 150Hz sine wave is fair for PCF8951. It is 73 points per cycle.

However, the data sheet specification for the PCF8591 is SCL=100 kHz maximum, so I'm surprised that it works at 600 kHz.

A guy achieved even higher frequency, 727kHz, with PCF8951 here.

popsi12:
I am playing with the DAC and other sensors/ICs/elements just for fun and learning purpose.

Good idea. Have fun.

popsi12:
Can you suggest me a good book or site with practical common electric circuits? I am reading "The art of electronics", it is a good one.

The literature for electronics is huge. I always look into datasheets and application notes from manufacturers first because they are specific to a device. Many basics you look up in Wikipedia.
But the best thing to do is practice. Getting yourself a board and some sensors was a good move. :slight_smile:

popsi12:
A guy achieved even higher frequency, 727kHz, with PCF8951

Electrical parameters in datasheets have margin to allow production and environmental variations and to limit test time/cost. Sometimes the parameters vary under different conditions e.g. temperature and voltage. So, as long as you play in the lab you can try to go outside of the datasheet’s recommendation. However, you might damage your device or any attached components. Therefore, this is not really recommended especially when you are a beginner. It is good practice to keep the datasheet nearby and stay within the limits of the device you are using.