Showing posts with label Zero. Show all posts
Showing posts with label Zero. Show all posts

Sunday, July 15, 2018

Speeding up the ADC on Arduino SAMD21 Boards (Zero, Mkr, etc) Part 2

In this video we look at how to get higher ADC speeds out of Arduino boards that are based off of the SAMD21 microcontroller. In part 2 we discuss memory limitations and we leverage an Adafruit library to do an FFT on the ADC data.


Link to details on PCBWay Maker Contest: ttps://www.pcbway.com/project/PCB_DESIGN_CONTEST.aspx

//*******************Arduino Code from Video*********************************
/*This code is from a tutorial on the ForceTronics YouTube Channel that talks about speeding up the sample rate on Arduino boards 
 * that use the SAMD21 microcontroller like the Arduino Zero or MKR series. This code is free and clear for other to use and modify 
 * at their own risk. 
 */
#include "Adafruit_ZeroFFT.h" //adafruit library for FFT
#include <SPI.h>
#include <SD.h>

const long sRate = 300000; //sample rate of ADC
const int16_t dSize = 1024; //used to set number of samples
const byte chipSelect = 38; //used for SPI chip select pin
const byte gClk = 3; //used to define which generic clock we will use for ADC
const byte intPri = 0; //used to set interrupt priority for ADC
const int cDiv = 1; //divide factor for generic clock
const float period = 3.3334; //period of 300k sample rate
String wFile = "ADC_DATA"; //used as file name to store wind and GPS data
volatile int16_t aDCVal[dSize]; //array to hold ADC samples
volatile int count = 0; //tracks how many samples we have collected
bool done = false; //tracks when done writing data to SD card

void setup() {
  portSetup(); //setup the ports or pin to make ADC measurement
  genericClockSetup(gClk,cDiv); //setup generic clock and routed it to ADC
  aDCSetup(); //this function set up registers for ADC, input argument sets ADC reference
  setUpInterrupt(intPri); //sets up interrupt for ADC and argument assigns priority
  aDCSWTrigger(); //trigger ADC to start free run mode
}

void loop() {
  
  if(count==(dSize-1) and !done) { //if done reading and they have not been written to SD card yet
    removeDCOffset(aDCVal, dSize, 8); //this function removes DC offset if you are measuring an AC signal
    int16_t fTTVal[dSize]; //array to hold FFT samples
    for(int j=0; j<dSize; j++) fTTVal[j] = aDCVal[j]; //copy one array to another array
    ZeroFFT(fTTVal,dSize); //calculate FFT and store into array
    SD.begin(chipSelect); //start SD card library
    File myFile = SD.open((wFile + ".csv"), FILE_WRITE); //open file to write data to CSV file
    if (myFile) {
      float sTime = 0;
      for (int y = 0; y < dSize; y++) { //write each reading to CSV 
        myFile.print(String(FFT_BIN(y, sRate, dSize))+",");
        myFile.print(String((fTTVal[y]))+",");
        myFile.print(sTime,5); //write each reading to SD card as string
        myFile.print(",");
        myFile.println(String(aDCVal[y])+","); //write each reading to SD card as string
        sTime = sTime + period; //update signal period info
      }
    }
    myFile.close(); //close file
    done = true; //we are done 
  }
}

//function for configuring ports or pins, note that this will not use the same pin numbering scheme as Arduino
void portSetup() {
  // Input pin for ADC Arduino A0/PA02
  REG_PORT_DIRCLR1 = PORT_PA02;

  // Enable multiplexing on PA02_AIN0 PA03/ADC_VREFA
  PORT->Group[0].PINCFG[2].bit.PMUXEN = 1;
  PORT->Group[0].PINCFG[3].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}

//this function sets up the generic clock that will be used for the ADC unit
//by default it uses the 48M system clock, input arguments set divide factor for generic clock and choose which generic clock
//Note unless you understand how the clock system works use clock 3. clocks 5 and up can brick the microcontroller based on how Arduino configures things
void genericClockSetup(int clk, int dFactor) {
  // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;
  
  //This allows you to setup a div factor for the selected clock certain clocks allow certain division factors: Generic clock generators 3 - 8 8 division factor bits - DIV[7:0]
  GCLK->GENDIV.reg |= GCLK_GENDIV_ID(clk)| GCLK_GENDIV_DIV(dFactor);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);  

  //configure the generator of the generic clock with 48MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(clk); // GCLK_GENCTRL_DIVSEL don't need this, it makes divide based on power of two
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  
  //enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(clk) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);
}

/*
ADC_CTRLB_PRESCALER_DIV4_Val    0x0u  
ADC_CTRLB_PRESCALER_DIV8_Val    0x1u   
ADC_CTRLB_PRESCALER_DIV16_Val   0x2u   
ADC_CTRLB_PRESCALER_DIV32_Val   0x3u   
ADC_CTRLB_PRESCALER_DIV64_Val   0x4u   
ADC_CTRLB_PRESCALER_DIV128_Val  0x5u   
ADC_CTRLB_PRESCALER_DIV256_Val  0x6u   
ADC_CTRLB_PRESCALER_DIV512_Val  0x7u   
--> 8 bit ADC measurement takes 5 clock cycles, 10 bit ADC measurement takes 6 clock cycles
--> Using 48MHz system clock with division factor of 1
--> Using ADC division factor of 32
--> Sample rate = 48M / (5 x 32) = 300 KSPS
This function sets up the ADC, including setting resolution and ADC sample rate
*/
void aDCSetup() {
  // Select reference
  REG_ADC_REFCTRL = ADC_REFCTRL_REFSEL_INTVCC1; //set vref for ADC to VCC

  // Average control 1 sample, no right-shift
  REG_ADC_AVGCTRL |= ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);
  
  // Input control and input scan
  REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
  // Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV32 | ADC_CTRLB_FREERUN; //This is where you set the divide factor, note that the divide call has no effect until you change Arduino wire.c
  //Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; // Disable window monitor mode
  while(ADC->STATUS.bit.SYNCBUSY);

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI; //start ADC when event occurs
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);
}

//This function sets up an ADC interrupt that is triggered 
//when an ADC value is out of range of the window
//input argument is priority of interrupt (0 is highest priority)
void setUpInterrupt(byte priority) {
  
  ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable ADC ready interrupt
  while (ADC->STATUS.bit.SYNCBUSY);

  NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
  NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}

//software trigger to start ADC in free run
//in future could use this to set various ADC triggers
void aDCSWTrigger() {
  ADC->SWTRIG.reg |= ADC_SWTRIG_START;
}

//This ISR is called each time ADC makes a reading
void ADC_Handler() {
    if(count<1023) {
      aDCVal[count] = REG_ADC_RESULT;
      count++;
    }
    ADC->INTFLAG.reg = ADC_INTENSET_RESRDY; //Need to reset interrupt
}

//This function takes out DC offset of AC signal, it assumes that the offset brings signal to zero volts
//input arguments: array with measured points and bits of measurement
void removeDCOffset(volatile int16_t aDC[], int aSize, int bits) {
  int aSteps = pow(2,bits)/2; //get number of levels in ADC measurement and cut it in half
  for(int i=0; i<aSize; i++) {
    aDC[i] = aDC[i] - aSteps; //take out offset
  }
}

Saturday, June 30, 2018

Speeding up the ADC on Arduino SAMD21 Boards (Zero, Mkr, etc) P1

In this video we look at how to get higher ADC speeds out of Arduino boards that are based off of the SAMD21 microcontroller.



Windows file paths from video:
  • files that define register data structures: C:\Users\yourname\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.18\bootloaders\sofia\Bootloader_D21_Sofia_V2.1\src\ASF\sam0\utils\cmsis\samd21\include\component
  • wire.c file path: C:\Users\yourname\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.18\cores\arduino

//*******************Arduino example code from video*************************
/*This code is from a tutorial on the ForceTronics YouTube Channel that talks about speeding up the sample rate on Arduino boards 
 * that use the SAMD21 microcontroller like the Arduino Zero or MKR series. This code is free and clear for other to use and modify 
 * at their own risk. 
 */
#include <SPI.h>
#include <SD.h>

const int16_t dSize = 1024; //used to set number of samples
const byte chipSelect = 38; //used for SPI chip select pin
const byte gClk = 3; //used to define which generic clock we will use for ADC
const byte intPri = 0; //used to set interrupt priority for ADC
const int cDiv = 1; //divide factor for generic clock
const float period = 3.3334; //period of 300k sample rate
String wFile = "ADC_DATA"; //used as file name to store wind and GPS data
volatile int aDCVal[dSize]; //array to hold ADC samples
volatile int count = 0; //tracks how many samples we have collected
bool done = false; //tracks when done writing data to SD card

void setup() {
  portSetup(); //setup the ports or pin to make ADC measurement
  genericClockSetup(gClk,cDiv); //setup generic clock and routed it to ADC
  aDCSetup(); //this function set up registers for ADC, input argument sets ADC reference
  setUpInterrupt(intPri); //sets up interrupt for ADC and argument assigns priority
  aDCSWTrigger(); //trigger ADC to start free run mode
}

void loop() {
  
  if(count==(dSize-1) and !done) { //if done reading and they have not been written to SD card yet
    removeDCOffset(aDCVal, dSize, 8); //this function removes DC offset if you are measuring an AC signal
    SD.begin(chipSelect); //start SD card library
    File myFile = SD.open((wFile + ".csv"), FILE_WRITE); //open file to write data to CSV file
    if (myFile) {
      float sTime = 0;
      for (int y = 0; y < dSize; y++) {
        myFile.print(sTime,5); //write each reading to SD card as string
        myFile.print(",");
        myFile.println(String(aDCVal[y])+","); //write each reading to SD card as string
        sTime = sTime + period; //update signal period info
      }
    }
    myFile.close(); //close file
    done = true; //we are done 
  }
}

//function for configuring ports or pins, note that this will not use the same pin numbering scheme as Arduino
void portSetup() {
  // Input pin for ADC Arduino A0/PA02
  REG_PORT_DIRCLR1 = PORT_PA02;

  // Enable multiplexing on PA02_AIN0 PA03/ADC_VREFA
  PORT->Group[0].PINCFG[2].bit.PMUXEN = 1;
  PORT->Group[0].PINCFG[3].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}

//this function sets up the generic clock that will be used for the ADC unit
//by default it uses the 48M system clock, input arguments set divide factor for generic clock and choose which generic clock
//Note unless you understand how the clock system works use clock 3. clocks 5 and up can brick the microcontroller based on how Arduino configures things
void genericClockSetup(int clk, int dFactor) {
  // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;
  
  //This allows you to setup a div factor for the selected clock certain clocks allow certain division factors: Generic clock generators 3 - 8 8 division factor bits - DIV[7:0]
  GCLK->GENDIV.reg |= GCLK_GENDIV_ID(clk)| GCLK_GENDIV_DIV(dFactor);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);  

  //configure the generator of the generic clock with 48MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(clk); // GCLK_GENCTRL_DIVSEL don't need this, it makes divide based on power of two
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  
  //enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(clk) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);
}

/*
ADC_CTRLB_PRESCALER_DIV4_Val    0x0u  
ADC_CTRLB_PRESCALER_DIV8_Val    0x1u   
ADC_CTRLB_PRESCALER_DIV16_Val   0x2u   
ADC_CTRLB_PRESCALER_DIV32_Val   0x3u   
ADC_CTRLB_PRESCALER_DIV64_Val   0x4u   
ADC_CTRLB_PRESCALER_DIV128_Val  0x5u   
ADC_CTRLB_PRESCALER_DIV256_Val  0x6u   
ADC_CTRLB_PRESCALER_DIV512_Val  0x7u   
--> 8 bit ADC measurement takes 5 clock cycles, 10 bit ADC measurement takes 6 clock cycles
--> Using 48MHz system clock with division factor of 1
--> Using ADC division factor of 32
--> Sample rate = 48M / (5 x 32) = 300 KSPS
This function sets up the ADC, including setting resolution and ADC sample rate
*/
void aDCSetup() {
  // Select reference
  REG_ADC_REFCTRL = ADC_REFCTRL_REFSEL_INTVCC1; //set vref for ADC to VCC

  // Average control 1 sample, no right-shift
  REG_ADC_AVGCTRL |= ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN(0);
  
  // Input control and input scan
  REG_ADC_INPUTCTRL |= ADC_INPUTCTRL_GAIN_1X | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0;
  // Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->CTRLB.reg |= ADC_CTRLB_RESSEL_8BIT | ADC_CTRLB_PRESCALER_DIV32 | ADC_CTRLB_FREERUN; //This is where you set the divide factor, note that the divide call has no effect until you change Arduino wire.c
  //Wait for synchronization
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; // Disable window monitor mode
  while(ADC->STATUS.bit.SYNCBUSY);

  ADC->EVCTRL.reg |= ADC_EVCTRL_STARTEI; //start ADC when event occurs
  while (ADC->STATUS.bit.SYNCBUSY);

  ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; //set ADC to run in standby
  while (ADC->STATUS.bit.SYNCBUSY);
}

//This function sets up an ADC interrupt that is triggered 
//when an ADC value is out of range of the window
//input argument is priority of interrupt (0 is highest priority)
void setUpInterrupt(byte priority) {
  
  ADC->INTENSET.reg |= ADC_INTENSET_RESRDY; // enable ADC ready interrupt
  while (ADC->STATUS.bit.SYNCBUSY);

  NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
  NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}

//software trigger to start ADC in free run
//in future could use this to set various ADC triggers
void aDCSWTrigger() {
  ADC->SWTRIG.reg |= ADC_SWTRIG_START;
}

//This ISR is called each time ADC makes a reading
void ADC_Handler() {
    if(count<1023) {
      aDCVal[count] = REG_ADC_RESULT;
      count++;
    }
    ADC->INTFLAG.reg = ADC_INTENSET_RESRDY; //Need to reset interrupt
}

//This function takes out DC offset of AC signal, it assumes that the offset brings signal to zero volts
//input arguments: array with measured points and bits of measurement
void removeDCOffset(volatile int aDC[], int aSize, int bits) {
  int aSteps = pow(2,bits)/2; //get number of levels in ADC measurement and cut it in half
  for(int i=0; i<aSize; i++) {
    aDC[i] = aDC[i] - aSteps; //take out offset
  }
}

Sunday, November 5, 2017

How to Build a Simple DC Electronic Load with Arduino Part 2

In this video we look at how to make a simple DC electronic load with Arduino and some simple components. In part two 2 we add some flexible measurement capabilities.







//***************************Arduino Code*************************************************
#include <Average.h> /* * This code was used for a tutorial on how to build a simple eload with Arduino * The Tutorial can be found on the ForceTronics YouTube Channel * This code is public domain and free for anybody to use at their own risk */ //Uncomment AVGMEAS to print out avg measurement data and uncomment FASTMEAS to print fast voltage or current measur #define AVGMEAS //#define FASTMEAS //uncomment to print fast current measurements, leave commented to print fast voltage measurements //#define FASTAMP //The following variables set the eload sequence const int sValues[] = {20,50,280}; //set the DAC value for each step const unsigned long sTime[] = {350,50,15}; //set the dwell time for each step const int sNum = 3; //number of steps in the sequence, this number should match the number or items in the arrays long repeat = -1; //set the number of times the sequence repeats, -1 means infinite //The following variables control the measurement rates int measInterval = 5; //in milli seconds, fast measurement rate. This should be less than or equal to measAvgInt int measAvgInt = 1000; //this is done in milliseconds and should be a multiple of the meas interval int aCount = 0; //tracks where we are in the sequence unsigned long sStart; //trcks when a sequence step starts its timer unsigned long mStart; //tracks when a new measurement interval starts unsigned long aHours = 0; //holds amp hour value const unsigned long m2Hours = 360000; //constant value for converting mil sec to hours const float lRes = 5.08; //exact value of eload resistor --> 5.08 const float rMult = 1.51; //multiplier for resistor divider network: R1 = 5.08k and R2 = 9.98k ratio is 9.98 / (9.98 + 5.08) = .663 --> 1.51 const byte aDCVolt = A2; //ADC channel for measuring input voltage const byte aDCCurrent = A4; //ADC channel for measuring voltage across resistor Average<float> voltMeas((measAvgInt/measInterval)); //create average obect to handle voltage measurement data Average<float> currMeas((measAvgInt/measInterval)); //create average object to handle current measurement data void setup() { pinMode(A0, OUTPUT); //A0 DAC pin to output analogWriteResolution(10); //default DAC resolution is 8 bit, swith it to 10 bit (max) analogReadResolution(12); //default ADC resolution is 10 bit, change to 12 bit Serial.begin(57600); analogWrite(A0, sValues[aCount]); //Set DAC value for first step sStart = mStart = millis(); //start timer for seq and measure interval } void loop() { while(repeat > 0 || repeat < 0) { //loop controls how often sequence repeats //timer for changing sequence step if(timer(sTime[aCount],sStart)) { aCount++; //go to next sequence step if(aCount >= sNum) aCount = 0; //if at end go back to beginning analogWrite(A0, sValues[aCount]); //Set DAC value for step sStart = millis(); //reset timer } if(timer(measInterval,mStart)) { voltMeas.push(inputVolt(aDC2Volt(analogRead(aDCVolt)))); //push value into average array currMeas.push(inputCurrent(aDC2Volt(analogRead(aDCCurrent)))); //push value into average array //print input voltage value and current values #ifdef FASTMEAS #ifdef FASTAMP Serial.println(currMeas.get((currMeas.getCount() - 1))*1000); //serial print out of fast current measurements #else Serial.println(voltMeas.get((voltMeas.getCount() - 1))); //serial print out of fast voltage measurements #endif #endif mStart = millis(); //reset timer } //print out average, max / min, and amp hour measurements if(voltMeas.getCount() == (measAvgInt/measInterval)) { #ifdef AVGMEAS Serial.print("Average voltage: "); Serial.print(voltMeas.mean()); Serial.println(" V"); //get and print average voltage value float mA = currMeas.mean()*1000; //get average current value in mA Serial.print("Average current: "); Serial.print(mA); Serial.println(" mA"); //print current value Serial.print("Max voltage: "); Serial.print(voltMeas.maximum()); Serial.println(" V"); //print max and min voltage Serial.print("Min voltage: "); Serial.print(voltMeas.minimum()); Serial.println(" V"); Serial.print("Max current: "); Serial.print(currMeas.maximum()*1000); Serial.println(" mA"); //print max and min current Serial.print("Min current: "); Serial.print(currMeas.minimum()*1000); Serial.println(" mA"); float aH = ampHoursCal(measAvgInt,mA); //calculate how much amp hours of current was consumed since start if(aH < 1000) { Serial.print("Amp hours of power source: "); Serial.print(aH); Serial.println(" uAh"); } //print current in uA else { Serial.print("Amp hours of power source: "); Serial.print(aH/1000); Serial.println(" mAh"); } //print current in mA #endif voltMeas.clear(); //clear voltage measurement array currMeas.clear(); //clear current measurement array } if(repeat > 0) repeat--; //increment repeat if not infinite loop } } //timer function that runs in mill second steps. //Inputs are timer interval and timer start time bool timer(unsigned long tInterval, unsigned long tStart) { unsigned long now = millis(); //get timer value if ((now - tStart) > tInterval ) return true; //check if interval is up return false; //interval is not up } //converts raw ADC reading to voltage value based on 3.3V reference //input is 12 bit ADC value float aDC2Volt(int aDC) { return (((float)aDC/4095)*3.3); } //function converts voltage value to input voltage value based off resistor voltage divider constant //input is measured voltage float inputVolt(float aVolt) { return (rMult*aVolt); } //converts voltage measurement at load resistor to current measurement based on load resistor value //Input is measured voltage float inputCurrent(float rVolt) { return (rVolt/lRes); } //This functions calculates amp hours //amp hour = amp hour value + (amps * (mil sec / 360k) //input: measInt is measurement interval in milli sec and aVal is the measured current value in mA float ampHoursCal(int measInt, float aVal) { aHours = aHours + (aVal * ((double)measInt/m2Hours)*1000); //this converts currect measurement to mA return aHours; }



Wednesday, February 1, 2017

Accessing Hidden Pins on the Arduino Zero

In this video we look at how you can access six additional digital pins from your Arduino Zero board. We also take a look at some flexible wired communication options you can take advantage of on the Arduino Zero.






Link to Adafruit tutorial referenced in the video: https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-serial

Path on Windows to get to variants.cpp: C:\Users\yourname\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.6

Zero pin out chart from video:


//**********************Arduino Code from the video*********************
//This code is free to use and modify at your own risk

bool tog = false; //used to toggle LEDs on and off

void setup() {
  pinMode(38,OUTPUT); //This is the pin labeled "ATN"
  pinMode(22,OUTPUT); //This is MISO pin on ICSP header
  pinMode(20,OUTPUT); //This is SDA pin
  SerialUSB.begin(57600); //this is the native USB port
  Serial.begin(57600); //this is the programming port
  Serial1.begin(57600); //this is for pins D0 and D1
}

void loop() {

  if(tog) { //set each of the pins to high to turn LEDs on
    digitalWrite(38,HIGH);
    digitalWrite(22,HIGH);
    digitalWrite(20,HIGH);
    tog = false;
  }
  else { //set each pin to low to turn them off
    digitalWrite(38,LOW);
    digitalWrite(22,LOW);
    digitalWrite(20,LOW);
    tog = true;
  }

  delay(1500); //delsy for 1.5 seconds
}

Friday, December 16, 2016

Measuring Wind Speed with an Anemometer and Arduino

In this video we look at how to measure wind speed using an anemometer and Arduino. This approach will work on both ARM and AVR based Arduinos.


//*****************Arduino anemometer sketch******************************
const byte interruptPin = 3; //anemomter input to digital pin
volatile unsigned long sTime = 0; //stores start time for wind speed calculation
unsigned long dataTimer = 0; //used to track how often to communicate data
volatile float pulseTime = 0; //stores time between one anemomter relay closing and the next
volatile float culPulseTime = 0; //stores cumulative pulsetimes for averaging
volatile bool start = true; //tracks when a new anemometer measurement starts
volatile unsigned int avgWindCount = 0; //stores anemometer relay counts for doing average wind speed
float aSetting = 60.0; //wind speed setting to signal alarm

void setup() {
  pinMode(13, OUTPUT); //setup LED pin to signal high wind alarm condition
  pinMode(interruptPin, INPUT_PULLUP); //set interrupt pin to input pullup
  attachInterrupt(interruptPin, anemometerISR, RISING); //setup interrupt on anemometer input pin, interrupt will occur whenever falling edge is detected
  dataTimer = millis(); //reset loop timer
}

void loop() {
 
  unsigned long rTime = millis();
  if((rTime - sTime) > 2500) pulseTime = 0; //if the wind speed has dropped below 1MPH than set it to zero
     
  if((rTime - dataTimer) > 1800){ //See if it is time to transmit
   
    detachInterrupt(interruptPin); //shut off wind speed measurement interrupt until done communication
    float aWSpeed = getAvgWindSpeed(culPulseTime,avgWindCount); //calculate average wind speed
    if(aWSpeed >= aSetting) digitalWrite(13, HIGH);   // high speed wind detected so turn the LED on
    else digitalWrite(13, LOW);   //no alarm so ensure LED is off
    culPulseTime = 0; //reset cumulative pulse counter
    avgWindCount = 0; //reset average wind count

    float aFreq = 0; //set to zero initially
    if(pulseTime > 0.0) aFreq = getAnemometerFreq(pulseTime); //calculate frequency in Hz of anemometer, only if pulsetime is non-zero
    float wSpeedMPH = getWindMPH(aFreq); //calculate wind speed in MPH, note that the 2.5 comes from anemometer data sheet
   
    Serial.begin(57600); //start serial monitor to communicate wind data
    Serial.println();
    Serial.println("...................................");
    Serial.print("Anemometer speed in Hz ");
    Serial.println(aFreq);
    Serial.print("Current wind speed is ");
    Serial.println(wSpeedMPH);
    Serial.print("Current average wind speed is ");
    Serial.println(aWSpeed);
    Serial.end(); //serial uses interrupts so we want to turn it off before we turn the wind measurement interrupts back on
   
    start = true; //reset start variable in case we missed wind data while communicating current data out
    attachInterrupt(digitalPinToInterrupt(interruptPin), anemometerISR, RISING); //turn interrupt back on
    dataTimer = millis(); //reset loop timer
  }
}

//using time between anemometer pulses calculate frequency of anemometer
float getAnemometerFreq(float pTime) { return (1/pTime); }
//Use anemometer frequency to calculate wind speed in MPH, note 2.5 comes from anemometer data sheet
float getWindMPH(float freq) { return (freq*2.5); }
//uses wind MPH value to calculate KPH
float getWindKPH(float wMPH) { return (wMPH*1.61); }
//Calculates average wind speed over given time period
float getAvgWindSpeed(float cPulse,int per) {
  if(per) return getWindMPH(getAnemometerFreq((float)(cPulse/per)));
  else return 0; //average wind speed is zero and we can't divide by zero
  }

//This is the interrupt service routine (ISR) for the anemometer input pin
//it is called whenever a falling edge is detected
void anemometerISR() {
  unsigned long cTime = millis(); //get current time
  if(!start) { //This is not the first pulse and we are not at 0 MPH so calculate time between pulses
   // test = cTime - sTime;
    pulseTime = (float)(cTime - sTime)/1000;
    culPulseTime += pulseTime; //add up pulse time measurements for averaging
    avgWindCount++; //anemomter went around so record for calculating average wind speed
  }
  sTime = cTime; //store current time for next pulse time calculation
  start = false; //we have our starting point for a wind speed measurement
}

Friday, October 14, 2016

Utilizing Advanced ADC Capabilities on Arduino’s with the SAMD21 (Zero, MKR1000, etc) Part 1

We are all familiar with the Arduino "analogRead()" function, but there is a lot more to the SAMD21 ADC then just taking simple readings. In this video series we look at some of the more advanced ADC capabilities of the SAMD21 and how to use them. In part 1 we look at how to use the window monitoring capability of the ADC.



//*******************Arduino code from the video*********************
//This sketch is from a tutorial on the ForceTronics YouTube Channel called 
//Utilizing Advanced ADC Capabilities on Arduino’s with the SAMD21 (Zero, MKR1000, etc)
//This code is public domain and free to anyone to use or modify at your own risk

//declare const for window mode settings
const byte DISABLE = 0;
const byte MODE1 = 1;
const byte MODE2 = 2;
const byte MODE3 = 3;
const byte MODE4 = 4;

void setup() {
  //call this function to start the ADC in window and define the window parameters
  ADCWindowBegin(MODE1, 512, 750); //Do not use the Arduino analog functions until you call ADCWindowEnd()
  Serial.begin(57600);
}

void loop() {
  delay(1500);
  Serial.println(readADC()); //the "readADC()" function can be used to get ADC readings while in Window mode
  Serial.println();
}

//This is the interrupt service routine (ISR) that is called 
//if an ADC measurement falls out of the range of the window 
void ADC_Handler() {
    digitalWrite(LED_BUILTIN, HIGH); //turn LED off
    ADC->INTFLAG.reg = ADC_INTFLAG_WINMON; //Need to reset interrupt
}

//this function sets up the ADC window mode with interrupt
void ADCWindowBegin(byte mode, int upper, int lower) {
  setMeasPin(); //function sets up ADC pin A0 as input
  setGenClock(); //setup ADC clock, using internal 8MHz clock
  setUPADC(); //configure ADC
  setADCWindow(mode, upper, lower); //setup ADC window mode 
  setUpInterrupt(0); //setup window mode interrupt with highest priority
  enableADC(1); //enable ADC 
}

void ADCWindowEnd() {
  NVIC_DisableIRQ(ADC_IRQn); //turn off interrupt
  enableADC(0); //disable ADC 
}

//setup measurement pin, using Arduino ADC pin A3
void setMeasPin() {
  // Input pin for ADC Arduino A3/PA04
  REG_PORT_DIRCLR1 = PORT_PA04;

  // Enable multiplexing on PA04
  PORT->Group[0].PINCFG[4].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].reg = PORT_PMUX_PMUXE_B | PORT_PMUX_PMUXO_B;
}

//Function sets up generic clock for ADC
//Uses built-in 8MHz clock
void setGenClock() {
   // Enable the APBC clock for the ADC
  REG_PM_APBCMASK |= PM_APBCMASK_ADC;

  configOSC8M(); //this function sets up the internal 8MHz clock that we will use for the ADC
  
  // Setup clock GCLK3 for no div factor
   GCLK->GENDIV.reg |= GCLK_GENDIV_ID(3)| GCLK_GENDIV_DIV(1);
   while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);  

  //configure the generator of the generic clock, which is 8MHz clock
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSC8M | GCLK_GENCTRL_ID(3) | GCLK_GENCTRL_DIVSEL;
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  
  //enable clock, set gen clock number, and ID to where the clock goes (30 is ADC)
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(3) | GCLK_CLKCTRL_ID(30);
  while (GCLK->STATUS.bit.SYNCBUSY);
}

//Function that does general settings for ADC
//sets it for a single sample
//Uses internal voltage reference
//sets gain factor to 1/2
void setUPADC() {
  // Select reference, internal VCC/2
  ADC->REFCTRL.reg |= ADC_REFCTRL_REFSEL_INTVCC1; // VDDANA/2, combine with gain DIV2 for full VCC range

  // Average control 1 sample, no right-shift
  ADC->AVGCTRL.reg |= ADC_AVGCTRL_ADJRES(0) | ADC_AVGCTRL_SAMPLENUM_1;

  // Sampling time, no extra sampling half clock-cycles
  REG_ADC_SAMPCTRL |= ADC_SAMPCTRL_SAMPLEN(0);

  // Input control: set gain to div by two so ADC has measurement range of VCC, no diff measurement so set neg to gnd, pos input set to pin 0 or A0
  ADC->INPUTCTRL.reg |= ADC_INPUTCTRL_GAIN_DIV2 | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN4;
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);

  // PS16, 8 MHz, ADC_CLK = 500 kHz, ADC sampling rate, single eded, 12 bit, free running, DIV2 gain, 7 ADC_CLKs, 14 usec
  ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV16 | ADC_CTRLB_RESSEL_10BIT | ADC_CTRLB_FREERUN; // Run ADC continously, 7 ADC_CLKs, 14 usec
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);
}

//This function is used to setup the ADC windowing mode
//inputs are the mode, upper window value, and lower window value
//
void setADCWindow(byte mode, int upper, int lower) {
  ADC->WINCTRL.reg = mode; //set window mode
  while (ADC->STATUS.bit.SYNCBUSY);

   ADC->WINUT.reg = upper; //set upper threshold
   while (ADC->STATUS.bit.SYNCBUSY);

   ADC->WINLT.reg = lower; //set lower threshold
   while (ADC->STATUS.bit.SYNCBUSY);
}

//This function sets up an ADC interrupt that is triggered 
//when an ADC value is out of range of the window
//input argument is priority of interrupt (0 is highest priority)
void setUpInterrupt(byte priority) {
  
  ADC->INTENSET.reg |= ADC_INTENSET_WINMON; // enable ADC window monitor interrupt
   while (ADC->STATUS.bit.SYNCBUSY);

   NVIC_EnableIRQ(ADC_IRQn); // enable ADC interrupts
   NVIC_SetPriority(ADC_IRQn, priority); //set priority of the interrupt
}

//function allows you to enable or disable ADC
void enableADC(bool en) {
  if(en) ADC->CTRLA.reg = 2; //2 is binary 010 which is register bit to enable ADC
  else ADC->CTRLA.reg = 0; //0 disables ADC
}

//This function will return the latest ADC reading made during free run window mode
//must first start the ADC before calling this function
unsigned int readADC() {
  // Free running, wait for conversion to complete
  while (!(REG_ADC_INTFLAG & ADC_INTFLAG_RESRDY));
  // Wait for synchronization before reading RESULT
  while (REG_ADC_STATUS & ADC_STATUS_SYNCBUSY);
  
  return REG_ADC_RESULT;
}

//function enables the 8MHz clock used for the ADC
void configOSC8M() 
{
  SYSCTRL->OSC8M.reg |= SYSCTRL_OSC8M_ENABLE;
}


Saturday, September 24, 2016

Reducing Power Consumption on Arduino Zero, MKR1000, or any SAMD21 Arduino Part 1

In this multiple part series we look at how to reduce power consumption for battery powered designs that utilize Arduino's with the Atmel SAMD21 MCU (Zero, MKR1000, etc). In part one we look at how to put the SAMD21 to sleep and wake it up with either the real time clock (RTC) or an external event on an input pin.



//***************Arduino Sketch from the video*********************.
//This code was used for a tutorial on the ForceTronics YouTube channel. It shows how to save power
//by putting Arduino's based on the SAMD21 MCU (MKR1000, Zero, etc) to sleep and how to wake them
//This code is public domain for anybody to use or modify

//#include "RTCZero.h"
#include <RTCZero.h>

/* Create an rtc object */
RTCZero rtc;

/* Change these values to set the current initial time */
const byte seconds = 0;
const byte minutes = 00;
const byte hours = 00;

/* Change these values to set the current initial date */
const byte day = 24;
const byte month = 9;
const byte year = 16;

void setup() 
{
  delay(5000); //delay so we can see normal current draw
   pinMode(LED_BUILTIN, OUTPUT); //set LED pin to output
  digitalWrite(LED_BUILTIN, LOW); //turn LED off

  rtc.begin(); //Start RTC library, this is where the clock source is initialized

  rtc.setTime(hours, minutes, seconds); //set time
  rtc.setDate(day, month, year); //set date

  rtc.setAlarmTime(00, 00, 10); //set alarm time to go off in 10 seconds
  
  //following two lines enable alarm, comment both out if you want to do external interrupt
  rtc.enableAlarm(rtc.MATCH_HHMMSS); //set alarm
  rtc.attachInterrupt(ISR); //creates an interrupt that wakes the SAMD21 which is triggered by a FTC alarm
  //comment out the below line if you are using RTC alarm for interrupt
 // extInterrupt(A1); //creates an interrupt source on external pin
  
  //puts SAMD21 to sleep
  rtc.standbyMode(); //library call
  //samSleep(); //function to show how call works
}

void loop() 
{
  //do nothing in main loop
}

//interrupt service routine (ISR), called when interrupt is triggered 
//executes after MCU wakes up
void ISR()
{
  digitalWrite(LED_BUILTIN, HIGH);
}


//function that sets up external interrupt
void extInterrupt(int interruptPin) {
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(interruptPin, ISR, LOW);
}

//function to show how to put the 
void samSleep()
{
  // Set the sleep mode to standby
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
  // SAMD sleep
  __WFI();
}

//**********************Changed "begin" function from RTCZero Library**************
void RTCZero::begin(bool resetTime)
{
  uint16_t tmp_reg = 0;
  
  PM->APBAMASK.reg |= PM_APBAMASK_RTC; // turn on digital interface clock
  //config32kOSC();

  // If the RTC is in clock mode and the reset was
  // not due to POR or BOD, preserve the clock time
  // POR causes a reset anyway, BOD behaviour is?
  bool validTime = false;
  RTC_MODE2_CLOCK_Type oldTime;

  if ((!resetTime) && (PM->RCAUSE.reg & (PM_RCAUSE_SYST | PM_RCAUSE_WDT | PM_RCAUSE_EXT))) {
    if (RTC->MODE2.CTRL.reg & RTC_MODE2_CTRL_MODE_CLOCK) {

      validTime = true;
      oldTime.reg = RTC->MODE2.CLOCK.reg;
    }
  }
  // Setup clock GCLK2 with OSC32K divided by 32
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2)|GCLK_GENDIV_DIV(4);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
    ;                                                         /*XOSC32K*/
  GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_DIVSEL );
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
    ;
  GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos)));
  while (GCLK->STATUS.bit.SYNCBUSY)
    ;

  RTCdisable();

  RTCreset();

  tmp_reg |= RTC_MODE2_CTRL_MODE_CLOCK; // set clock operating mode
  tmp_reg |= RTC_MODE2_CTRL_PRESCALER_DIV1024; // set prescaler to 1024 for MODE2
  tmp_reg &= ~RTC_MODE2_CTRL_MATCHCLR; // disable clear on match
  
  //According to the datasheet RTC_MODE2_CTRL_CLKREP = 0 for 24h
  tmp_reg &= ~RTC_MODE2_CTRL_CLKREP; // 24h time representation

  RTC->MODE2.READREQ.reg &= ~RTC_READREQ_RCONT; // disable continuously mode

  RTC->MODE2.CTRL.reg = tmp_reg;
  while (RTCisSyncing())
    ;

  NVIC_EnableIRQ(RTC_IRQn); // enable RTC interrupt 
  NVIC_SetPriority(RTC_IRQn, 0x00);

  RTC->MODE2.INTENSET.reg |= RTC_MODE2_INTENSET_ALARM0; // enable alarm interrupt
  RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = MATCH_OFF; // default alarm match is off (disabled)
  
  while (RTCisSyncing())
    ;

  RTCenable();
  RTCresetRemove();

  // If desired and valid, restore the time value
  if ((!resetTime) && (validTime)) {
    RTC->MODE2.CLOCK.reg = oldTime.reg;
    while (RTCisSyncing())
      ;
  }

  _configured = true;
}

Sunday, August 7, 2016

Programming Arduino Zero, MKR1000, or any SAMD21 Arduino via Registers

In this video we look at how to program registers on Arduino's based on the SAMD21 MCU, like the Zero and MKR1000. To do this we will leverage the Atmel ASF data structures. This is a great way to access capabilities in the SAMD21 that are not covered by Arduino libraries. The link to the ASF documentation is http://asf.atmel.com/docs/3.16.0/samd21/html/annotated.html


Arduino code from video:
bool tog = false; //variable to toggle digital output
int cDiv = 1; //varible to hold clock divider
int count = 0; //variable to track when to switch clock freq

void setup() {
  PM->CPUSEL.reg = PM_CPUSEL_CPUDIV(1); //Sets CPU frequency: power management struct, CPUSEL union, register variable
  pinMode(3, OUTPUT); //set D3 to output
}

void loop() {
  if(tog) tog = false; //if tog is true turn it false
  else tog =true;
  digitalWrite(3,tog); //set digital output
  count++;  
  if(count > 6) { //check if it is time to change clock frequency
    if(cDiv == 1) cDiv = 4;
    else cDiv = 1;
    PM->CPUSEL.reg = PM_CPUSEL_CPUDIV(cDiv); //PM_CPUSEL_CPUDIV_DIV4_Val
    count = 0;
  }
}

Saturday, July 2, 2016

Advanced PWM for Arduino Zero or any Atmel SAMD21 Based Arduino Board


The Arduino PWM library leaves a lot to be desired since it really only scratches the surface on PWM capabilities built into today's MCUs. In this video we look at how to unlock some of the more advanced PWM features on any Arduino board based on the Atmel SAMD21 32 bit ARM MCU.



Click here to access the Atmel programming API from the video

Arduino code from video***********************************************************
//This was used for ForceTronics YouTube tutorial on generating PWM signals with SAMD21 based Arduinos
//This code is public domain and can be used by anyone at their own risk
//Some of this code was leveraged from MartinL on Arduino Forum http://forum.arduino.cc/index.php?topic=346731.5;wap2

//sets the period of the PWM signal, PWM period = wPer / gen clock rate 
volatile unsigned char wPer = 255;
//This variable is to generate the duty cycle of the PWM signal 0.5 --> 50%
volatile float pWMDC = .5;
//selects the gen clock for setting the waveform generator clock or sample rate
const unsigned char gClock = 4;
//sets the divide factor for the gen clk, 48MHz / 3 = 16MHz
const unsigned char dFactor = 3;

void setup() 

  pinMode(3, OUTPUT);
  analogReadResolution(8); //set the ADC resolution to match the PWM max resolution (0 to 255)
  
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(dFactor) |          // Divide the main clock down by some factor to get generic clock
                    GCLK_GENDIV_ID(gClock);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(gClock);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the digital pin. Note commented out line is pin D7, other is D3
 // PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
  
   //Connect the TCC0 timer to digital output - port pins are paired odd PMUO and even PMUXE (note D7 is commented out and D3 is not)
  // PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F; 
  PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;

  // Feed GCLK4 to TCC0 and TCC1
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC0 and TCC1
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  //Set for Single slope PWM operation: timers or counters count up to TOP value and then repeat
  REG_TCC1_WAVE |= TCC_WAVE_WAVEGEN_NPWM;       // Reverse the output polarity on all TCC0 outputs
                   //TCC_WAVE_POL(0xF)      //this line inverts the output waveform
                   //TCC_WAVE_WAVEGEN_DSBOTH;    // Setup dual slope PWM on TCC0
  while (TCC1->SYNCBUSY.bit.WAVE);               // Wait for synchronization

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation: 
  REG_TCC1_PER = wPer;         // This sets the rate or frequency of PWM signal. 
  while (TCC1->SYNCBUSY.bit.PER);                // Wait for synchronization
  
  // Set the PWM signal to output 50% duty cycle initially (0.5 x 255)
  REG_TCC1_CC1 = pWMDC*wPer;        
  while (TCC1->SYNCBUSY.bit.CC1);                // Wait for synchronization

  //enable interrupts
  REG_TCC1_INTENSET = TCC_INTENSET_OVF; //Set up interrupt at TOP of each PWM cycle
  enable_interrupts(); //enable in NVIC
  
  // Set prescaler and enable the outputs
  REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC1->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() { 

  //Put main code here
 }

//This function sets the interrupts priority to highest and then enables the PWM interrupt
void enable_interrupts() {
  NVIC_SetPriority(TCC1_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority
  NVIC_EnableIRQ(TCC1_IRQn);
}

//This ISR is called at the end or TOP of each PWM cycle
void TCC1_Handler() {
    REG_TCC1_PER = analogRead(A1); //Get period from A1
    while (TCC1->SYNCBUSY.bit.PER);
    REG_TCC1_CC1 = (analogRead(A0)/255.0)*analogRead(A1); //calculate PWM using A0 reading and A1 current state
    while (TCC1->SYNCBUSY.bit.CC1);
    REG_TCC0_INTFLAG = TC_INTFLAG_OVF; //Need to reset interrupt
}

Wednesday, January 27, 2016

Utilizing the Arduino ADC Internal Reference

In this video we look at the ADC internal reference in an Arduino, why / when would you use it, how to ensure you get good measurement accuracy when using it, and how to use it to check your battery voltage.



**********************Arduino Code from the video****************************
float iREF = 1.08; //internal reference cal factor

void setup() {
  // put your setup code here, to run once:
  analogReference(EXTERNAL);
  //burn some ADC readings after reference change
  for(int i=0; i<8; i++) analogRead(A0);
  Serial.begin(57600);
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(3000);
  Serial.print("Measured battery voltage is: ");
  Serial.println(fReadVcc());
  Serial.println();
}

//This function uses the known internal reference value of the 328p (~1.1V) to calculate the VCC value which comes from a battery
//This was leveraged from a great tutorial found at https://code.google.com/p/tinkerit/wiki/SecretVoltmeter?pageId=110412607001051797704
float fReadVcc() {
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(3); //delay for 3 milliseconds
  ADCSRA |= _BV(ADSC); // Start ADC conversion
  while (bit_is_set(ADCSRA,ADSC)); //wait until conversion is complete
  int result = ADCL; //get first half of result
  result |= ADCH<<8; //get rest of the result
  float batVolt = (iREF / result)*1024; //Use the known iRef to calculate battery voltage
  return batVolt;
}

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;
}