Electronic – Can’t get PWM to work on a PIC12F683 using MPLAB XC8

cpicpwmxc8

I'm trying to make PWM to work on the PIC12F683. According to my calculations I should get an 8 bit 20kHz PWM at GPIO2, but that does not happen. Instead I get a 5kHz "weird" PWM signal such that when I set the duty to be 255, which should be maximum, I get this 5kHz wave. What could the problem be?

EDIT: I am following this procedure which is written in the PIC12F683 datasheet, but I am either not doing it right or there's something else I need to do.

This is the code:

  /*
   * File:   main.c
   * Author: Calin
   *
   * Created on November 9, 2015, 11:10 PM
   */


  #define _XTAL_FREQ 8000000
  #include <xc.h>
  #include <PIC12F683.h>
  #define CHECK_BIT(var,pos) ((var) & (1<<(pos)))

  /* Prototypes *****************************************************************/
  long calculatePower(void);
  unsigned int readVoltage(void);
  unsigned int readCurrent(void);
  void PWM_setup(void);
  void PWM_set_duty(int);
  void interrupt ISR(void);
  /******************************************************************************/

  void main(void) {
  // Select 8Mhz internal clock
  OSCCON |= 0b01110001;
  // Configure GP0 and GP1 as analog inputs
  TRISIO  = 0b00000011; //input
  ANSEL   = 0b00000011; // clock = 1 meg and analog configure
  INTCONbits.PEIE = 1;
  INTCONbits.GIE = 1;
  INTCONbits.T0IE = 1;
  PIE1bits.CCP1IE = 1;
  PIE1bits.TMR2IE = 1;
  PWM_setup();

  PWM_set_duty(255);
  while (1){
      //long power = calculatePower();
  }
  return;
  }

  void PWM_setup(){
  TRISIO &= 0b11111011; // make sure GP2 is OUTPUT
  PR2 = 0x65;
  CCP1CON = 0b00001100; // active high PWM
  PIR1bits.TMR2IF = 0;
  T2CONbits.T2CKPS = 0x1; // set prescaler to 1
  T2CONbits.TMR2ON = 1; // enable Timer 2 and therefor PWM
  }

  void PWM_set_duty(int duty_cycle){
  // Sets the PWM duty cycle by setting
  // the 2 LSB's in DCB and the 8 MSB's
  // in CCPR1L. 10 bit resolution
  CCP1CONbits.DC1B = duty_cycle;
  CCPR1L = duty_cycle >> 2;
  }

  long calculatePower(){
  return readVoltage()*readCurrent();
  }

  unsigned int readVoltage(){
  /*
   * Reads and returns the voltage at AN0
   */
  // Select channel 0 and turn on ADC
  ADCON0 = 0b10000001; // enable ADC
  ADCON0 = 0b10000011; // GO

  while (CHECK_BIT(ADCON0, 1)){
      // wait
  }

  // 10 bit ADC result
  unsigned int voltage = ADRESL | (ADRESH << 8);
  return voltage;
  }

  unsigned int readCurrent(){
  /*
   * Reads and returns the current at AN1
   */
  // Select channel 1 and turn on ADC
  ADCON0 = 0b10000101; // enable ADC & select channel
  ADCON0 = 0b10000111; // GO

  while(CHECK_BIT(ADCON0, 1)){
      // wait
  }
  unsigned int current = ADRESL | (ADRESH << 8);
  return current;
  }

  void interrupt ISR(){
  // Timer2 overflow => start a new PWM cycle
  if(PIR1bits.TMR2IF == 1){
      PIR1bits.TMR2IF = 0;
      TRISIObits.TRISIO2 = 0;
  }
  }

Best Answer

A few issues I see:

T2CONbits.T2CKPS = 0x1; // set prescaler to 1

This line is actually setting the prescaler to 4, not 1. Based on the values you have chosen for PR2, Tosc and the TMR2 prescaler I calculate a PWM frequency of 4902 Hz. If you set the prescaler to 1 you should get a frequency of ~19608 Hz. I think this is what you intended. You should write 0b00 to T2CONbits.T2CKPS instead.

Also the PWM duty cycle is set by a 10 bit value. The equation for duty cycle ratio is:

Duty Cycle Ratio = (CCPR1L:CCP1CON<5:4>) / (4*(PR2 + 1))

To achieve 100% duty cycle you need to write 408 (or 0x198), not 255.