Bottle
Bottle

Reputation: 23

PWM settings in PIC24FJ64GA002

Good day, I am working with a PIC24FJ64GA002 microcontroller and am struggling to get the PWM working for a servo I want to use. My code is as follows:

// PIC24FJ64GA002 Configuration Bit Settings

// 'C' source line config statements

// CONFIG2
#pragma config POSCMOD = NONE           // Primary Oscillator Select (Primary oscillator disabled)
#pragma config I2C1SEL = PRI            // I2C1 Pin Location Select (Use default SCL1/SDA1 pins)
#pragma config IOL1WAY = ON             // IOLOCK Protection (Once IOLOCK is set, cannot be changed)
#pragma config OSCIOFNC = OFF           // Primary Oscillator Output Function (OSC2/CLKO/RC15 functions as CLKO (FOSC/2))
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor (Clock switching and Fail-Safe Clock Monitor are disabled)
#pragma config FNOSC = FRC              // Oscillator Select (Fast RC Oscillator (FRC))
#pragma config SOSCSEL = SOSC           // Sec Oscillator Select (Default Secondary Oscillator (SOSC))
#pragma config WUTSEL = LEG             // Wake-up timer Select (Legacy Wake-up Timer)
#pragma config IESO = ON                // Internal External Switch Over Mode (IESO mode (Two-Speed Start-up) enabled)

// CONFIG1
#pragma config WDTPS = PS32768          // Watchdog Timer Postscaler (1:32,768)
#pragma config FWPSA = PR128            // WDT Prescaler (Prescaler ratio of 1:128)
#pragma config WINDIS = ON              // Watchdog Timer Window (Standard Watchdog Timer enabled,(Windowed-mode is disabled))
#pragma config FWDTEN = ON              // Watchdog Timer Enable (Watchdog Timer is enabled)
#pragma config ICS = PGx1               // Comm Channel Select (Emulator EMUC1/EMUD1 pins are shared with PGC1/PGD1)
#pragma config GWRP = OFF               // General Code Segment Write Protect (Writes to program memory are allowed)
#pragma config GCP = OFF                // General Code Segment Code Protect (Code protection is disabled)
#pragma config JTAGEN = OFF             // JTAG Port Enable (JTAG port is disabled)

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

/*
 * File:   34660046LAB2.c
 * Author: leone
 *
 * Created on 06 September 2021, 1:27 PM
 */


   

 

   

 #include "p24FJ64GA002.h"

        #include "xc.h"

        #define _LATR15 OC1R
        
        
        
        
        int main(void) {
        
            
            T2CON = 0x8010;
            TMR2=0;
            PR2=9999;
            
            _T2IP=4;  //Default priority value
            _T2IF=0;  //Clears interrupt flag before interrupt
            _T2IE=1;  //Enables interrupt
            
            OC1CONbits.OC  = 0;        // Output compare channel is disabled
        OC1R           = 0x1388 ; // Initialize Compare Register1 with 50% duty cycle
        
        OC1CONbits.OCSIDL   = 0;     // Output capture will continue to operate in CPU Idle mode
        OC1CONbits.OCFLT    = 0;     // No PWM Fault condition has occurred (this bit is only used when OCM<2:0> = 111)
        OC1CONbits.OCTSEL   = 0;     // Timer2 is the clock source for output Compare
        OC1CONbits.OCM      = 0x6;   // PWM mode on OC, Fault pin disabled
            
            
            
            TRISBbits.TRISB15=0;
           _LATB15=0;
            while(1)
            {
                if(TMR2>OC1R)
                {
                    _LATB15=1;
                }
                else
                {
                    _LATB15=0;
                }
            }
            return 0;
        }

I programmed the T1CON to have a period of 2 ms, and the OC1R to have half that period, which should lead to a duty cycle of 50%. I am using the FRC oscillator (8MHz) and my pre-scaler values were <0,1>. I understand the it's the OC1R pin that gives the period the cycle is high, but in the datasheets they refer to it as a pin, however they don't give what pin it is (i.e. A0,A1,R15 etc.) There is also very little example code I could find explaining the how to code this well. If anyone is a PIC expert of some kind help would be very much appreciated! The datasheet for the MCU can be downloaded at https://www.microchip.com/en-us/product/PIC24FJ64GA002.

Upvotes: 1

Views: 1829

Answers (2)

Dan1138
Dan1138

Reputation: 1225

This is a complete application that builds with MPLABX v5.50 and XC16 v1.70:

/*
 * File:   main.c
 * Author: dan1138
 * Target: PIC24FJ64GA002
 * Compiler: XC16 v1.70
 * IDE: MPLABX v5.50
 *
 * Created on October 8, 2021, 1:12 PM
 *
 *                                 PIC24FJ64GA002
 *                  +-------------------:_:-------------------+
 *      ICD_VPP --> :  1 MCLR                         VDD  28 : <-- 3v3
 *              < > :  2 RA0/AN0                      VSS  27 : <-- GND
 *              < > :  3 RA1/AN1             AN9/RP15/RB15 26 : < > 
 *      ICD_PGD < > :  4 RB0/PGD1/AN2        AN6/RP14/RB14 25 : < > 
 *      ICD_PGC < > :  5 RB1/PGC1/AN3        AN7/RP13/RB13 24 : < > 
 *      PWM/OC1 < > :  6 RB2/RP2/SDA2/AN4    AN8/RP12/RB12 23 : < > 
 *              < > :  7 RB3/RP3/SCL2/AN5        RP11/RB11 22 : <*> 
 *              - > :  8 VSS                     RP10/RB10 21 : <*> 
 *              < > :  9 RA2/OSCI                     VCAP 20 : <-- 10uF
 *              < > : 10 RA3/OSCO                     VSS  19 : <-- GND
 *              < > : 11 RB4/RP4             SDA1/RP9/RB9  18 : <*> 
 *              < > : 12 RA4                 SCL1/RP8/RB8  17 : <*> 
 *          3v3 --> : 13 VDD                      RP7/RB7  16 : <*>
 *              <*> : 14 RB5/RP5/PGD3        PGC3/RP6/RB6  15 : <*> 
 *                  +-----------------------------------------+
 *                                    DIP-28
 *               * Indicates 5.0 volt tolerant input pins.
 *
 * Description:
 *  Initialize the controller to use a system oscillator of 8MHz from the on chip Fast RC oscillator.
 *  Setup the OC1 function to provide a 4KHz square wave output on GPIO pin RB2.
 * 
 * Notes:
 * See: https://stackoverflow.com/questions/69453833/pwm-settings-in-pic24fj64ga002
 * 
 */

#pragma config POSCMOD = NONE, I2C1SEL = PRI, IOL1WAY = OFF, OSCIOFNC = ON
#pragma config FCKSM = CSECMD, FNOSC = FRC, SOSCSEL = SOSC, WUTSEL = LEG
#pragma config IESO = ON, WDTPS = PS32768, FWPSA = PR128, WINDIS = ON
#pragma config FWDTEN = OFF, ICS = PGx1, GWRP = OFF, GCP = OFF, JTAGEN = OFF

#include "xc.h"

/*
 * Define the system oscillator frequency that this code will setup
 */
#define FSYS (8000000ul)
#define FCY  (FSYS/2ul)

/*
 * Initialize this PIC
 */
void PIC_Init(void) {   
    /* Disable all interrupt sources  */ 
    __builtin_disi(0x3FFF); /* disable interrupts for 16383 cycles */
    IEC0 = 0;
    IEC1 = 0;
    IEC2 = 0;
    IEC3 = 0;
    IEC4 = 0;
    __builtin_disi(0x0000); /* enable interrupts */

    CLKDIV =  0x0000; /* set for 8MHz FRC clock operations */
    AD1PCFG = 0xffff; /* Set for digital I/O */
    CMCON   = 0x0000;

    _NSTDIS = 1;    /* disable interrupt nesting */
    
    TRISA   = 0xFFFF;
    TRISB   = 0xFFFF;
}
/*
 * Initialize OC1 for PWM operation on PORTB bit RB2/RP2
 */
void OC1_Init(void) {
    OC1CON = 0;             /* turn off OC1 */
    AD1PCFGbits.PCFG4 = 1;  /* make GPIO RB2/RP2/AN4 a digital I/O pin */
    LATBbits.LATB2 = 0;     /* make GPIO RB2/RP2/AN4 output low */
    TRISBbits.TRISB2 = 0;   /* make GPIO RB2/RP2/AN4 a digital output */
    _RP2R = 18;             /* magic number to assign OC1 to RB2, see DS39881E-page 109 */
    OC1CONbits.OCTSEL = 0;  /* Use TIMER2 clock input and prescaler settings as clock for PWM count register */
    OC1CONbits.OCM = 0b110; /* Set OC1 for PWM mode, fault shutdown disabled */
    T2CON = 0;              /* turn off TIMER2 */
    T2CONbits.TCKPS = 0b00; /* set TIMER2 prescale as 1:1 */
    T2CONbits.TCS = 0;      /* set TIMER2 clock source as FSYS/2 */
    PR2 = (FCY/4000ul)-1;   /* set PWM period to 4KHz */
    OC1RS = (PR2+1)>>1;     /* set PWM duty cycle to 50% */
    T2CONbits.TON = 1;      /* turn on TIMER2 */
}
/*
 * main application
 */
int main(void) {
    
    PIC_Init();
    OC1_Init();
    /*
     * application process loop
     */
    for(;;) {
        /* poll for start of PWM period */
        if(IFS0bits.T2IF){
            IFS0bits.T2IF= 0; /* put a breakpoint here to verify PWM timing with simulator stop watch */
            Nop();
            Nop();
            Nop();
        }
    }
    return 0;
}

This code does run correctly with the MPLABX simulation tool. This is almost never true. IMHO the MPLABX simulator is crap.

This is screen shot of the simulation session working: enter image description here

An important note on the PIC24FJ64GA002 output compare module is that it is an early silicon implementation and the newer controllers have independent period count registers for each output compare module. This means that the output compare module periods are not tied to the TIMER period on these controllers. I mention this because it really confused me.

Upvotes: 1

Kozmotronik
Kozmotronik

Reputation: 2520

First of all configure the IO pins for PWM output, I assume you use SPDIP package and wanna use the OC1 PWM output pin:

// Unlock Registers
__builtin_write_OSCCONL(OSCCON & 0xBF);
// Configure Output Functions (Table 10-3)
// Assign OC1 To Pin RP2
RPOR1bits.RP2R = 18;
// Lock Registers
__builtin_write_OSCCONL(OSCCON | 0x40);

For the output pin configurations see the 10.4.3.2 Output Mapping section of the datasheet.

As per steps in the datasheet's 14.3 Pulse-Width Modulation Mode section:

  1. Set the PWM period by writing to the selected Timery Period register (PRy). İf your period is 2 ms then compute the PRy register value:
#define Fosc          ( 8000000 )
#define PWM_PERIOD_MS ( 2 )
#define PWM_FREQ_HZ   ( 1000 / PWM_PERIOD_MS ) // In this case 500Hz
#define PRy_VALUE     (uint16_t) ( (( Fosc ) / ( 4 * TMRyPS * PWM_FREQ)) - 1 )

// Somewhere in the init func or code assign the computed value for the period register
PRx = PRx_VALUE;
  1. Set the PWM duty cycle by writing to the OCxRS register. Since you want a 50% duty cycle compute the value for OCxRS register:
#define DUTY          (50)
#define DUTY_VALUE    (uint16_t) ( ( 4 * ( TMRyPS - 1 ) * DUTY ) / 100 )

// Somewhere in the init func or code assign the computed value for the duty cycle register
OCxRS = DUTY_VALUE;
  1. Write the OCxR register with the initial duty cycle. Assign the DUTY_VALUE to the OCxR register right after OCxRS assignment:
    OCxR = DUTY_VALUE;

  2. Enable interrupts, if required, for the timer and output compare modules. The output compare interrupt is required for PWM Fault pin utilization.

  3. Configure the output compare module for one of two PWM Operation modes by writing to the Output Compare Mode bits, OCM<2:0> (OCxCON<2:0>).

 OC1CONbits.OCTSEL   = 0;     // Timer2 is the clock source for output Compare
 OC1CONbits.OCM      = 0x6;   // PWM mode on OC, Fault pin disabled
  1. Set the TMRy prescale value and enable the time base by setting TON (TyCON<15>) = 1.
T2CON = 0x0010 // Timer2 prescaler 1:8, internal clock
T2CON.TON = 1;

From now on you should have the PWM working if the above steps are done correctly. Modify your code as per instructions here. Then try it and let me know the result.

Upvotes: 1

Related Questions