Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate sine signal in C without using the standard function

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 lookup table with 40 points and interpolation.

Here's my first approach:

const int sine_table[40] = {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};

int i = 0;
int x1 = 0;
int x2 = 0;
float y = 0;

float sin1(float phase)
{
    x1 = (int) phase % 41;
    x2 = x1 + 1;
    y = (sine_table[x2] - sine_table[x1])*((float) ((int) (40*0.001*i*100) % 4100)/100 - x1) + sine_table[x1];
    return y;
}

int main()
{
    while(1)
    {
    printf("%f      ", sin1(40*0.001*i)/32768);
    i = i + 1;
    }
}

Unfortunately, this function sometimes returns values far bigger than 1. Furthermore, the interpolation doesn't seem to be good (I used this to create sine shaped brightness changes of a LED, but these are very unsmoooth).

Does anybody have a better idea to implement a sine generator in C?

like image 345
Peter123 Avatar asked Dec 20 '17 12:12

Peter123


People also ask

How do you draw a sine wave in C?

In this program we first initialize graphics mode, by passing graphics driver(DETECT), default graphics mode and specifies the directory path where initgraph looks for graphics drivers (*. BGI). Then we will draw a horizontal axis using line representing the angle in radians.

Which method is used to generate sine?

DAC Sine Wave Generator Generating high-quality sine waves is difficult but using a non-linear DAC method is used to generate high-quality sine waves. Additionally, by using the low-cost DAC-ADC technique, both ADC & DAC linearity information are precisely obtained through simply 1 hit per code.


2 Answers

OP's main problem is in generating the index for the table look-up.

OP's code attempts to access outside array sine_table[40] leading to undefined behavior. Fix that at least.

const int sine_table[40] = {0, 5125, 10125, ...
    ...
    x1 = (int) phase % 41;                     // -40 <= x1 <= 40
    x2 = x1 + 1;                               // -39 <= x2 <= 41  
    y = (sine_table[x2] - sine_table[x1])*...  // bad code, consider x1 = 40 or x2 = 40,41

Suggested change

    x1 = (int) phase % 40;   // mod 40, not 41
    if (x1 < 0) x1 += 40;    // Handle negative values
    x2 = (x1 + 1) % 40;      // Handle wrap-around 
    y = (sine_table[x2] - sine_table[x1])*...  

There exist much better approaches, yet to focus on OP's method see below.

#include <math.h>
#include <stdio.h>

const int sine_table[40] = { 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 };

int i = 0;
int x1 = 0;
int x2 = 0;
float y = 0;

float sin1(float phase) {
  x1 = (int) phase % 40;
  if (x1 < 0) x1 += 40;
  x2 = (x1 + 1) % 40;
  y = (sine_table[x2] - sine_table[x1])
      * ((float) ((int) (40 * 0.001 * i * 100) % 4100) / 100 - x1)
      + sine_table[x1];
  return y;
}

int main(void) {
  double pi = 3.1415926535897932384626433832795;
  for (int j = 0; j < 1000; j++) {
    float x = 40 * 0.001 * i;
    float radians = x * 2 * pi / 40;
    printf("%f %f %f\n", x, sin1(x) / 32768, sin(radians));
    i = i + 1;
  }
}

Output

         OP's     Reference sin()
0.000000 0.000000 0.000000
0.040000 0.006256 0.006283
0.080000 0.012512 0.012566
...
1.960000 0.301361 0.303035
2.000000 0.308990 0.309017
2.040000 0.314790 0.314987
...
39.880001 -0.020336 -0.018848
39.919998 -0.014079 -0.012567
39.959999 -0.006257 -0.006283

Better code would not pass the values i, x1, x2, y as global variables, but as function parameters or function variables. Perhaps that is an artifact of OP's debugging.


Does anybody have a better idea to implement a sine generator in C?

This is quite broad. Better as in speed, precision, code space, portability, or maintainability? sine() functions are easy to make. High-quality ones take more effort.

Although fuzzy, OP's use of a small look-up table is a good beginning - although I see it can be done without any floating point math. I recommend for OP to construct a tested and working solution and post it in Code Review for improvement ideas.

like image 149
chux - Reinstate Monica Avatar answered Oct 19 '22 05:10

chux - Reinstate Monica


...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, "better" likely means size and/or speed of compiled code, perhaps to support 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.

enter image description here

like image 18
ryyker Avatar answered Oct 19 '22 05:10

ryyker