Showing posts with label PWM. Show all posts
Showing posts with label PWM. Show all posts

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

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
}