waveform function generator functions (demo)

As a reference for a (part of) possible project I wrote a number of tunable waveform functions for sawtooth, triangle, square, sinus, stair. The sketch below print tab separated values to be shown e.g. in a spreadsheet.

The functions are not made or optimized for a real Arduino function generator however those functions could be derived from this implementation without too much trouble.

Typical function has the following signature:

double f(double t, double period, double amplitude, double phase, double yShift);

t: time (>=0)
period: period of signal
amplitude: positive and negative, an amplitude of 1 varies between -1 and 1 like sinus)
phase: offset of signal along time axis, typically 0 or between 0 and period
yShift: offset of signal along y-axis

the square and triangle have an additional dutyCycle param, stairs has an additional (int) steps parameter. All parameters except t have default values for easy usage.

As always comments, remarks and additions are welcome

//
//    FILE: functionGenerator.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.01
// PURPOSE: demo function generators
//    DATE: 2015-01-01
//     URL:
//
// Released to the public domain
//

double fgsaw(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0)
{
  t += phase;
  t = fmod(t, period);
  return yShift + amplitude * (-1.0 + 2 * t / period);
}

double fgtri(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0, double dutyCycle = 0.50)
{
  t += phase;
  t = fmod(t, period);
  // 50 % dutyCycle = faster
  //  if (t * 2 < period) return yShift + amplitude * (-1.0 + 4 * t / period);
  //  return yShift + amplitude * (3.0 - 4 * t / period);
  if (t < dutyCycle * period) return yShift + amplitude * (-1.0 + 2 * t / (dutyCycle * period));
  return yShift + amplitude * (-1.0 + 2 / (1 - dutyCycle) - 2 * t / ((1 - dutyCycle) * period));
}

double fgsqr(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0, double dutyCycle = 0.50)
{
  t += phase;
  t = fmod(t, period);
  if (t < dutyCycle * period) return yShift + amplitude;
  return yShift - amplitude;
}

double fgstr(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0, uint16_t steps = 8)
{
  t += phase;
  t = fmod(t, period);
  int level = steps * t / period;
  return yShift - amplitude + 2 * amplitude * level / (steps - 1);
}

double fgsin(double t, double period = TWO_PI, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0)
{
  t += phase;
  double rv = amplitude * sin(TWO_PI * t / period) + yShift;
  return rv;
}

uint32_t start;
uint32_t stop;

volatile double t;
volatile double y;

void setup()
{
  Serial.begin(115200);
  Serial.println("Start ");

  Serial.println("func \t usec\t max/sec");
  y = analogRead(A0);
  test_fgsqr();
  delay(10);
  test_fgsaw();
  delay(10);
  test_fgtri();
  delay(10);
  test_fgsin();
  delay(10);
  test_fgstr();
  delay(10);
  Serial.println();


  Serial.println("t \t sqr\t saw\t tri\t sin\t str");
  for (int i = 0; i < 500; i += 1)
  {
    // func(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0)
    double t = i * 0.01;
    Serial.print(t);
    Serial.print("\t");
    Serial.print(fgsqr(t));
    Serial.print("\t");
    Serial.print(fgsaw(t, 1.5));
    Serial.print("\t");
    Serial.print(fgtri(t, 1.0, 0.5, 1.0, 4.0));
    Serial.print("\t");
    Serial.print(fgsin(t, TWO_PI, 1.0, PI / 4)); // period 2PI, phase = 45°
    // Serial.print(fgsin(t, 1.0, 0.5, -0.5, 0.5));
    Serial.print("\t");
    Serial.print(fgstr(t));
    Serial.println();
  }
  Serial.println("\ndone...");
}

/******************************************************************/

void test_fgsqr()
{
  start = micros();
  for (int i = 0; i < 10000; i++) t = fgsqr(y);
  stop = micros();
  Serial.print("fsqr:\t");
  Serial.print((stop - start) / 10000.0);
  Serial.print("\t");
  Serial.println(1000000.0 / ((stop - start) / 10000.0));
}

void test_fgsaw()
{
  start = micros();
  for (int i = 0; i < 10000; i++)
  {
    t = fgsaw(y);
  }
  stop = micros();
  Serial.print("fsaw:\t");
  Serial.print((stop - start) / 10000.0);
  Serial.print("\t");
  Serial.println(1000000.0 / ((stop - start) / 10000.0));
}

void test_fgtri()
{
  start = micros();
  for (int i = 0; i < 10000; i++)
  {
    t = fgtri(y);
  }
  stop = micros();
  Serial.print("ftri:\t");
  Serial.print((stop - start) / 10000.0);
  Serial.print("\t");
  Serial.println(1000000.0 / ((stop - start) / 10000.0));
}

void test_fgsin()
{
  start = micros();
  for (int i = 0; i < 10000; i++)
  {
    t = fgsin(y);
  }
  stop = micros();
  Serial.print("fsin:\t");
  Serial.print((stop - start) / 10000.0);
  Serial.print("\t");
  Serial.println(1000000.0 / ((stop - start) / 10000.0));
}

void test_fgstr()
{
  start = micros();
  for (int i = 0; i < 10000; i++)
  {
    t = fgstr(y);
  }
  stop = micros();
  Serial.print("fstr:\t");
  Serial.print((stop - start) / 10000.0);
  Serial.print("\t");
  Serial.println(1000000.0 / ((stop - start) / 10000.0));
}

void loop()
{
}

// END OF FILE

todo’s: someday…

  • negative values for t
  • make a library of it
  • use integer math
  • make a real function generator
  • add some more
  • added support for negative values, code increase quite a bit :frowning:
  • made the fmod conditional to increase performance a bit.

It would be better to have t constrained between zero and period, for performance and size sake.

double fgsaw(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0)
{
    t += phase;
    if (t >= 0)
    {
        if (t >= period) t = fmod(t, period);
        return yShift + amplitude * (-1.0 + 2 * t / period);
    }
    t = -t;
    if (t >= period) t = fmod(t, period);
    return yShift + amplitude * ( 1.0 - 2 * t / period);
}

double fgtri(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0, double dutyCycle = 0.50)
{
    t += phase;
    if (t < 0) t = -t;
    if (t >= period) t = fmod(t, period);
    // 50 % dutyCycle = faster
    //  if (t * 2 < period) return yShift + amplitude * (-1.0 + 4 * t / period);
    //  return yShift + amplitude * (3.0 - 4 * t / period);
    if (t < dutyCycle * period) return yShift + amplitude * (-1.0 + 2 * t / (dutyCycle * period));
    return yShift + amplitude * (-1.0 + 2 / (1 - dutyCycle) - 2 * t / ((1 - dutyCycle) * period));
}

double fgsqr(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0, double dutyCycle = 0.50)
{
    t += phase;
    if (t >= 0)
    {
        if (t >= period) t = fmod(t, period);
        if (t < dutyCycle * period) return yShift + amplitude;
        return yShift - amplitude;
    }
    if (t >= period) t = fmod(-t, period);
    if (t < dutyCycle * period) return yShift - amplitude;
    return yShift + amplitude;
}

double fgsin(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0)
{
    t += phase;
    double rv = yShift + amplitude * sin(TWO_PI * t / period);
    return rv;
}

double fgstr(double t, double period = 1.0, double amplitude = 1.0, double phase = 0.0, double yShift = 0.0, uint16_t steps = 8)
{
    t += phase;
    if (t >= 0)
    {
        if (t >= period) t = fmod(t, period);
        int level = steps * t / period;
        return yShift + amplitude * (-1.0 + 2.0 * level / (steps - 1));
    }
    t = -t;
    if (t >= period) t = fmod(t, period);
    int level = steps * t / period;
    return yShift + amplitude * (1.0 - 2.0 * level / (steps - 1));
}

comments, remarks and additions are still welcome