Generate sine signal in C without using the standard function

后端 未结 11 745
情歌与酒
情歌与酒 2021-02-02 09:41

I want to generate a sine signal in C without using the standard function sin() in order to trigger sine shaped changes in the brightness of a LED. My basic idea was to use a lo

相关标签:
11条回答
  • 2021-02-02 09:52

    It would help if you explain why you don't want the built-in function, but as others have said, the Taylor series is one way to estimate the value. However, the other answers seem to actually be using the Maclaurin series, not Taylor. You should have a lookup table of both sine and cosine. Then find x0, the closest x value in your lookup table to the x you want, and find d = x-x0. Then

    sin(x) =sin(x0)+cos(x0)*d-sin(x0)*d2/2-cos(x0)*d3/6 + ...

    If your lookup table is such that d<.01, then you'll be getting more than two digits of precision per term.

    Another method is to use the fact that if x = x0+d, then

    sin(x) = sin(x0)*cos(d)+cos(x0)*sin(d)

    You can use a lookup table to get sin(x0) and cos(x0), and then use Maclaurin series to get cos(d) and sin(d).

    0 讨论(0)
  • 2021-02-02 09:53

    I would go with Bhaskara I's approximation of a sine function. Using degrees, from 0 to 180, you can approximate the value like so

    float Sine0to180(float phase)
    {
        return (4.0f * phase) * (180.0f - phase) / (40500.0f - phase * (180.0f - phase));
    }
    

    if you want to account for any angle, you'd add

    float sine(float phase)
    {
        float FactorFor180to360 = -1 * (((int) phase / 180) % 2 );
        float AbsoluteSineValue = Sine0to180(phase - (float)(180 * (int)(phase/180)));
        return AbsoluteSineValue * FactorFor180to360;
    }
    

    If you want to do it in radians, you'd add

    float SineRads(float phase)
    {
        return Sine(phase * 180.0f / 3.1416);
    }
    

    Here is a graph showing the points calculated with this approximation and also points calculated with the sine function. You can barely see the approximation points peeking out from under the actual sine points.

    0 讨论(0)
  • 2021-02-02 09:55

    ...a better idea to implement a sine generator in C?

    Edit: Suggest first reading this article to gain an appreciation of what OP is asking.

    From the context provided in your question, I am assuming the word better might have to do with size and speed of compiled code, as might be required to run on a small micro-processor.

    The CORDIC ( COordinate Rotation DIgital Computer ) algorithm is very suitable for use on smaller uP, and FPGA implementations that have limited mathematical computation capabilities as it computes the sine and cosine of a value using only basic arithmetic (addition, subtraction and shifts). More about CORDIC, and how to use it to produce sine/cosine of an angle are provided here.

    There are also several sites that provide algorithm implementation examples. Simple CORDIC is one that includes detailed explanations on how to generate a table that can then be pre-compiled for use on your target device, as well as code to test the output of the following function (which uses fixed point math):

    (See documentation of following, and other functions in link)

    #define cordic_1K 0x26DD3B6A
    #define half_pi 0x6487ED51
    #define MUL 1073741824.000000
    #define CORDIC_NTAB 32
    int cordic_ctab [] = {0x3243F6A8, 0x1DAC6705, 0x0FADBAFC, 0x07F56EA6, 0x03FEAB76, 0x01FFD55B, 
    0x00FFFAAA, 0x007FFF55, 0x003FFFEA, 0x001FFFFD, 0x000FFFFF, 0x0007FFFF, 0x0003FFFF, 
    0x0001FFFF, 0x0000FFFF, 0x00007FFF, 0x00003FFF, 0x00001FFF, 0x00000FFF, 0x000007FF, 
    0x000003FF, 0x000001FF, 0x000000FF, 0x0000007F, 0x0000003F, 0x0000001F, 0x0000000F, 
    0x00000008, 0x00000004, 0x00000002, 0x00000001, 0x00000000 };
    
    void cordic(int theta, int *s, int *c, int n)
    {
      int k, d, tx, ty, tz;
      int x=cordic_1K,y=0,z=theta;
      n = (n>CORDIC_NTAB) ? CORDIC_NTAB : n;
      for (k=0; k<n; ++k)
      {
        d = z>>31;
        //get sign. for other architectures, you might want to use the more portable version
        //d = z>=0 ? 0 : -1;
        tx = x - (((y>>k) ^ d) - d);
        ty = y + (((x>>k) ^ d) - d);
        tz = z - ((cordic_ctab[k] ^ d) - d);
        x = tx; y = ty; z = tz;
      }  
     *c = x; *s = y;
    }
    

    Edit:
    I found the documentation for using the examples at the Simple CORDIC site very easy to follow. However, one small thing I ran into was when compiling the file cordic-test.c the error: use of undeclared identifier 'M_PI' occurred. It appears that when executing the compiled gentable.c file (which generates the cordic-test.c file) the line:

    #define M_PI 3.1415926535897932384626
    

    although included in its own declarations, was not included in the printf statements used to produce the file cordic-test.c. Once this was remedied, everything worked as advertised.

    As documented, the range of data produced generates 1/4 of a complete sine cycle (-π/2 - π/2 ). The following illustration contains a representation of the actual data produced between the light blue dots. The remainder of the sine signal is fabricated via mirroring and transposing the original data section.

    0 讨论(0)
  • 2021-02-02 09:55

    The classic hack to draw a circle (and hence generate a sine wave too) is Hakmem #149 by Marvin Minsky. E.g.,:

    #include <stdio.h>
    
    int main(void)
    {
        float x = 1, y = 0;
    
        const float e = .04;
    
        for (int i = 0; i < 100; ++i)
        {
            x -= e*y;
            y += e*x;
            printf("%g\n", y);
        }
    }
    

    It will be slightly eccentric, not a perfect circle, and you may get some values slightly over 1, but you could adjust by dividing by the maximum or rounding. Also, integer arithmetic can be used, and you can eliminate multiplication/division by using a negative power of two for e, so shift can be used instead.

    0 讨论(0)
  • 2021-02-02 09:55

    Unless your application calls for real precision, don't kill yourself coming up with an algorithm for a 40 point sine or cosine wave. Also, the values in your table should match the range of your LED's pwm input.

    That said, I took a look at your code and it's output and figured you weren't interpolating between points. With a little modification, I fixed it, and the error between a excel's sign function and yours is off by a max of about 0.0032 or thereabouts. The change is pretty easy to implement and has been tested using tcc, my personal go-to for C algorithm testing.

    First off, I added one more point to your sine array. The last point is set to the same value as the first element in the sine array. This fixes the math in your sine function, in particular when you set x1 to (int)phase%40, and x2 to x1+1. Adding the extra point isn't necessary, as you could set x2 to (x1+1)%40, but I chose the first approach. I'm just pointing out different ways you could accomplish this. I also added the calculation of a remainder (Basically phase - (int)phase). I'm using the remainder for the interpolation. I also added a temporary sine value holder and a delta variable.

    const int sine_table[41] = 
    {0, 5125, 10125, 14876, 19260, 23170, 26509, 29196,
    31163, 32364, 32767,  32364, 31163, 29196, 26509, 23170, 
    19260, 14876, 10125, 5125, 0, -5126, -10126,-14877,
    -19261, -23171, -26510, -29197, -31164, -32365, -32768, -32365,
    -31164, -29197, -26510, -23171, -19261, -14877, -10126, -5126, 0};
    
    int i = 0;
    int x1 = 0;
    int x2 = 0;
    float y = 0;
    
    float sin1(float phase)
    {
        int tsv,delta;
        float rem;
    
        rem = phase - (int)phase;
        x1 = (int) phase % 40;
        x2 = (x1 + 1);
    
        tsv=sine_table[x1];
        delta=sine_table[x2]-tsv;
    
        y = tsv + (int)(rem*delta);
        return y;
    }
    
    int main()
    {
        int i;  
        for(i=0;i<420;i++)
        {
           printf("%.2f, %f\n",0.1*i,sin1(0.1*i)/32768);
        }
        return 0;
    }
    

    The results look pretty good. Comparing the linear approximation vs the system's floating point sine function gave me the error plot shown below.

    0 讨论(0)
提交回复
热议问题