1
\$\begingroup\$

I am trying to set up the internal oscillator on my PIC to oscillate at 16MHz and am trying to test if it's working correctly by blinking an LED. As I understand it, each command takes 4 clock cycles to execute, and I have two statements in my while loop. I increment of my counter and have if statement to check if the counter has reached 4000000 yet. If it has reached it, then it toggles the LED and resets the counter.

If both the increment and if statement take 4 clock cycles to execute, that means that the LED should roughly toggle once every 2 seconds as it should take roughly 32000000 clock cycles for i to equal 4000000. However my LED is only toggling once every 20 seconds. Could someone please tell me what am I doing wrong/What do I not understand?

// PIC12F1822 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)

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

#include <xc.h>


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

/*
 * 
 */
int main(int argc, char** argv) {
    OSCCONbits.SCS=0b11;//needs to be 1x for high frequency oscillator
    OSCCONbits.IRCF = 0b1111; //sets to 16MHz
    unsigned long i=0x0;
    TRISA=0X00; //portA as an output
    PORTA=0X00; //LED off
    while (1)
    {
        i++;//increment counter
        if (i>4000000) //is time to toggle?
        {
            i=0; //reset counter
            PORTA=~PORTA; //toggle LED
        }


    }
        return (EXIT_SUCCESS);
}
\$\endgroup\$
  • 4
    \$\begingroup\$ C statements do not translate 1:1 into assembler statements. Your PIC will be executing many more instructions than what you think. Look at the disassembly listing for your code and you'll see... \$\endgroup\$ – brhans Sep 12 '16 at 15:39
  • \$\begingroup\$ You're completely correct, the increment is 8 assembly lines and the if statement is 11 assembly lines. Making it almost 20 lines, instead of 2 explaining the x10 delay. Thank you. \$\endgroup\$ – genericpurpleturtle Sep 12 '16 at 16:12
  • \$\begingroup\$ @noname Now you know not to accept claims that C code is as efficient as assembly without a certain amount of skepticism (10:1 worse!). \$\endgroup\$ – Spehro Pefhany Sep 12 '16 at 19:26
2
\$\begingroup\$

On an 8 bit PIC most machine code instructions take 4 clocks to execute. But your program is written in C, not machine code. A single C statement may compile into anything from zero instructions up to the entire available memory space, depending on what machine code the compiler decides is required to implement it.

The code in your while(1) loop compiles to this (XC8 V1.37, 'free' mode):-

42:                    while (1)
   7FD    2FE0     GOTO 0x7e0
43:                    {
44:                        i++;//increment counter
   7E0    3001     MOVLW 0x1
   7E1    07F4     ADDWF 0x74, F
   7E2    3000     MOVLW 0
   7E3    3DF5     ADDWFC 0x75, F
   7E4    3000     MOVLW 0
   7E5    3DF6     ADDWFC 0x76, F
   7E6    3000     MOVLW 0
   7E7    3DF7     ADDWFC 0x77, F
45:                        if (i>4000000) //is time to toggle?
   7E8    0877     MOVF 0x77, W
   7E9    1D03     BTFSS 0x3, 0x2
   7EA    2FF3     GOTO 0x7f3
   7EB    3001     MOVLW 0x1
   7EC    0274     SUBWF 0x74, W
   7ED    3009     MOVLW 0x9
   7EE    3B75     SUBWFB 0x75, W
   7EF    303D     MOVLW 0x3d
   7F0    3B76     SUBWFB 0x76, W
   7F1    1C03     BTFSS 0x3, 0
   7F2    2FE0     GOTO 0x7e0
46:                        {
47:                            i=0; //reset counter
   7F3    3000     MOVLW 0
   7F4    00F7     MOVWF 0x77
   7F5    3000     MOVLW 0
   7F6    00F6     MOVWF 0x76
   7F7    3000     MOVLW 0
   7F8    00F5     MOVWF 0x75
   7F9    3000     MOVLW 0
   7FA    00F4     MOVWF 0x74
48:                            PORTA=~PORTA; //toggle LED
   7FB    0020     MOVLB 0

So the single i++ statement resulted in 8 machine code instructions. With i redefined as char instead of long only 4 instructions are generated. The same operation in assembler might only require 1 instruction.

A few machine code instructions take more than 1 CPU cycle (4 clocks) to execute. BTFSS (bit test file, skip if set) always takes 2 cycles, even when skipping the next instruction. GOTO also takes 2 cycles, but BTFSS followed by GOTO only take 3 cycles.

PICs only have a 1 instruction pipeline so the timing is very predictable. Even so, calculating the precise execution time of a complex machine code program is not easy. Doing it from high level source is practically impossible.

If you want precise timing then your choices are:-

  1. 'tweak' the delay loop (and realize that any change may break it)

  2. Write in assembler

  3. Use a hardware timer

Option 3 is the best choice if you want reliable, scalable, maintainable code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.