NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Potentiometer to control Duty Cycle

March 03, 2012
by RevMoses
RevMoses's Avatar

I'll attempt to stick all info in this post. Purpose of this was to control duty cycle of a wave with a pot using the Microcontroller.

The circuit: Alt Text

The c code:

// servosquirter.c
// for NerdKits with ATmega168
// mrobbins@mit.edu
//http://www.nerdkits.com/videos/servosquirter/

#define F_CPU 14745600

#include <stdio.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PB3 - pump control
// PB2 - servo signal (OC1B)
//~~~~~~~~~~~~~~~~~~~~~~~PWM Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void pwm_set(uint16_t x) 
{
  OCR1B = x;
}

#define PWM_MIN 1
#define PWM_MAX 66
#define PWM_START 33
void pwm_init() {
  // setup Timer1 for Fast PWM mode, 16-bit
  // COM1B1 -- for non-inverting output
  // WGM13, WGM12, WGM11, WGM10 -- for Fast PWM with OCR1A as TOP value
  // CS11 -- for CLK/8 prescaling

  // I want to drive my DC motor at 28kHz
  // F=1/T
  // this means it will repeat every 35 microseconds (period)
  // each count is 8/14745600 = 0.5425us (speed)
  // Therefore: MagicNumber=period/speed=65.8 ...use 66

  OCR1A = 66;   // sets PWM to repeat pulse every 35 microseconds
  pwm_set(PWM_START);
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

  // so 1.0ms = 1843.2
  //    1.5ms = 2764.8
  //    2.0ms = 3686.4
  //   20.0ms = 36864
}

//~~~~~~~~~~~~~~~~~~~~~~~~~END PWM Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//~~~~~~~~~~~~~~~~~~~~~ADC Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void adc0_init()  //void method means your going to have the chip do something 
{
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  //selects ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() 
{
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) 
  {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);

  return result;
}

double sampleToVolts(uint16_t sample) //changing a 16 bit to a double
{
  // conversion ratio in degrees/STEP:
  // (5000 mV / 1024 steps) * (1 degree / 1000mV)
  //    ^^^^^^^^^^^            ^^^^^^^^^^
  //     from ADC               Pot Logic
  return sample * (5000.0 / 1024.0 / 1000);  
}

//~~~~~~~~~~~~~~~~~~~~~~~~~END ADC Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

int main() {
  //~~~~~~~~~~~~~~~~~~~~~PWM part MAIN~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // set PB2 as output
  DDRB |= (1<<PB2);

  // init PWM
  pwm_init();

  uint16_t HighTime = PWM_START;

  //~~~~~~~~~~~~~~~~~~~~~~END PWM part MAIN~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  //~~~~~~~~~~~~~~~~~~~~~ADC part MAIN~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  // holder variables for first ADC
  uint16_t last_sample0 = 0; //defining last_sample as an unsigned integer that is 16 bits long with a value of 0 (similar to initialization)
  double this_temp0;            //definig this_temp as a double
  double temp_avg0;         //defining temp_avg as a doulbe
  uint8_t i;                //defining i as an unsigned integer that is 16 bits long

 //~~~~~~~~~~~~~~~~~~END ADC part MAIN~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  uint16_t DutyCycle;

  while(1) {
    //~~~~~~~~~~~~~~~~~~ State Machine Now Begins~~~~~~~~~~~~~~~~~~~~~
    // start up the Analog to Digital Converter
    adc0_init();
    // take 100 samples and average them!
    temp_avg0 = 0.0;
    for(i=0; i<100; i++) 
    {
      last_sample0 = adc_read();
      this_temp0 = sampleToVolts(last_sample0);

      // add this contribution to the average
      temp_avg0 = temp_avg0 + this_temp0/100.0;
    }

    //putting the pot values to work
    //we know the pot can go from 0 to 4.99 volts
    //we want this to change the Duty Cycle of the wave (or high duration)
    //for the purposes of this excercise, I will make the 0 to 4.99 give the full swing
    //from 0 to 66 therefore, we can write
    // (66/4.99) = 13
    // we now have our magic number 13
    //we now have the proportion equation in the code directly below

    HighTime=temp_avg0 * 13;
    pwm_set(HighTime);

    DutyCycle = (HighTime * 100)/66; 
    //(HighTime/66)*(100);...i have no clue why i couldn't write the formula that way in the code

    // Print the Duty Cycle to the serial port
    printf_P(PSTR("%d\r\n"), DutyCycle);
  }

  return 0;
}

The Python Code:

import serial

serial = serial.Serial("COM4", 115200, timeout=1)
i=1
# while (i<200):
while (1):
    #uses the serial port
    data = serial.readline()
    print data

#    i=i+1
# http://www.nerdkits.com/forum/thread/608/
# http://www.nerdkits.com/forum/thread/1348/
serial.close()

The Results...it worked :) Alt Text Alt Text Alt Text

March 03, 2012
by RevMoses
RevMoses's Avatar

Next step is to 1) get a motor with a built in speed sensor. 2) get a frequency to voltage converter 3) determine PID control code for micro 4) close loop (motor should spin at a desired speed regardless of mechanical forces acting on the shaft)

Post a Reply

Please log in to post a reply.

Did you know that an analog comparator can tell when one voltage input crosses another? Learn more...