Showing posts with label waveform generator. Show all posts
Showing posts with label waveform generator. Show all posts

Thursday, October 1, 2015

Arduino Zero DAC Overview and Waveform Generator Example

In this video we take a look at the digital to analog converter (DAC) on the Arduino Zero. We will look at a simple example how to use the DAC and then we will look at a more complex example that turns the DAC into a pseudo waveform generator. You can find the code from this video below.


//***************ZeroDACExample Sketch*******************************
//This sketch provides an example on using the DAC on the Arduino Zero.
//It was used in a video tutorial on the ForceTronics YouTube Channel.
//This code is free and open for anybody to use and modify at their own risk

void setup()
{
  Serial.begin(57600); //start serial communication
  analogWriteResolution(10); //set the Arduino DAC for 10 bits of resolution (max)
  analogReadResolution(12); //set the ADC resolution to 12 bits, default is 10
  
  //Get user entered voltage, convert to DAC value, output DAC value
  analogWrite(A0,setDAC(getVoltValue()));
}

void loop()
{
  delay(2000);
  Serial.println();
  Serial.print("Measured voltage value is ");
  Serial.println(convertToVolt(analogRead(A1))); //Read value at ADC pin A1 and print it

}

//this function converts a user entered voltage value into a 10 bit DAC value 
int setDAC(float volt) {
//formula for calculating DAC output voltage Vdac = (dVal / 1023)*3.3V
return (int)((volt*1023)/3.3);
}

//this function gets the user entered DAC voltage value from serial monitor
float getVoltValue() {
float v = 0; //variable to store voltage 
Serial.println("Enter the voltage you want the DAC to output (range 0V to 3.3V)");
v = readParameter();
if (v < 0 || v > 3.3) v = 0; //check to make sure it is between 0 and 3.3V
Serial.print("DAC voltage set to ");
Serial.println(v);
Serial.println("Outputting DAC value........");
return v;
}

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

//This function takes and ADC integer value (0 to 4095) and turns it into a voltage level. The input is the measured 12 bit ADC value.
float convertToVolt(int aVAL) {
return (((float)aVAL/4095)*3.3); //formula to convert ADC value to voltage reading
}

//***************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 = 1000; //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(57600);
Serial.println("Enter number of points in sine wave (range 10 to 1000)");
sampleCount = readParameter();
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 = readParameter();
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;
}