Generator for Sine and other wave forms


The source

There are many ways to produce sine waves with Arduino. You can
  1. produce the samples "just-in-time" calculating immediately before sending
  2. calculate them during setup
  3. precalculate and store in SRAM
  4. in FLASH
  5. in EEPROM
  6. on SD card

The hardware interface

To send a signal to an audio device there are various methods like 1-bit-audio or PWM.
We are going to use an 8 bit digital-to-analog converter (DAC) which needs 8 pins of the Arduino but makes handling much easier. Also, you don't have to to prepare much when sending sampled sound; just normalize it, save it in a "low-quality" format like "mono, 16,000 samples per second, 8-bit", and store it on a micro SD card.
By the way: to connect the SD card, you need another 4 pins. Often pins 10, 11, 12, and 13 are used to communicate with the SD card. If you still want to use the Serial terminal (RX and TX) you run out of digital pins. (You can use the analog pins as digital ones if you like.)
If you run out of port pins you can also decide to use only a 6 bit DAC by setting the lower 2 bits to zero. This will of course result in some more loss of quality.

If it is only a very short sample (<1 second) you could use your sound editor to save it in ASCII format as a text file and #include it in you FLASH memory. (If you reduce the sample rate to 8,000 samples per second you might be able to store and reproduce the numbers from zero to nine.) By doing this you don't even need an SD card.
So, for speed reasons we better not use the Arduino digitalWrite() command. Instead we look for pins mapped to one or two ports and use the PORT commands.

Some math

The following formula is to be used:

vt = [ 127 * ( sin(2 * PI * f * t) + 1) ]

where f is the desired frequency and t is the time given in seconds.

The "+ 1" makes sure no negative values will occur. The "127 *" is to use the maximum range available. It is like the automatic gain control of your tape recorder - if you ever had one. The "[ ]" is to round the floating point number to an integer number.

You also can produce other signal forms as

where PORTn is the port of your microcontroller you are using and b is the byte we are going to send.

If you want to send sampled sound to the DAC you can easily use a "*.wav" file coded as Windows PCM, 1 channel (mono), 22050 samples per second, 8 bits per sample (mostly called unsigned), and just skip the 44 byte header.

Implementation

Of course you could send the values to the ADC port in the loop() procedure with a delay of your choice. But it is much better to install a timer interrupt routine to do this. Recommended frequencies are:

To produce sine waves with Arduino we take this square wave generator, set it to the desired frequency and use either this or that digital to analog converter.
The picture above was taken from an oscilloscope using a six bit digital-to-analog converter and a sample rate of 8000 samples per second (this is what most PC sound cards offer as "telephone quality"). If you don't want the steps go for an analog oscillator. If you just want smaller steps you have to increase the sample rate and use more bits. You also have to find an Arduino port where you have access to all the bits (if you want to use port D you will loose the TxD and RxD pins which are used for the Serial terminal) or you have to combine the bits of two ports. We are using the pins d2 to d7 which belong to port D, and the pins d8 and d9 which belong to port B. Unfortunately, it is not possible to write to both port at the same time. But the delay is so short that it makes no difference.


#define FILENAME "dac8.ino - 9.3.2014"
#include <SD.h>
/* 
DAC connected to PORT B and D 
/*
Ports B & D:
data              d7 d6 d5 d4 d3 d2 d1 d0
pin   13 12 11 10  9  8  7  6  5  4  3  2  1  0
PORTB             d1 d0
PORTD                   d7 d6 d5 d4 d3 d2
*/
#define type 1
/*
1 sine      1000 Hz
2 sawtooth   122 Hz
3 triangle    31 Hz
4 square wave 61 Hz
5 random noise
6 voice msg
7 DTMF "1" (697 Hz and 1209 Hz) as an example
(actually, the ATmega328 is a bit too slow for it. Reduce tcnt2 to 70)
*/

// SD card:
const int chipSelect = 10; // SD card
boolean sd_card = false;
File fileHandle;
boolean eof;
boolean done = false;
char* filename = "welcome.wav";

// Timer-2:
const byte prescaler = 2;  // 1:1, 2:8, 3:32, 4:64, 5:128, 6:256, 7:1024
const byte tcnt2 = 131;    // 16000 Hz

float t  = 0;              // seconds
float dt = 1.0 / 16000;    // seconds
float f  = 1000;           // Hz
float f1 =  697;           // DTMF frequencies
float f2 = 1209;
byte sinetab[256];

void setup() {
  Serial.begin(9600);   
  Serial.println(FILENAME); 
  Serial.print("type ");
  Serial.println(type);
  if (type == 6) {
    Serial.println("Initializing SD card...");
    // ------------ SD card ---------------:
    pinMode(chipSelect, OUTPUT);
    if (!SD.begin(chipSelect)) {
      Serial.println("Card failed, or not present");
      return;
    }
    sd_card = true;
    Serial.println("card initialized.");
    openFile(filename);
  }
  if ((type == 1) || (type == 7)) 
    for (int i = 0; i < 256; i++) 
      sinetab[i] = 127 * (sin(2 * PI * i / 256) + 1);
  // ------------- DAC port pins --------:  
  Serial.println("output: PORTB and PORTD"); 
  delay(100);
  DDRB = B00000011 | DDRB; // remaining bits used for SD card
  DDRD = B11111100; 

  // ------------- Timer ----------------:
  setupTimer();
  DDRC =   1;
}

void openFile(char* s) {
  fileHandle.close();
  fileHandle = SD.open(s);
  if (fileHandle != 0) eof = false;
  fileHandle.seek(44); // skip normal header
}

long count = 0;
long time = millis() + 1000;

void loop() { 
  while (millis() < time); // wait
  time = millis() + 1000; 
  Serial.println(count);  // I am alive
  // the values printed prove: some of the interrupts are skipped
  count = 0;
}

//=========================================================================

ISR(TIMER2_OVF_vect) {
  // all the work is done in here:
  TCNT2 = tcnt2; 
  byte b;
  switch (type) {
    case 1: b = sine(t); break;         // sine
    case 2: b = sawtooth(t); break;     // sawtooth
    case 3: b = triangle(t); break;     // triangle
    case 4: b = squareWave(t); break;   // square wave
    case 5: b = randomNoise(t); break;  // random noise
    case 6: b = voiceMsg(t); break;     // voice msg
    case 7: b = dtmf(t); break;         // DTMF "1" (697Hz and 1209Hz)
  }
  sendByte(b);
  t = t + dt;
  count++;
  TIFR2 = 0x00; 
}

//=========================================================================

void sendByte(byte b) {
      byte d;
      // b                 7 6 5 4 3 2 1 0
      // PORTB - - - - - - ^ ^
      // PORTD                 ^ ^ ^ ^ ^ ^ - -
      d = b << 2;
      b = (b >> 6) | (PORTB & B11111100);
      PORTB = b; // nearly no time between the PORT commands
      PORTD = d;
}

//=========================================================================

byte frac(float x) { // not a standard C function
  return 256 * (x - floor(x));
}

byte sine(float t) {
  //return 127 * ( sin(2 * PI * f * t) + 1);  // too slow!!!
  return sinetab[ frac(f * t) ];
}

byte sawtooth(float t) {
  static byte b;
  return b++;
}

byte triangle(float t) {
  static byte b;
  static int8_t dir = 1; // toggle +/-1
  b = b + dir;
  if ((b == 0) || (b == 255)) dir = -dir;
  return b;
}

byte squareWave(float t) {
  static byte b;
  return b++ < 128 ? 0 : 255;
}

byte randomNoise(float t) {
  return random(256);
}

byte voiceMsg(float t) {
  byte b = 128;
  if (!done) {
    if (fileHandle) { 
      b = fileHandle.read();
      eof = !fileHandle.available();
      if (eof) done = true;
    }
  }
  return b;
}

byte dtmf(float t) {
  //return 63 * ( sin( 2 * PI * f1 * t) + sin( 2 * PI * f2 * t) + 2);
  byte s1 = sinetab[ frac(f1 * t) ] / 2;
  byte s2 = sinetab[ frac(f2 * t) ] / 2;
  return s1 + s2;
}  

//=========================================================================

void setupTimer() {
  int prescalers[] = {0,1,8,32,64,128,256,1024}; // see doc8271.pdf, p. 164
  TIMSK2 = 0x01;        // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  TCCR2A = 0x00;        // Timer2 Control Reg A: Wave Gen Mode normal
  TCCR2B = prescaler;   // Timer2 Control Reg B: Timer Prescaler set to 2
  float clk = F_CPU / prescalers[prescaler];
  float t1 = 1 / clk;
  float t2 = (256 - tcnt2) * t1;
  float f = 1 / t2;
  Serial.print("cpu frequency [Hz]: ");
  Serial.println(F_CPU);
  Serial.print("clock frequency [Hz]: ");
  Serial.println(clk);
  Serial.print("ISR frequency [Hz]: ");
  Serial.println(f);
}

the soundfile


contact: nji(at)gmx.de