cembeddedpicpic18

Function writing "wrong" value to register - PIC18F


I've written a function in which you should able to input a number and the function then calculates the value to write to the register. However it looks like it's writing the "wrong" value to the register. I've used a calculator and wolframalpha to confirm that I'm not screwing up order of operations. The function is listed below:

void SetFrequency_Hz(int Freq) {
    PR4 = ((41667000 / (4 * 16 * Freq)) - 1);
}

When I try to set the Frequency to 20kHz (20,000Hz), I would need to set the PR4 register to 32. Which in theory if I put in the 20000 calue into the function should be spit out, BUT it's spitting out 179 for some reason. Any guesses why?

MCU: PIC18F67J60 IDE: MP LAB X


Solution

  • Since in PIC18 a int type would be only 16 bits, the somewhat arcane implicit conversion rules in C will cause intermediate results in your expression to be truncated. I am surprised that your compiler did not issue a warning for the literal 41667000 since that clearly will not fit in a PIC18 int type in any case.

    The problem can be solved easily by using explicit literal type suffixes to change the overall type of the expression:

    PR4 = 41667000ul / (64ul * Freq) - 1u ;
    

    or if the required frequency resolution is in KHz, you can scale the expression:

    PR4 = 41667u / (64u * (Freq/1000)) - 1u ;
    

    It should be noted however that in both these cases the real value of 41667000 / (64 x 20x103)) - 1 is ~31.55 so the value in PR4 will be 31 not 32, and will result in an actual frequency of 20345Hz.

    To round to the nearest integer value of the real expression:

    PR4 = (41667000ul / (32ul * Freq) - 1u) / 2 ;
    

    That will result in PR4=32 and a frequency of 19729Hz.

    It may be useful to have the function return the actual achieved frequency:

    unsigned SetFrequency_Hz( unsigned ideal_freq ) 
    {
        PR4 = (41667000ul / (32ul * ideal_freq ) - 1u) / 2u ;
    
        // Return actual achievable frequency
        return 41667000ul / ((PR4 + 1ul) * 64ul)
    }