I am working on a project that needs to modulate a digital potentiometer (IIC in my case) in a waveform pattern. I have been wearing out google trying to find examples as waveform generation seems to be just above my comprehension at the moment... I am not having much luck increasing my understanding. All the pages I am seeing are about driving a DAC, not send the output number over IIC. I know this should be a fairly straightforward task but it seems to be eluding my understanding, would someone be willing to give me some examples with good explanation?
What I THINK I need to do is have a time variable that loops from 0 to the 100(sample rate? 128? or?) that iterates at 1/frequency/sample rate. This time variable would basically be the x value in a normal signal function y=f(x) and y would be the number 0-255 for the digi-pot, correct?
I only need to have a max frequency of about 50hz but if the processor can handle it, having the capability to drive it faster wouldn't hurt.
Max amplitude of the wave will always be 255, but the minimum needs to be adjustable (for example 100% to 20%, or 100% to 60%, only the lower range will change)
I need to generate Sine, Square, Triangle, and Saw waveforms.
I'm not sure if it would be better to generate the value on the fly or have a lookup table. I am using an ESP32 so it has "lots" of power and I'm thinking at only 50hz it should be able to generate it real time, but is that a bad idea? If lookup table is best, I still need to generate the values to fill, so what's the best way to go about that?
The adafruit digital pot I'm using to test has 128 intervals, but I will likely use one with 256 on the finished product. I'm not sure what would be a good value for the sample rate, 128? Or would something evenly divisible by 10 be better?
vaj4088 I'm not sure what you mean about how it's wired? Currently everything is just stuck into a breadboard. The pot will vary a 0-10v signal and be controlled via IIC.
I think I do, but maybe my terminology is getting mixed up here or I am misunderstanding?
My understanding of sample rate is:
How many times per full cycle the value is changed, or possibly how many times per second depending on how you look at it. So since a sine wave is a theoretical curve, the sample rate would be how many facets would be on that curve. a sample rate of 3 would be like a triangle wave because it goes from 0 to 1 to 0 to -1 to 0, etc. Whereas a higher sample rate the output would more closely resemble a correct curve. In this application it is how many times the micro is changing the value per second whereas the frequency is just how many times the wave does a complete cycle. (sample rate would be fixed in code, frequency could be varied by user)
Am I way off? I apologize I have been doing a lot of reading on this and it is a little over my head so I may have some terms mixed up.
Basically I don't know what a good or practical sample rate would be. It seems like the higher the better, but that would take more processing power and I'm not sure what would be reasonable on a esp32.
OK, after a LOT of trial and error I think I'm getting close. I made a function to write to a 128x64 OLED because I figure it is easier to read the output than staring at a bunch of numbers, but I THINK the basic concept will be the same. Here is what I have:
void drawWaveform(){
display.clearDisplay();
int sampleRate = 128; // display is 128px
int frequency = 1; // cycles to complete
int y = 0;
int a = 32; // max amplitude / 2 display is 64px
float c = (2 * PI) / sampleRate * frequency; // frequency
int u = a; // shift up
float r = .5f * PI; // shift right
for (int x = 0; x < sampleRate; x++){
y = a * sin((c * x) - r) + u;
display.drawPixel(x, map(y, 0, 64, 63, 0), SSD1306_WHITE);
}
display.display();
}
Basically I have my sample rate set to 128 because I don't want it skipping any Y pixels. in normal application this would probably be 100? 200? Still not sure what a good sample rate would be...
Frequency is how many cycles to complete on a full set of samples. If I set it to 8, there are 8 (choppy) waves displayed on screen. A setting of 1 has one hump starting at 0 and peaking at 63.
c is my conversion factor, since I want complete cycles.
How does this look? Am I way off? Should it convert well to driving a Digital Pot based off of time? I'm thinking take millis() at the start of a cycle and use the time difference as X every iteration. A timer based on the sample rate will call an iteration and once it has taken a full sample it will reset the start time to the current millis(). Would that work?
762SPR:
Basically I don't know what a good or practical sample rate would be.
The Nyquist criterion tells you that. You need to sample at twice the highest frequency of interest. So if you need a 1.0 kHz bandwidth, you have to use 2,000 samples per second.
Obviously we can't tell you what bandwidth you need, since you haven't mentioned what you're using it for.
Ah ok that helps! I think I mentioned before that I have a max anticipated frequency of about 50hz so 2x that means a sample rate of 100. I might just do 200 just in case I want to bump up the max frequency from 50hz. The intended use will be replacing a human operated potentiometer for a piece of machinery so it doesn't necessarily have to be 100% accurate as long as the pulses are consistent and fairly closely follow the desired waveform. I don't mean to be evasive I just don't know exactly how many details about the end use I can share.
Thanks for the help so far. I'm taking a quick code break for my head to decompress and then I will probably try to figure out how to convert this code to handle other waveforms.
Actually, I think that the Nyquist criterion says that (assuming that your frequency band extends down to zero/DC ) you must sample at GREATER THAN two times the highest frequency. In practice, a factor of 2.5 to 5 is used. You may want to use 10 or more for good "eyeball" results.
Oh, and powers of two (such as 16, 32, 64, 128, ...) tend to make for speedier calculations, but it depends upon what you are attempting to accomplish and how you are attempting to accomplish it.
Vaj4088 I'm sorry I still don't think I know how to answer that. I have the ESP module plugged into a breadboard and will also be plugging in an adafruit breakout for the DS3502 I2C digital potentiometer and connecting them with jumper wires. This will modulate a 0-10v signal.
This is just for prototyping/proof of concept. Eventually we will have PCB's made up for the finished product and will likely use a 256 step digital pot.
Thanks for the tip with the Nyquist criterion, I don't think I'm anywhere near running into speed problems so I might just set it to 512 just to be on the safe side. I can always bring it down if it starts bogging the system down.
I found some people talking on stack exchange about other waves and found this:
Triangular Wave
y = abs((x++ % 6) - 3);
This gives a triangular wave of period 6, oscillating between 3 and 0.
Square Wave
y = (x++ % 6) < 3 ? 3 : 0;
This gives a regular square wave of period 6, oscillating between 3 and 0.
Just to follow up for anyone that comes upon this thread in the future, here is what I have for: (It's all super simple btw...)
int sampleRate = 128; // display is 128px
float dutyCycle = .75f; // duty cycle is the percentage high 1= 100%
int a = 64; // max amplitude. display is 64px
Square:
//for loop:
y = (x < sampleRate * dutyCycle ? a : 0);
Triangle:
y = abs(x - sampleRate/2)
Sawtooth:
y = x;
Now, anyone with more understanding that what I had going into this will probably quickly realize that these, especially the saw and triangle are just plotting 45 degree angle lines (y = x) and you are correct! I had each function much more complicated and was using the mouldo of the samples, and the amplitude, etc when I realized that was all unnecessary! The simplest (although probably not the fastest) way to get things done was to do a simple y=x function and then use the map function to "squish" it down to the correct amplitude. This makes it not only easy for a dummy like me to read and write, but it also allows me in the same map function to control the base value as well (eg. instead of min 0 max 255, I could do min 32, max 255, or whatever). Now, I DO realize that this isn't the most efficient way to do it, there will likely be more overhead using the map function vs. doing it directly but these ARE making the desired waveforms and drawing them to the screen. I'm sure you guys can do it in a more efficient way, I just wanted to share what I did to get it working just in case someone else has the same problem. I hate it when people post " I figured it out!" and then don't post the solution!
The next step should be fairly straightforward, instead of drawing each pixel to the screen in a for loop, I will use a timer and a int for the samples that will iterate up to the sample rate and reset. Timer should be frequency / sample rate. Each iteration, the result will be mapped and sent to the potentiometer for hopefully flawless operation!