The Netduino forums have been replaced by new forums at community.wildernesslabs.co.
This site has been preserved for archival purposes only
and the ability to make new accounts or posts has been turned off.
[font="arial, sans-serif;"]Below is a simple class implementing a zero-biased sine table with values -255 to 255. I use it frequently and thought I'd share in hope of it being useful to some of you as well. [/font]
Values of the first quadrant ( 0 to pi / 2) are pre-calculated and stored in a byte array. By horizontal and vertical mirroring, values for a full period (2 * pi) is achieved using only a single 256 byte array which should keep memory footprint to a minimum.
[font="arial, sans-serif;"]An integer indexer [/font][font="arial, sans-serif;font-size:14px;"]0 to 1023 (wraps beyond 1023) [/font]is used to get the sine values for angles 0 to 2 * pi.
Hopefully there are none but please let me know should you find any errors.
[font="arial, sans-serif;font-size:14px;"]Note: The [/font][font="arial, sans-serif;font-size:14px;"]microcontroller of [/font][font="arial, sans-serif;font-size:14px;"]Netduino 2 and Netduino Plus 2 features FPU but I don't know if .NET MF takes advantage of that, either way the regular Netduino and Netduino mini does not have an FPU.[/font]
// Simple 8 bit sine table public class SineTable { private static byte[] _sin; public static readonly SineTable Sin = new SineTable(0); public static readonly SineTable Cos = new SineTable(256); private int _offset; private SineTable(int offset) { _offset = offset; // 1st quadrant (0...pi/2) in 256 steps if (_sin == null) { _sin = new byte[256]; double a = 0; double da = System.Math.PI / (2.0 * _sin.Length); for (int i = 0; i < _sin.Length; i++, a += da) _sin[i] = (byte)(256 * System.Math.Sin(a)); } } // 1024 = 2 * pi (one period) public int this[int index] { get { index += _offset; index &= 1023; // 1st quadrant? if (index < 0x100) return _sin[index]; // 2nd quadrant? else if (index < 0x200) return _sin[0xff - (index & 0xff)]; // 3rd quadrant? else if (index < 0x300) return -_sin[index & 0xff]; // 4th quadrant else return -_sin[0xff - (index & 0xff)]; } } }
You can easily change the resolution by altering the length of the array and the modulo (1023).
[font="arial, sans-serif;font-size:10.5pt;"]UPDATE: The SineTable class now contains two static instances called "Sin" and "Cos" which are used like this:[/font]
// some arbitrary angleint alpha = 134; // get sine value for angleint sinval = SineTable.Sin[alpha];// get cosine value for angleint cosval = SineTable.Cos[alpha];
I use tables like this all the time in signal processing applications, quite often they are built into the ROM of the DSP processors we use, other times we have to include a 'C' array created by a script.
Would be nice to rename the class into SineAndCosineTable, and add a Cosine function that shares the same array.
sine and cosine are related by a phase shift, so you don't need a separate array (much like your 4-quadrant optimization)
Hmm, doesn't 'byte' only hold 0-255 (or is it -128 to 127?), but never +/- 255? Oh never mind you get an extra bit of precision since you're in the first quadrant, which is always positive (and you get the negative values in your [] method).
I wonder if privatestaticbyte[] _sin; could be just be a constant array (i.e. not need initialization in the constructor, and stored in ROM). Then again, I've found that netmf only seems to be clever like that (storing in ROM) for strings, but admittedly I haven't tried an array of integers... If it's not, then perhaps on the upside you can claim that 'sine table' has a double meaning....
Looks like someones gonna be doing some Fourier transforms?
sine and cosine are related by a phase shift, so you don't need a separate array (much like your 4-quadrant optimization)
I wonder if private static byte[] _sin; could be simply be a constant array (i.e. not need initialization in the constructor, and stored in ROM). Then again, I've found that netmf only seems to be clever like that for strings, but admittedly I haven't tried an array of integers... If not, then perhaps on the upside 'sine table' has a double meaning....
Yes, they are the same wave form only cosine is 90 degrees ahead, e.g. cos(x) = sin(x + pi / 2) and consequently cos[x] = sin[x + 256] in code.
As you said, the array does not have to be generated run-time but then you'd have to re-generate it outside .NETMF if/when you want to change the resolution. Also, I think arrays will end up in RAM anyway and that someone (was it you?) recently investigated the latter and came to that very conclusion. Also, generating the 256 byte array does not take long.
.. Also, I think arrays will end up in RAM anyway and that someone (was it you?) recently investigated the latter and came to that very conclusion....
'twas indeed, alas. I haven't tried it for ints, though, but I'm skeptical that they will avoid ram; only strings it seems But your table is quite small, so no biggie!
Added cosine and updated code of initial post (see far above).
AFAIK, the microcontroller of Netduino 2 and Netduino Plus 2 features FPU but I don't know if .NETMF takes advantage of that. Either way, the regular Netduino and Netduino mini does not have an FPU and could probably benefit performance-wise.
However, I honestly don't really know how much performance gain there is to this or even if there is any at all - could even be performance is worse than that of Math.Sine() but I haven't tested.
Perhaps somebody knows off heart or is willing to test it?
take it you start at -90 degrees at xpixel=0 and so zero degrees is at xpixel=64, correct?
Nope. xpixel=0 is 0[font="Calibri, 'sans-serif';font-size:11pt;"]°[/font]. The 180[font="Calibri, 'sans-serif';font-size:11pt;"]°[/font] phase change you see is because the AGENT screen's origin is upper left, and positive Y is DOWN the screen.
Below, I've inverted the sine data to "correct" the phase. Thanks. I'll update the picture too.
?
?
static SineTable _sine; ... // initialize sine table _sine = new SineTable(); ... // Display single cycle of sine wave on 128 x 128 pixel screen for (int i = 0; i < 128; i++) { _display.SetPixel(i, 64 - _sine[i * 8] / 4, Color.White); // Minus sign is 'cause screen Y is inverted }
Nice, I am currently tinkering with something similar myself, using an ad9831 to create a basic arb sig gen although rather than storing the tables in codespace I am considering using an eeprom, that said I may end up using a CSV file on an SD card as it could be also used for storing arb waveforms...
Nak.
Cool, I've been thinking of that too at some point. It would be really nice to have a PC app where you could draw arb forms in free hand and then upload to the chip/board over USB. Also controlling freq and amplitude from the PC app.
Will the AD9831 retrieve output samples from EEPROM as it goes (like a RAMDAC) or will you be uploading a full "period" to the chip before starting it?
The mirroring duplicates samples that shouldn't be duplicated. It is most egregious at the zero crossing -- a double sample of zero right where the sine should be most linear.
The fix is elegant. I just extended the dataset by 1 sample to be endpoint INCLUSIVE, then handled the mirroring asymmetrically to avoid duplicates. It actually cleans up the mirroring code, I think:
// Below, endpoint asymmetry assures non-overlapping quadrants if (index < quadLen) return _sin[index]; // 1st quadrant index -= quadLen; if (index < quadLen) return _sin[quadLen - index]; // 2nd quadrant index -= quadLen; if (index < quadLen) return -_sin[index]; // 3rd quadrant index -= quadLen; return -_sin[quadLen - index]; // 4th quadrant
I also made these changes:
1) Added two public constructors, one for default sine wave and one for arbitrary phase (in radians).
2) Replaced your bitwise AND with more general modulo (%, aka "remainder"), required for next item.
3) Refactored the byte array length to be arbitrary. Just change variable quadLen to any integer and the sine data will be stored in quadLen+1 bytes. I'm using quadLen=32 for my tiny AGENT screen.
using System;using Microsoft.SPOT;namespace AgentWatchSineWaves{ // Simple 8-bit sine table // Adapted From http://forums.netduino.com/index.php?/topic/9115-simple-sine-table/ // public class SineTable { const double Tau = 2.0 * System.Math.PI; // http://tauday.com/tau-manifesto 6.283185307179586476925286766559 ... const int quadLen = 32; // A quandrant is one quarter length of a full period const double amplitude = Byte.MaxValue; // Maximum byte value is 255 (byte is unsigned) public static readonly SineTable Sin = new SineTable(); public static readonly SineTable Cos = new SineTable(Tau / 4.0); private static byte[] _sin; private int _offset; public SineTable() { if (_sin == null) // one-time initialization { _sin = new byte[quadLen + 1]; // From 0 to quadLen INCLUSIVE double a = 0; // Angle in radians // Tau (one period) = 4 quadrants double da = Tau / (4.0 * quadLen); // 1st quadrant (0...Tau/4) for (int i = 0; i < _sin.Length; i++, a += da) _sin[i] = (byte)(amplitude * System.Math.Sin(a)); } } public SineTable(double phaseInRadians) : this() { phaseInRadians %= Tau; // Normalize to ±1 cycle if (phaseInRadians < 0.0) phaseInRadians += Tau; // Equivalent positive angle _offset = (int)(phaseInRadians/Tau * 4 * quadLen); } public int this[int index] { get { index += _offset; index %= 4 * quadLen; // Modulo limits index to 4 quadrants // Below, endpoint asymmetry assures non-overlapping quadrants if (index < quadLen) return _sin[index]; // 1st quadrant index -= quadLen; if (index < quadLen) return _sin[quadLen - index]; // 2nd quadrant index -= quadLen; if (index < quadLen) return -_sin[index]; // 3rd quadrant index -= quadLen; return -_sin[quadLen - index]; // 4th quadrant } } }}
Nice, it got better and I agree, the mirroring is more elegant now.
The biwise AND was there for performance reasons, modulo requires a DIV but there might be no difference in execution time, it's just one of those autonomous things since my assembler days. When quadLen is an integer power of two, its the same thing and I was thinking of using quadBits but quadLen and modulo is more generic.
You left quadLen=32, was that intentional?
Cool, I've been thinking of that too at some point. It would be really nice to have a PC app where you could draw arb forms in free hand and then upload to the chip/board over USB. Also controlling freq and amplitude from the PC app.
Will the AD9831 retrieve output samples from EEPROM as it goes (like a RAMDAC) or will you be uploading a full "period" to the chip before starting it?
Essentially you write to the phase register via spi, im considering making it as a shield and go module at this point in time im still reseraching but it should be doable with one of those cheap $10 dds modules you can find on eBay, when im in front of my pc shall post some links
Nak.
Nice, it got better and I agree, the mirroring is more elegant now.
The biwise AND was there for performance reasons, modulo requires a DIV but there might be no difference in execution time, it's just one of those autonomous things since my assembler days. When quadLen is an integer power of two, its the same thing and I was thinking of using quadBits but quadLen and modulo is more generic.
You left quadLen=32, was that intentional?
I'm using quadLen=32 for the tiny AGENT screen. Converting to a different quadLen led me down the refactoring path. And it was also how I discovered the sample overlap. (With fewer samples, any mistake becomes more visible.)
Definitely change it back to 256 unless you're using AGENT. 32 is a bit coarse for general use.
I do understand how blazingly fast a single AND is, but I worry that "autonomous things since... assembler days" add code complexity when the benefit is negligible. I need to learn how to quantify such concerns and questions. AGENT is size constrained AND severely power constrained -- so every detail might matter in the end on that platform.
In other words, how can I measure the difference between these two lines:
Measure time spent performing, say 10k of each variant and then compare the end results.
There are always trade-offs between, speed, complexity, size, ease of use, clarity, etc. In a triangle, you caanot be close to any single corner without getting farther away from the other two :-)
@nakchak: I've got one of these laying around but I don't think it can do arbitrary wave forms, only sine and square:
http://www.ebay.com/...=item5d3f85ca20