MUFF WIGGLER Forum Index
 FAQ & Terms of UseFAQ & Terms Of Use   Wiggler RadioMW Radio   Muff Wiggler TwitterTwitter   Support the site @ PatreonPatreon 
 SearchSearch   RegisterSign up   Log inLog in 
WIGGLING 'LITE' IN GUEST MODE

Need help calculating a sine wave in c++ for AVR
MUFF WIGGLER Forum Index -> Music Tech DIY  
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? wink
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 hihi
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 Mr. Green
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 hihi
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 hyper
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.
MUFF WIGGLER Forum Index -> Music Tech DIY  
Page 1 of 1
Powered by phpBB © phpBB Group