Arduino Zero DAC Sine Wave Clipping

Hello! I'm using an Arduino Zero for generating a 1000 Hz sine wave. The board is being operated with the native USB port (operating with the programming port gives a 'shutdown command invoked' error). Following is the code being used for generating the sine wave:

//***************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
int sampleCount = 100; // Number of samples to read in block
int *wavSamples; //array to store sinewave points
uint32_t sampleRate = 100; //sample rate of the sine wave

void setup() {
  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
}

void loop() {
  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++;
  TC5->COUNT16.INTFLAG.bit.MC0 = 1;
}

The frequency of sine wave generated is: Sample Rate/ Sample Count. However, after uploading the code, I find that the sine wave generated has the correct frequency but is clipped at 1V. i.e. Only the sine wave part between 0 and 1V is visible and the voltage for the rest of the sine wave is constant at 1V. I read about this problem online and redirected me to removing the 'pinMode' command from my code. However, there is no pinMode command in my code. So, could someone please tell me why the sine wave generated is being clipped and how do I fix the issue?

Check the libraries, they may have inserted it in one of those. I have not used these but I have found it in other libraries in the past.

I would simply sent the data to the serial monitor as well as the PWM output to see if the issue is in the calculations or a hardware limit.

Did you know that the D/A converter on the Arduino Zero doesn’t output rail to rail signals. It looks like that is what you are generating therefore yes it will clip, because that is what the hardware does.

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

Thanks a lot everyone for your response. I'm not very familiar with Arduino programming and am learning the ropes still.

MartinL, I tried your code, but I got the same waveform clipped at 1V. I compared your code with mine and it seems like you've commented everything from the void loop section and have copied the uncommented commands to the void setup section. However, the tcDisable and tCReset commands from the void loop aren't copied to the void setup. any particular reason for that? I tried copying them to the void setup section and uploading the code but ended up getting a DC signal of 1V.

I'm looking for a sinusoidal signal going from 0 to 3.3V with a frequency of 1 kHz.

Hi @vedsoni1996

On my Arduino Zero, using the code above, I'm getting a sinusoidal signal going from 0 to 3.3V with a frequency of 1kHz.

To measure the output, I connected the board's A0 output directly to my oscilloscope probe.

Do you have any additional circuitry connected to your board's A0 output?

1 Like

I guess there really isn't a right way. If you need to output a partial waveform, I'd reset the index and enable the timer's ISR in the loop(). Then when a given number of samples (sIndex) have elapsed from within the TC5_Handler(), I'd disable the ISR from within the ISR function itself. The timer would just free-run in the background.

1 Like

Hey Martin! My oscilloscope BNC connector has 2 probes, positive and ground. I am connecting the positive probe to A0 and negative to GND. There's no additional circuitry apart from my board. I am using a USB cable plugged in the native USB port for powering the Arduino Zero. I also tried plugging in the power supply of the Arduino but ended up with the same clipped waveform.

I dont have a zero to test this; however I'd suggest a "start simple" approach to resolving this issue.
Perhaps you have already done this.
So - can you read in a voltage from a pot and directly output it to the DAC and get values ranging from zero to 3300mV?
does your genSin() routine create the right table of values?
Can you choose a value from the table, send it to the DAC and get the right voltage?

Hi @vedsoni1996

I retested your original code that you posted and it's working fine, here's the output:

If you're not getting the same output, I can only conclude that there must be a some issue with your board.

1 Like

Hello everyone! Since y'all were able to run the code, I though that there must be some issue with the oscilloscope settings. I spoke to my colleague who uses the oscilloscope often and he switched the coupling mode of the oscilloscope which solved the clipping issue. No I can see a full sine wave at 1 kHz from 0 to 3.3 V. Thanks a lot for all your help and support!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.