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

Wednesday, May 23, 2018

Building an RGB LED Display Part 1

In video series we build a scalable RGB LED Display or matrix and control it with Arduino. In part 1 we take a close look at the magic of the WS2812B RGB IC and Arduino options for controlling a large display of them.



//***************Arduino Code from Video*************************************
/* This Arduino sketch was made for a tutorial entitled "Building a Fun and Scalable RGB LED Display Part 1" for the ForceTronics YouTube Channel
 *  This sketch was leveraged from an example with the Adafruit NeoPixel library.This sketch was used to compare performance of an Arduino UNO versus 
 *  the Altrium Sno FPGA based Arduino for controlling the ADafruit NeoMatrix. This sketch randomly changes each pixel's brightness and color in a timed
 *  loop.
 *  This code is free for others to use and modify at their own risk
 */

#include <Adafruit_NeoPixel.h> //uncomment this library when using an AVR based Arduino
//#include <XLR8NeoPixel.h> //uncomment this library when using the SNO

// Defines single pin to control NeoMatrix from Arduino
#define PIN            6

//defines number of pixels in the NeoMatrix that was used
#define NUMPIXELS      64

// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use
//We can also set the comm speed and settings related to the how the NeoMatrix is configured
//For more information on how to use arguments for this function go to: https://learn.adafruit.com/adafruit-neopixel-uberguide/neomatrix-library
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); //decomment this function if you are using an AVR based Arduino
//XLR8NeoPixel pixels = XLR8NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); //uncomment this function if you are using the SNO FPGA Arduino

int delayval = 10; // delay for 10 msec

void setup() {
  pixels.begin(); // This initializes the NeoPixel or XLR8NeoPixel library.
}

void loop() {

  // For a set of NeoPixels the first NeoPixel is 0, second is 1, all the way up to the count of pixels minus one.
  //This loops through all the pixels (1 to 64) and then starts back at 1
  for(int i=0;i<NUMPIXELS;i++){
    //This function sets the brightness of a pixel. Brightness can be 0 to 255. Using random() to generate psuedo random value between 0 and 255
    pixels.setBrightness(random(0,255));
    //This function sets the color scheme of an RGB LED, so values are red 0 to 255, green 0 to 255, blue 0 to 255.
    //'i' is the pixel number that is being set and the other arguments are the RGB values which are set using psuedo random values between 0 and 255
    pixels.setPixelColor(i, pixels.Color(random(0,255),random(0,255),random(0,255))); // Moderately bright green color.
    //This function updates the pixels
    pixels.show(); // This sends the updated pixel color to the hardware.
    delay(delayval); // Delay for a period of time (in milliseconds).
  }
}

Monday, April 9, 2018

Ground Considerations for PCB Layout of Mixed Signal Designs Part 2

In this video series we look at considerations when laying out a ground plane on a PCB when working with a mixed signal designs. If you want to ensure you are making accurate noise free ADC measurements in your design this video series is a must watch.




Reference material:
Partitioning and Layout of a Mixed-Signal PCB http://www.hottconsultants.com/pdf_files/june2001pcd_mixedsignal.pdf

Grounding in mixed-signal systems demystified, Part 1 and 2
http://www.ti.com/lit/an/slyt499/slyt499.pdf
http://www.ti.com/lit/an/slyt512/slyt512.pdf

Tuesday, April 3, 2018

Ground Considerations for PCB Layout of Mixed Signal Designs Part 1

In this video we look at considerations when laying out a ground plane on a PCB when working with a mixed signal designs. If you want to ensure you are making accurate noise free ADC measurements in your design this video series is a must watch.



Partitioning and Layout of a Mixed-Signal PCB http://www.hottconsultants.com/pdf_files/june2001pcd_mixedsignal.pdf

Saturday, March 24, 2018

How to Calculate the Thermal Range of a Linear Regulator

In this video we look at how to determine whether an IC is going to work in a design from a thermal standpoint or is it going to get too hot and break / burn up. In the video we look at the popular LM317 Linear Regulator as an example.


Here is a link to the app note referenced in the video: http://www.ti.com/lit/an/spra953c/spra953c.pdf

Sunday, February 11, 2018

Converting an Arduino PWM Output to a DAC Output

In this video we look at how to convert a PWM output or signal to a analog or DAC signal.



To access the low pass filter tutorial mentioned in the video got to: https://youtu.be/gW5oF8vcYb8


//*************Arduino code from video***************************
/*This code was made for a vidoe tutorial on the ForceTronics YouTube Channel called
 * Converting an Arduino PWM Output to a DAC Output. This code is free to use and 
 * modify at your own risk
 */

uint8_t pVal = 127; //PWM value 
const float pi2 = 6.28; //Pie times 2, for building sinewave
const int samples = 100; //number of samples for Sinewave. This value also affects frequency
int WavSamples[samples]; //Array for storing sine wave points
int count = 0; //tracks where we are in sine wave array

void setup() {
// Serial.begin(115200); //for debugging
  pinMode(10, OUTPUT); //pin used for analog voltage value
  pinMode(4,OUTPUT); //pin used to fake PWM for sinewave
  setPwmFrequency(10,1); //function for setting PWM frequency
  analogWrite(10,127); //set duty cycle for PWM

  float in, out; //used for building sine wave
  
  for (int i=0;i<samples;i++) //loop to build sinewave
  {
    in = pi2*(1/(float)samples)*(float)i; //calculate value for sine function
    WavSamples[i] = (int)(sin(in)*127.5 + 127.5); //get sinewave value and store in array
   // Serial.println(WavSamples[i]); //for debugging
  }
}

void loop() {
  if(count > samples) count = 0; //reset the count once we are through array
  bitBangPWM(WavSamples[count],4); //function for turning sinewave into "fake" PWM signal
  count++; //increment position in array
}

//Function to bit bang a PWM signal (we are using it for the sinewave)
//input are PWM high value for one cycle and digital pin for Arduino
//period variable determines frequency along with number of signal samples
//For this example a period of 1000 (which is 1 millisecond) times 100 samples is 100 milli second period so 10Hz
void bitBangPWM(unsigned long on, int pin) {
  int period = 1000; //period in micro seconds
  on = map(on, 0, 255, 0, period); //map function that converts from 8 bits to range of period in micro sec
 // Serial.println(on); //debug check
  unsigned long start = micros(); //get current value of micro second timer as start time
  digitalWrite(pin,HIGH); //set digital pin to high
  while((start+on) > micros()); //wait for a time based on PWM duty cycle
  start = micros(); 
  digitalWrite(pin,LOW); //set digital pin to low
  while((start+(period - on)) > micros()); //wait for a time based on PWM duty cycle
}

/**
 * https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
 * Divides a given PWM pin frequency by a divisor.
 * 
 * The resulting frequency is equal to the base frequency divided by
 * the given divisor:
 *   - Base frequencies:
 *      o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
 *      o The base frequency for pins 5 and 6 is 62500 Hz.
 *   - Divisors:
 *      o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
 *        256, and 1024.
 *      o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
 *        128, 256, and 1024.
 * 
 * PWM frequencies are tied together in pairs of pins. If one in a
 * pair is changed, the other is also changed to match:
 *   - Pins 5 and 6 are paired on timer0
 *   - Pins 9 and 10 are paired on timer1
 *   - Pins 3 and 11 are paired on timer2
 * 
 * Note that this function will have side effects on anything else
 * that uses timers:
 *   - Changes on pins 3, 5, 6, or 11 may cause the delay() and
 *     millis() functions to stop working. Other timing-related
 *     functions may also be affected.
 *   - Changes on pins 9 or 10 will cause the Servo library to function
 *     incorrectly.
 * 
 * Thanks to macegr of the Arduino forums for his documentation of the
 * PWM frequency divisors. His post can be viewed at:
 *   http://forum.arduino.cc/index.php?topic=16612#msg121031
 */
void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x07; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

Wednesday, January 31, 2018

Designing a Driver Circuit for a Bipolar Stepper Motor Part 2

In this video we design a low cost driver circuit for a four wire bipolar stepper motor using two H bridges.


Link to Eagle files on GitHub: https://github.com/ForceTronics/Bipolar_Stepper_Driver_Circuit/tree/master