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 Change Frequency

March 04, 2012
by RevMoses
RevMoses's Avatar

Circuit Alt Text

The C code

// FreqGen.c
// for NerdKits with ATmega168

#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:
//
// PB2 - freq signal (OC1B)

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

void pwm_Freqset(uint16_t y) 
{
  OCR1A = y;
}

//this is how to define a global variable..i think
#define PWM_START 33
#define PWM_Freq 66 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start PWM Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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 and T=1/F
  // this means it will repeat every 35 microseconds (period)
  // each count is 8/14745600 = 0.5425us (speed)

  pwm_Freqset(PWM_Freq);
  pwm_set(PWM_START);
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11);

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~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() {
  // init LCD
  //lcd_init();
  //FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  //lcd_write_string(PSTR(" NerdKits ServoSquirter "));

  // 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);
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start PWM part MAIN~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // init PWM
  pwm_init();

  uint16_t HighDur = PWM_START;
  uint16_t freqNum = PWM_Freq;
  double ActualFreq;
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~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~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

    freqNum=temp_avg0*1000;
    //for better adjustment use the following multipliers in the above
    //10000 for 50Hz to 500Hz
    //1000 for 500Hz to 3kHz
    //100 for 3kHz to 30kHz
    //10 for 30kHz to 368kHz
    //5k pot was used

    HighDur=freqNum/2; //2 can be changed to give a different duty cycle 2 gives about 50% duty

    pwm_Freqset(freqNum);
    pwm_set(HighDur);

    //we can write Freq=(1/FreqNum)(14745600 / 8.0)
    //to see where this came from, refer to pwm_init comments
    ActualFreq=(14745600 / 8.0 /(double) freqNum);
    //ActualFreq=((14745600/8)*(1/(double) freqNum));

    // Print the Frequency, freqNum and Pot Voltage to the serial port.
    printf_P(PSTR("Frequency %.2f value of freqNum is %d Pot Voltage %.4f \r\n"), ActualFreq, freqNum, temp_avg0);
  }

  return 0;
}

python code is same as potentiometer to Control Duty Cycle project

Results...it works :)

Alt Text Alt Text Alt Text

March 04, 2012
by RevMoses
RevMoses's Avatar

Next step: Write code to turn ADC1 into a frequency to number converter. Wire PIN 16 on micro to ADC1 ADC0 will continue to be the control pin for frequency(pot will stay here).

Post a Reply

Please log in to post a reply.

Did you know that signed numbers need to be sign-extended when chaging variable sizes? Learn more...