Twitter   Patreon WIGGLING 'LITE' IN GUEST MODE

Author Need help calculating a sine wave in c++ for AVR
Grumble
 So I'm working on an arbitrary waveform generator, and for that I need to calculate a 16 bit sine wave (4096 points) In fact, I need to calculate a number of different wave forms, so far I have a saw (ramp up and down), a triangle and noise. Now I'm terrible with calculus, I'm more into hardware bitbanging... Some help would be very appreciated!
onre
 You can calculate it in realtime if you want, but I used a 512-point lookup table interpolating "in between" samples to get a nicer waveform. As a disclaimer, I suck at DSP, this is just what I did.
commodorejohn
I'm not super familiar with AVR programming, does the C++ compiler implement the C standard math library, or is it using a cut-down custom solution? Because sine is part of stdlib, so you could just use that.

Otherwise, if it's not included or true sine is prohibitive from a performance standpoint, here's a trick I found a while back. If you...
• Take the linear ramp value (upsaw) and multiply it by its own absolute value (you'll need a 32-bit intermediate variable if you're looking for a 16-bit result.)
• Shift the result right by 15 (to bring it back down to 16-bit range.)
• Subtract the original ramp value.
• Subtract 1 if the result is positive or add 1 if the result is negative (this is to prevent an overflow issue in the next step.)
• Shift the result left by 2.

...you get something that looks and sounds much like a sine wave, with only one multiply. It's not as pure as the genuine article (some of the signal/math geeks on sdiy informed me that it's a derivation of a square wave, like a triangle wave is but moreso,) but it's a pretty quick-'n-easy microcontroller-friendly approach. Here's the code for my implementation:

 Code: int16_t ramp, temp, sine;         // "Sine" (fake sine derived through mangling the ramp)     temp = ramp + 0x4000;       // puts "sine" in phase with ramp         // Mangles the ramp to produce a quasi-sine waveform. The proper formula is     // (((x * abs(x)) / +maxint) - x) * 4 but this has been tweaked a bit to     // avoid some issues with overflow.     sine = ((int16_t)((temp * abs(temp)) >> 15) - temp + (-(temp < 0) | (temp > 0))) << 2;

Of course, you could just use a lookup table, but where's the fun in that?
Grumble
 the thing is: I dont want a lookup table, because I have 8 blocks of 4 kbyte I want to fill with waves. So I just need a sine wave calculation using sin()
Grumble
 Got it: for (SIN=0; SIN<4096; SIN++)//4096 points { uint16_t ramdata=0x8000+(0x8000 * sin(2*3.14*(double SIN/4096.0)))); Fill_RAM(SIN, (ramdata & 0xFF));//low byte to DAC SIN++; Fill_RAM(SIN, ((ramdata>>8) & 0xFF));//high byte to DAC } Now I have a sine wave, going amplitude full max to full min.
onre
 ... double post, ignore ...
onre
 AVR libc sin() uses floating-point which is emulated in software on an AVR and from experience I can tell that this is slow. Ideally you want a 16-bit sine approximation instead of sin() if speed is of any concern.
Grumble
 Speed is not of essence in this module, since I'm calculating the values and put them in RAM, and read that RAM out hardware wise, a sine of over 1.9kHz is possible (mind you! 4096 points!) so, calculating a sine of 512 points will give me over 15kHz sine wave (at this point I'm iusing a clock frequency of 8MHz to read the RAM value.
Grumble
 Part modern, part old school... a 512 point 16 bit sine wave.
emmaker
For me I'd use a single quadrant vector table if I was doing this. What I'd do is write my code and if I have a lot of Flash left over I'd generate a const table and use that. Also if you are just using floating point here you may end up using more Flash space for the floating point library than the table itself.

But that aside you only need to calculate a single quadrant and then derive the other 3 quadrants using phase and inversion.

Question: Is there a reason that you're storing things as bytes and not words, do the bytes need to be swapped for the DAC?

Seems like this would work:

 Code: #define PHASE_MAX  (4096) int16_t    sineData[ PHASE_MAX ]; for ( phase = 0; phase < PHASE_MAX; phase++ ) {     sineData[ phase ] = YOUR_SINE_CALC( phase ); }
commodorejohn
 Grumble wrote: Speed is not of essence in this module, since I'm calculating the values and put them in RAM, and read that RAM out hardware wise, a sine of over 1.9kHz is possible (mind you! 4096 points!) so, calculating a sine of 512 points will give me over 15kHz sine wave (at this point I'm iusing a clock frequency of 8MHz to read the RAM value.

Oh, yeah, if you're just filling a wavetable to be read back there's no reason not to take a slower but more accurate approach.
Grumble
 emmaker wrote: Question: Is there a reason that you're storing things as bytes and not words, do the bytes need to be swapped for the DAC?

The DAC is 16bits, divided into two bytes parallel, but the RAM is 8bits wide, so I use the even bytes for the low dac byte and the odd bytes for the high dac bytes, therefore the dac uses hben, lben and ldac (high byte enable, low byte enable and load dac)
Synthiq
 Grumble wrote: uint16_t ramdata=0x8000+(0x8000 * sin(2*3.14*(double SIN/4096.0))));

I think you have to reduce the amplitude by 1 to avoid overflow when sin=1:
uint16_t ramdata=0x8000+(0x7FFF * sin(2*3.141592654*(double SIN/4096.0))));
Grumble
 I think you are right, I will correct that. At the moment there is no overflow visible because the 3.14 I use for PI prevents that. The reason I use 3.14 for PI is that it was what I remembered from PI
onre
 Grumble wrote: Speed is not of essence in this module, since I'm calculating the values and put them in RAM, and read that RAM out hardware wise, a sine of over 1.9kHz is possible (mind you! 4096 points!) so, calculating a sine of 512 points will give me over 15kHz sine wave (at this point I'm iusing a clock frequency of 8MHz to read the RAM value.

Well, why not! Is that 1284? It has 16 kilobytes of RAM so that's a plenty, I'm used to 328 with two kilobytes so everything goes to flash.
Grumble
 No it is an Arduino with an atmega328 running at 16MHz. The Ram used is a cy62256, which is a 32kbyte RAM, but I use 8 sections of 4kbyte, so I can make 8 waveforms of 16bits wide with a length of 2048 samples.
Grumble
 The arduino is there to just fill the RAM, set the clock frequency and that’s it. You should try to read out the internal flash with a speed of 8MHz on an arduino After filling the RAM and setting the readout frequency the arduino can do stuff like writing to the oled, reading knobs, cv’s and so on...
onre
 Well this clears it up - I thought you would be using an AVR to drive a DAC directly.
Grumble
 No, that has been done so many times Using the arduino to control the dac it is impossible to get the speed, accuracy and continuity like done in this module. I like to do things differently
555x555
 Generally speaking, musl libc has a lot of very readable code, if you won’t have a direct access to math.h. Any floating point operation can be adapted to fixed point by imagining where the decimal point is—in this case you want it at the MSB. No reason to go with floating point unless you need to preserve numerical accuracy when your numbers are on an order much less than 1.
 Page 1 of 1