Arduino Zero DAC Sine Wave Clipping

Hi @vedsoni1996

I modified your code to get it to generate a continous 1kHz sine wave, rail-to-rail:

//***************ZeroWaveGen Sketch**************************************
//This sketch generates a sine wave on the Arduino Zero DAC based on user entered sample count and sample rate
//It was used in a tutorial video on the ForceTronics YouTube Channel. This code can be used and modified freely
//at the users own risk
volatile int sIndex; //Tracks sinewave points in array
volatile int sampleCount = 100; // Number of samples to read in block
int* volatile wavSamples; //array to store sinewave points
uint32_t sampleRate = 100; //sample rate of the sine wave

void setup() {
  //SerialUSB.begin(115200);
  //while(!SerialUSB);
  analogWriteResolution(10); //set the Arduino DAC for 10 bits of resolution (max)
  getSinParameters(); //get sinewave parameters from user on serial monitor
  
  /*Allocate the buffer where the samples are stored*/
  wavSamples = (int *) malloc(sampleCount * sizeof(int));
  genSin(sampleCount); //function generates sine wave
  sIndex = 0;   //Set to zero to start from beginning of waveform
  tcConfigure(sampleRate); //setup the timer counter based off of the user entered sample rate
  //loop until all the sine wave points have been played
  //while (sIndex<sampleCount)
  //{ 
 //start timer, once timer is done interrupt will occur and DAC value will be updated
    tcStartCounter(); 
  //}
}

void loop() {
  //while(1);
  //sIndex = 0;   //Set to zero to start from beginning of waveform
  //tcConfigure(sampleRate); //setup the timer counter based off of the user entered sample rate
  //loop until all the sine wave points have been played
  //while (sIndex<sampleCount)
  //{ 
 //start timer, once timer is done interrupt will occur and DAC value will be updated
   // tcStartCounter(); 
 // }
  //disable and reset timer counter
  //tcDisable();
  //tcReset();
}

//This function generates a sine wave and stores it in the wavSamples array
//The input argument is the number of points the sine wave is made up of
void genSin(int sCount) {
 const float pi2 = 6.28; //2 x pi
 float in; 
 
 for(int i=0; i<sCount;i++) { //loop to build sine wave based on sample count
  in = pi2*(1/(float)sCount)*(float)i; //calculate value in radians for sin()
  wavSamples[i] = ((int)(sin(in)*511.5 + 511.5)); //Calculate sine wave value and offset based on DAC resolution 511.5 = 1023/2
 }
}

//This function handles getting and setting the sine wave parameters from 
//the serial monitor. It is important to use the Serial.end() function
//to ensure it doesn't mess up the Timer counter interrupts later
void getSinParameters() {
  //Serial.begin(9600);
//  Serial.println("Enter number of points in sine wave (range 10 to 1000)");
  sampleCount = 100;
//  if (sampleCount < 10 || sampleCount > 1000) sampleCount = 100;
//  Serial.print("Sample count set to ");
//  Serial.println(sampleCount);
//  Serial.println("Enter sample rate or samples per second for DAC (range 1 to 100k)");
  sampleRate = 100000;
//  if (sampleRate < 1 || sampleRate > 100000) sampleRate = 10000;
//  Serial.print("Sample rate set to ");
//  Serial.println(sampleRate);
//  Serial.println("Generating sine wave........");
  //Serial.end();
  }

//waits for serial data and reads it in. This function reads in the parameters
// that are entered into the serial terminal
int readParameter() {
 //while(!Serial.available());
 //return Serial.parseInt(); //get int that was entered on Serial monitor
}

// Configures the TC to generate output events at the sample frequency.
//Configures the TC in Frequency Generation mode, with an event output once
//each time the audio sample frequency period expires.
 void tcConfigure(int sampleRate)
{
 // Enable GCLK for TCC2 and TC5 (timer counter input clock)
 GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
 while (GCLK->STATUS.bit.SYNCBUSY);

 tcReset(); //reset TC5

 // Set Timer counter Mode to 16 bits
 TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
 // Set TC5 mode as match frequency
 TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ;
 //set prescaler and enable TC5
 TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 | TC_CTRLA_ENABLE;
 //set TC5 timer counter based off of the system clock and the user defined sample rate or waveform
 TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / sampleRate - 1);
 while (tcIsSyncing());
 
 // Configure interrupt request
 NVIC_DisableIRQ(TC5_IRQn);
 NVIC_ClearPendingIRQ(TC5_IRQn);
 NVIC_SetPriority(TC5_IRQn, 0);
 NVIC_EnableIRQ(TC5_IRQn);

 // Enable the TC5 interrupt request
 TC5->COUNT16.INTENSET.bit.MC0 = 1;
 while (tcIsSyncing()); //wait until TC5 is done syncing 
} 

//Function that is used to check if TC5 is done syncing
//returns true when it is done syncing
bool tcIsSyncing()
{
  return TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY;
}

//This function enables TC5 and waits for it to be ready
void tcStartCounter()
{
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; //set the CTRLA register
  while (tcIsSyncing()); //wait until snyc'd
}

//Reset TC5 
void tcReset()
{
  TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
  while (tcIsSyncing());
  while (TC5->COUNT16.CTRLA.bit.SWRST);
}

//disable TC5
void tcDisable()
{
  TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
  while (tcIsSyncing());
}

void TC5_Handler (void)
{
  analogWrite(A0, wavSamples[sIndex]);
  sIndex = (sIndex + 1) % sampleCount;
  TC5->COUNT16.INTFLAG.bit.MC0 = 1;
}

Note that I made wavSamples a volatile integer pointer, since it's used in both the TC5 timer's ISR and the main loop.

The main change was to let the timer free-run and continously loop the sIndex.

1 Like