Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Controlling PWM duty cycle with ADC

Status
Not open for further replies.

Cantafford

Member
Hello,

Quick question. I want to learn how to control a duty cycle with an analogue value that I convert to a digital one via ADC module.
In my case I'm trying to set the duty cycle of a PWM signal for a DC motor with the value converted by the ADC. I will be using a potentiometer to modify the value.

The duty cycle of my motor is a 10 bit value: CCPR1L and CCP1CON(5:4).
The value of the ADC is a 10 bit value: ADRESH and ADRESL(I will be using left-justified mode so I can get a 10 bit resolution)

I have searched some examples and to my understanding is I have to read the digital value converted by the ADC(which is CCPR1L and CCP1CON registers) and feed it to the duty cycle(ADRESH+ADRESL).

Now I'm guessing that I have to read the value from (ADRESH+ADRESL) convert it to binary then feed it to (CCPR1L and CCP1CON(5:4)). Since for the duty cycle I need only bits 5:4 of CCP1CON, how can I do to not change the others bits when I pass my desired value?

I can get the result of the ADC like this with the function: ReadADC(); provided by the compiler
Code:
ADCResult = ReadADC(); // Read the converted value
result = (ADCResult*5.0)/1024;
But then how can I store this result in the duty cycle registers?: CCPR1L and CCP1CON(5:4)
 
Last edited:
If you're using XC8 you can just use the ADRES keyword to read your 10bit result without the need to do any bit shifting. You can access CCP1CON(5:4) bits directly using CCP1CONbits.DC1B so you could possibly do something like this..

int x = ADRES; // Your 10bit ADC Result
CCPR1L = x >> 2;
CCP1CONbits.DC1B = x & 0x03;

I cant test this out where I'm at though... Hope it helps
 
Thanks. I got the value from the ADC and did what you told me to get the 8MSB's and 2LSB's and loaded them accordingly to get the PWM duty cycle.
However when I try to simulate in proteus the motor runs full speed no matter what the position of the potentiometer on the ADC is.

This is the proteus schematic:
1191gle.png


This is the code:
Code:
/* 
 * File:   main.c
 * Author: Paul
 *
 * Created on January 7, 2016, 11:15 AM
 */

#include <stdio.h>
#include <stdlib.h>
#include "header.h"
#include <stdio.h>
#include <stdlib.h>

unsigned int ADCResult=0;

void init_ADC1(void);

void main()
{
 OSCCON = 0x46; // set internal oscillator to 1Mhz
 TRISA = 0b00000001; // ADC(must be set as input)
 TRISB = 0b11111111; // portB as input(the switches)
 TRISC = 0b11111000; // set portC as output(RC0 = in1, RC1 = in2, RC2 = pwm)
 LATCbits.LATC0 = 1; // motor direction
 LATCbits.LATC1 = 0;
 PR2 = 0b11111001 ;
 T2CON = 0b00000100 ;
 CCP1CON = 0b00011100 ;
 while(1)
  {
   ADCResult =0;
   ConvertADC();
   while(BusyADC());              //Wait here until conversion is finished
   ADCResult = ReadADC(); 
   
   CCPR1L = ADCResult >> 2;   // duty
   DC1B1 = ADCResult & 0x02;  // cycle
   DC1B0 = ADCResult & 0x01;
  }
}
void init_ADC1(void)              //Initialize ADC
{
  /**** ADC configured for:
    * FOSC/2 as conversion clock
    * Result is right justified
    * Aquisition time of 2 AD
    * Channel 1 for sampling
    * ADC interrupt off
    * ADC reference voltage from VDD & VSS
*/
    OpenADC(ADC_FOSC_2 & ADC_LEFT_JUST & ADC_2_TAD,
            ADC_CH0 & ADC_INT_OFF & ADC_REF_VDD_VSS,
            ADC_1ANA);
}

This is the header.h
Code:
/* 
 * File:   header.h
 * Author: Paul
 *
 * Created on January 7, 2016, 11:11 AM
 */


// PIC18F2550 Configuration Bit Settings

// 'C' source line config statements

#include <xc.h>

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1L
#pragma config PLLDIV = 1       // PLL Prescaler Selection bits (No prescale (4 MHz oscillator input drives PLL directly))
#pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary Oscillator Src: /1][96 MHz PLL Src: /2])
#pragma config USBDIV = 1       // USB Clock Selection bit (used in Full-Speed USB mode only; UCFG:FSEN = 1) (USB clock source comes directly from the primary oscillator block with no postscale)

// CONFIG1H
#pragma config FOSC = INTOSCIO_EC// Oscillator Selection bits (Internal oscillator, port function on RA6, EC used by USB (INTIO))
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRT = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOR = OFF        // Brown-out Reset Enable bits (Brown-out Reset disabled in hardware and software)
#pragma config BORV = 3         // Brown-out Reset Voltage bits (Minimum setting)
#pragma config VREGEN = OFF     // USB Voltage Regulator Enable bit (USB voltage regulator disabled)

// CONFIG2H
#pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = ON      // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = OFF     // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset)
#pragma config LPT1OSC = OFF    // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = OFF      // MCLR Pin Enable bit (RE3 input pin enabled; MCLR pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON         // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
#pragma config CP2 = OFF        // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
#pragma config CP3 = OFF        // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
#pragma config WRT2 = OFF       // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
#pragma config WRT3 = OFF       // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)

#define _XTAL_FREQ 8000000

Any ideea what am I missing? Thanks.
 
Then..

If the PWM is above PR2, nothing will happen.. You need to set right justification

Ok I have set it as right-justified so now my ADC result(say value 1011001101) is in this form:
ADRESH: 0000010
ADRESL: 11001101

And so to my understanding I need: (bits <1:0> from ADRESH + bits <7:3> from ADRESL) in CCPR1L register and (bits <1:0> from ADRESL) in DC1B1 and DC1B2 registers.
Also to ensure that PERIOD>Duty cycle from their formulas: PERIOD = [(PR2+1)*4*TOSC*TMR2prescale] and DC = CCPR1L:CCP1CON(5:4) (which is 1023 maximum in decimal if I'm not mistaken) I have done some calculations and I concluded that for the PERIOD always be > than DC PR2 has to be bigger than 254 decimal value which is 11111110 in binary so I put PR2 to 11111111 in the code.

So I edited the code like this(+ the PR2 value): I may have missed something I have not worked with bitwise operations too much.

Code:
CCPR1L = (ADCResult << 6) | (ADRESL & 0xFC);   
DC1B1 = ADCResult & 0x02;  // 
DC1B0 = ADCResult & 0x01;

The motor works the same. Goes full speed no matter the value that the ADC converts :(
 
Here's mine..

With RIGHT justification it's quite simple.......
C:
CCPR1L = ADCresult >> 2;               // this converts ADC result to 8 bits..
CCP1CON |= ((ADCresult  & 0x03 ) << 4 ) ;  // this shifts the ADC two lower bits then get rid of all the other bits..
 
Ahhhh!! One of lifes little hiccups..... You don't actually initialize the ADC...

Its defined.... Its written... But you don't call it!!
I'm an idiot :D. Works fine now like this:

Code:
while(1)
  {
   ADCResult =0;
   ConvertADC();
   while(BusyADC());              //Wait here until conversion is finished
   ADCResult = ReadADC(); 
   
   CCPR1L = ADCResult >> 2;   // duty
   DC1B1 = ADCResult & 0x02;  // cycle
   DC1B0 = ADCResult & 0x01;
  }

Thanks
 
Looks like i missed the end of the party.. I literally just had this written out :joyful:

Code:
#include <xc.h>

// CONFIGURATION BITS
#pragma config OSC =  INTIO7  // Oscillator Selection bits (Internal oscillator block, CLKO function on RA6, port function on RA7)
#pragma config WDT =  OFF  // Watchdog Timer is off, set by SWDTEN in software
#pragma config PBADEN = OFF  // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset)
#pragma config MCLRE =  OFF  // MCLR (Pin 1 / RE3) set as input, internally tied high
#pragma config LVP =  OFF  // Low voltage programming pin off

void setup()
{
  OSCCONbits.IRCF = 7;  // 8Mhz IntOSC

  // ADC Config
  ADCON2bits.ADCS  = 1;  //Selecting the clk division factor = FOSC/8
  ADCON2bits.ADFM = 1;  //Result right justified
  ADCON2bits.ACQT = 7;  //Acq time 8Tad
  ADCON0bits.ADON = 1;  //Turns on ADC module
  ADCON1bits.PCFG = 0xA;  //Port A analogue

  // Port Config
  TRISCbits.RC2 = 0;  // Make CPP1 pin output
  TRISAbits.RA0 = 1;  // Make AN0 pin input
 
  // CCP & TMR2 Config
  PR2 = 0xFF;  // Load Max Value to PR2
  CCP1CON = 0x2C;  // PWM mode, 2 LSbs of Duty cycle = 10
  T2CONbits.TMR2ON = 1; // Enable TMR2
}

int read_ADC(int channel)
{
  ADCON0bits.CHS = channel;  // Select Channel
  ADCON0bits.GO = 1;  // Starts ADC conversion
  while (ADCON0bits.nDONE);  // Wait till ADC conversion is over
  return (ADRESH << 8) + ADRESL ;  // Merge the MSB and LSB
}

void main(void)
 {
  setup();
  int x = read_ADC(0);  // Read Pin AN0

  while (1)
  {
  x = read_ADC(0);

  CCPR1L = x >> 2;
  CCP1CONbits.DC1B = x & 0b11;
  }
 }
 
Looks like i missed the end of the party.. I literally just had this written out :joyful:

Code:
#include <xc.h>

// CONFIGURATION BITS
#pragma config OSC =  INTIO7  // Oscillator Selection bits (Internal oscillator block, CLKO function on RA6, port function on RA7)
#pragma config WDT =  OFF  // Watchdog Timer is off, set by SWDTEN in software
#pragma config PBADEN = OFF  // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset)
#pragma config MCLRE =  OFF  // MCLR (Pin 1 / RE3) set as input, internally tied high
#pragma config LVP =  OFF  // Low voltage programming pin off

void setup()
{
  OSCCONbits.IRCF = 7;  // 8Mhz IntOSC

  // ADC Config
  ADCON2bits.ADCS  = 1;  //Selecting the clk division factor = FOSC/8
  ADCON2bits.ADFM = 1;  //Result right justified
  ADCON2bits.ACQT = 7;  //Acq time 8Tad
  ADCON0bits.ADON = 1;  //Turns on ADC module
  ADCON1bits.PCFG = 0xA;  //Port A analogue

  // Port Config
  TRISCbits.RC2 = 0;  // Make CPP1 pin output
  TRISAbits.RA0 = 1;  // Make AN0 pin input

  // CCP & TMR2 Config
  PR2 = 0xFF;  // Load Max Value to PR2
  CCP1CON = 0x2C;  // PWM mode, 2 LSbs of Duty cycle = 10
  T2CONbits.TMR2ON = 1; // Enable TMR2
}

int read_ADC(int channel)
{
  ADCON0bits.CHS = channel;  // Select Channel
  ADCON0bits.GO = 1;  // Starts ADC conversion
  while (ADCON0bits.nDONE);  // Wait till ADC conversion is over
  return (ADRESH << 8) + ADRESL ;  // Merge the MSB and LSB
}

void main(void)
{
  setup();
  int x = read_ADC(0);  // Read Pin AN0

  while (1)
  {
  x = read_ADC(0);

  CCPR1L = x >> 2;
  CCP1CONbits.DC1B = x & 0b11;
  }
}
Thanks a lot but yeah I solved it already with Ian's help :D
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top