Subversion Repositories Code-Repo

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "PWM.h"
#include "INTERRUPTS.h"

static volatile uint8_t computedFrequency = 0;
static volatile uint8_t computedDutyCycleHigh_UpperByte = 0;
static volatile uint8_t computedDutyCycleHigh_LowerBits = 0;
static volatile uint8_t computedDutyCycleLow_UpperByte = 0;
static volatile uint8_t computedDutyCycleLow_LowerBits = 0;
static volatile uint8_t savedDutyCycleHigh = PWM_DEFAULT_HIGH_CYCLE;
static volatile uint8_t savedDutyCycleLow = PWM_DEFAULT_LOW_CYCLE;
static volatile uint16_t savedPattern = 0xAAAA;

void PWM_Init() {
    // Initialize CCP1 / Timer 2
    CCP1_TRIS = 1;  // PWM output starts disabled
    CCP1CONbits.P1M = 0b00;     // Single output, P1A modulated only
    CCP1CONbits.CCP1M = 0b1100; // PWM Mode, P1A active-high, P1B active-high
    PIR1bits.TMR2IF = 0;        // Clear Timer 2 interrupt flag
    TMR2 = 0x0;

    Set_PWM_Frequency(PWM_DEFAULT_FREQ);
    Set_PWM_Duty_Cycle(PWM_DEFAULT_HIGH_CYCLE, PWM_DEFAULT_LOW_CYCLE);
}

void Set_PWM_Frequency(uint32_t frequency) {
    // Timer 2 clocked at FOSC/4 (20 Mhz)
    // Prescaler 1:1 = minimum frequency of 19,532 Hz
    // Prescaler 1:4 = minimum frequency of 4,883 Hz
    // Prescaler 1:16 = minimum frequency of 1,221 Hz
    // Prescaler 1:64 = minimum frequency of 306 Hz

    // PWM Period = [PR2 + 1] * 4 * TOSC * Prescale
    //            = [PR2 + 1] * 4 * (1/FOSC) * Prescale
    //            = ([PR2 + 1] * 4 * Prescale) / FOSC
    // PWM Freq = 1/(PWM Period)
    //          = 1/(PR2 + 1) * 1/4 * FOSC * 1/Prescale)
    //          = FOSC / ([PR2 + 1] * 4 * Prescale)
    // PR2 = (FOSC / [(PWM Freq) * 4 * Presccale]) - 1

    uint8_t preScaleValue;
    if (frequency > 19532) {
        preScaleValue = 1;
        T2CONbits.T2CKPS = 0b00;
    } else if (frequency > 4883) {
        preScaleValue = 4;
        T2CONbits.T2CKPS = 0b01;
    } else if (frequency > 1221) {
        preScaleValue = 16;
        T2CONbits.T2CKPS = 0b10;
    } else {
        preScaleValue = 64;
        T2CONbits.T2CKPS = 0b11;
    }

    uint32_t tmp = frequency * 4 * preScaleValue;
    computedFrequency = (_XTAL_FREQ / tmp) - 1;

    // Updated duty cycle
    Set_PWM_Duty_Cycle(savedDutyCycleHigh, savedDutyCycleLow);
}

void Set_PWM_Duty_Cycle(uint8_t highPercent, uint8_t lowPercent) {
    // Duty cycle specified by 10 bit value in CCPR1L:DC1B<1:0>
    savedDutyCycleHigh = highPercent;
    savedDutyCycleLow = lowPercent;

    // Compute values to store in register
    uint32_t highValue = (computedFrequency + 1) * 4;
    highValue *= highPercent;
    highValue /= 100;
    computedDutyCycleHigh_LowerBits = highValue & 0x3;
    computedDutyCycleHigh_UpperByte = (highValue >> 2) & 0xFF;

    uint32_t lowValue = (computedFrequency + 1) * 4;
    lowValue *= lowPercent;
    lowValue /= 100;
    computedDutyCycleLow_LowerBits = lowValue & 0x3;
    computedDutyCycleLow_UpperByte = (lowValue >> 2) & 0xFF;
}

void Set_PWM_Pattern(uint16_t pattern) {
    savedPattern = pattern;
}

void PWM_Transmit_Pattern() {
    // Set PWM frequency pre-computed values
    PR2 = computedFrequency;

    // Set duty cycle to 0%
    CCP1CONbits.DC1B = 0b00;
    CCPR1L = 0x00;

    // Start timer and wait for it to rollover to latch duty cycle value
    T2CONbits.TMR2ON = 1;
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;
    CCP1_TRIS = 0;

    // Bit 15
    if (savedPattern & 0x8000) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    /* The above section of code disassembles to the following assembly code
     * According to the instruction set table, this should take 22 cycles to execute
     * 22 cycles corresponds to a maximum of ~227.272 kHz PWM frequency
     * If higher PWM frequency is needed, DC1B can be omitted for lower duty cycle accuracy
    !    if (pattern & 0x8000) {
    0x26: BTFSS 0x72, 0x7
    0x27: GOTO 0x34
    !        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
    0x28: MOVLB 0x0
    0x29: MOVF computedDutyCycleHigh_LowerBits, W
    0x2A: MOVWF 0x73
    0x2B: SWAPF 0x73, F
    0x2C: MOVLB 0x5
    0x2D: MOVF CCP1CON, W
    0x2E: XORWF 0x2F3, W
    0x2F: ANDLW 0xCF
    0x30: XORWF 0x2F3, W
    0x31: MOVWF CCP1CON
    !        CCPR1L = computedDutyCycleHigh_UpperByte;
    0x32: MOVF computedDutyCycleHigh_UpperByte, W
    0x33: GOTO 0x41
    !    } else {
    !        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
    0x34: MOVLB 0x0
    0x35: MOVF computedDutyCycleLow_LowerBits, W
    0x36: MOVWF 0x73
    0x37: SWAPF 0x73, F
    0x38: MOVLB 0x5
    0x39: MOVF CCP1CON, W
    0x3A: XORWF 0x2F3, W
    0x3B: ANDLW 0xCF
    0x3C: XORWF 0x2F3, W
    0x3D: MOVWF CCP1CON
    !        CCPR1L = computedDutyCycleLow_UpperByte;
    0x3E: MOVLB 0x0
    0x3F: MOVF computedDutyCycleLow_UpperByte, W
    0x40: MOVLB 0x5
    0x41: MOVWF CCPR1
    !    }
    !    while (!PIR1bits.TMR2IF);
    0x42: MOVLB 0x0
    0x43: BTFSS PIR1, 0x1
    0x44: GOTO 0x42
    !    PIR1bits.TMR2IF = 0;
    0x45: BCF PIR1, 0x1

     * If DC1B is ignored, the disassembly is as follows:
     * According to the instruction set table, this should take 14 cycles to execute
     * 14 cycles corresponds to a maximum of ~357.142 kHz PWM frequency
    !    // Bit 15
    !    if (pattern & 0x8000) {
    0x26: BTFSS 0x72, 0x7
    0x27: GOTO 0x2A
    !        CCPR1L = computedDutyCycleHigh_UpperByte;
    0x28: MOVF computedDutyCycleHigh_UpperByte, W
    0x29: GOTO 0x2C
    !    } else {
    !        CCPR1L = computedDutyCycleLow_UpperByte;
    0x2A: MOVLB 0x0
    0x2B: MOVF computedDutyCycleLow_UpperByte, W
    0x2C: MOVLB 0x5
    0x2D: MOVWF CCPR1
    !    }
    !    while (!PIR1bits.TMR2IF);
    0x2E: MOVLB 0x0
    0x2F: BTFSS PIR1, 0x1
    0x30: GOTO 0x2E
    !    PIR1bits.TMR2IF = 0;
    0x31: BCF PIR1, 0x1
    */

    // Bit 14
    if (savedPattern & 0x4000) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 13
    if (savedPattern & 0x2000) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 12
    if (savedPattern & 0x1000) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 11
    if (savedPattern & 0x0800) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 10
    if (savedPattern & 0x0400) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 9
    if (savedPattern & 0x0200) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 8
    if (savedPattern & 0x0100) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 7
    if (savedPattern & 0x0080) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 6
    if (savedPattern & 0x0040) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 5
    if (savedPattern & 0x0020) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 4
    if (savedPattern & 0x0010) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 3
    if (savedPattern & 0x0008) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 2
    if (savedPattern & 0x0004) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 1
    if (savedPattern & 0x0002) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Bit 0
    if (savedPattern & 0x0001) {
        CCP1CONbits.DC1B = computedDutyCycleHigh_LowerBits;
        CCPR1L = computedDutyCycleHigh_UpperByte;
    } else {
        CCP1CONbits.DC1B = computedDutyCycleLow_LowerBits;
        CCPR1L = computedDutyCycleLow_UpperByte;
    }
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;

    // Set next duty cycle to 0% (idle line low)
    CCP1CONbits.DC1B = 0b00;
    CCPR1L = 0x00;

    // Wait for timer to rollover, then turn off timer
    while (!PIR1bits.TMR2IF);
    PIR1bits.TMR2IF = 0;
    T2CONbits.TMR2ON = 0;
    TMR2 = 0x0;
}