Frequently Asked Questions (FAQ) about C on the Microchip PIC

These are questions which are frequently asked on various Microchip and PIC-related forums which generally apply to the Hi-Tech C compilers (now known as XC8) and the MPLAB C18 compiler. Some of these are just general C questions that have nothing in particular to do with PICs or any specific compiler, and some are general PIC questions.
  1. What's the best way to get forum help with a code problem?
  2. How can I remap a group of pins on different ports into a single structure?
  3. How can I make an array of port pins?
  4. How can I declare a pointer to a port pin?
  5. How can I pass a port pin as an argument to a function?
  6. Why am I getting a syntax error when I use a #define?
  7. What do these MPLAB C18 warnings mean?
  8. Why am I getting a syntax error on a declaration in this simple code?
  9. How can I save a float, int, double, long, array, or structure to EEPROM?
  10. How can I share a global struct or other type among different C files?
  11. How can I find what's causing the weird behavior in my MPLAB C18 program?
  12. How can I debug a software stack overflow in MPLAB C18?
  13. What is the best way to declare a C18 interrupt service routine (ISR)?
  14. Should I use interrupt priorities on the PIC18?
  15. Why is my C18 interrupt behaving strangely or not running as fast as I expect?
  16. When should I use volatile?
  17. Should I just make everything volatile to be safe?
  18. I just upgraded C18 and there's no linker script for my processor?
  19. How can I pass a pointer to a two-dimensional array to a function, or declare a pointer to a two-dimensional array?
  20. What is Fosc? How is Fosc related to the clock speed? Does the PLL change Fosc?
  21. Why does the UART suddenly stop receiving data? Transmit still works.
  22. Why is my port pin not behaving the way I expect?
  23. Can I sleep in an ISR?
  24. With C18, how can I place some data at a specific address in ROM without using a linker script?
  25. Why am I getting a syntax error at the end of my header file?
  26. What do the different MPLAB C18 memory models do?
  27. What is read-modify-write (RMW)?
  28. Why does the PIC's flash and/or EEPROM get corrupted when powering the PIC on or off?
  29. How does the PORTB change interrupt work?
  30. How can I tell which pin caused the PORTB interrupt?
  31. How can I clear the RBIF flag?
  32. What does higher order function calls not supported yet in the presence of the overlay storage class mean?
  33. Where can I find the settings for the MPLAB C18 #pragma config directive?
  34. Why am I getting a syntax error on my #pragma config FOSC = ... directive?
  35. How can I fix an undefined symbol: _putch error when using printf() with a Hi-Tech/XC8 compiler?
  36. Why does my simple function return an incorrect value in MPLAB C18?
  37. How can I combine two bytes into an integer?
  38. How can I split an integer into two bytes?
  39. Why am I getting call of function without prototype even though I have a prototype?
  40. What does can't find 0x108 words (0x108 withtotal) for psect bss in segment RAM mean?
  41. What does stack frame too large mean?
  42. Why is the Hi-Tech/XC8 compiler issuing variable 'x' is not used warnings even though the variables are being used?
  43. What does 'The Extended CPU Mode configuration bit is enabled, but the program was not built using extended CPU instructions' mean?
  44. Why is the preprocessor calculating an incorrect value?
  45. How can I create an array or other object larger than 256 bytes with MPLAB C18?
  46. How can I fix a can not fit the section linker error?

  1. What's the best way to get forum help with a code problem?

    Create a minimal, self-contained example that demonstrates the problem. It should be a single C file -- no header files unless the problem specifically involves header files, and no external dependencies so that anyone can compile it. Remove any code (including commented-out code) that is not related to the problem. Use standard C types like char and int, not typedefs like DWORD or UINT8. Include your configuration fuse settings in the code. If the code compiles and runs, test the code in the simulator to confirm that it exhibits the problem. Attach the C file to your forum post.

    Do:

    Don't:

  2. How can I remap a group of pins on different ports into a single structure?

    If you have pins scattered on different ports or pins that are not in the desired order on one port, and you're hoping to define something that lets you do lcd.data = 42, you can't. C places all of the members of a structure in address order. C does not support the concept of writing to scattered memory addresses in a single operation.

    There is no simple or clean mechanism to logically rearrange port pins. The most straightforward technique is to write a function that encapsulates the necessary operations on the port pins. See the following three questions for further discussion.

  3. How can I make an array of port pins?
  4. How can I declare a pointer to a port pin?

    You can't. The C language does not permit taking the address of a bit or of a bit-field member of a structure. This means you can't have pointers to bits or arrays of bits, whether or not they're port pins.

  5. How can I pass a port pin as an argument to a function?

    You can't, because you can't take the address of a port pin (refer to the previous question). You can pass the value of a port pin to a function just like any other integer, but the function won't be able to modify the port pin.

    You can pass a reference to a port pin as an argument to a function, or make an array of port pin references, by using the address of the port (not the pin) and an identifier for the pin. For example:

    void set_port_pin(volatile unsigned char *port, unsigned char pin) {
        *port |= 1U << pin;
    }
    
    set_port_pin(&PORTA, 7);
       
    This will set RA7 to 1. Refer to your compiler's header file for the correct type to use for port.

    Note that this type of code is grossly inefficient on a PIC, and will use far more code space and execution time than simply setting the pin value directly or through a macro, which is the preferred solution.

    If you need a further layer of abstraction that lets you refer to the ports themselves indirectly (e.g., a PC program that sends commands controlling the state of arbitrary port bits), place the port addresses in an array:

    volatile unsigned char * const ports[] =
    {
        &PORTA,
        &PORTB,
        &PORTC
    };
    
    /* an enum that defines indices into the ports array */
    
    enum {
        MY_PORTA = 0,
        MY_PORTB = 1,
        MY_PORTC = 2
    };
    
    void set_port_pin(unsigned char port, unsigned char pin) {
        *ports[port] |= 1U << pin;
    }
    
    set_port_pin(MY_PORTB, 5);
    
  6. Why am I getting a syntax error when I use a #define?

    You probably have a semicolon at the end of your #define.

    #define is not a statement, and does not end in a semicolon. A #define just does simple text substution; if you have a semicolon in your #define text, the preprocessor will insert that semicolon into your C code, possibly in a place where a semicolon is a syntax error.

  7. What do these MPLAB C18 warnings mean?

    When compiling code that assigns pointers or passes them as function arguments, C18 tends to provide spurious warnings on valid code, or worse, fails to provide warnings on invalid code. These are the most commonly encountered issues with C18 pointer warnings:

  8. Why am I getting a syntax error on a declaration in this simple code?

    void main(void)
    {
        int a;
        a = 42;
        int b;
        b = a;
    }
    
    Because most PIC C compilers support the C90 standard, which does not permit mixing statements and declarations. This feature was added to the C99 standard. C90 requires that all declarations appear at the beginning of a block.

  9. How can I save a float, int, double, long, array, or structure to EEPROM?
    void eeprom_write_block(void *ptr, unsigned short addr, unsigned char len)
    {
        unsigned char *data = ptr;
    
        while (len--) {
            eeprom_write_byte(addr++, *data++);
        }
    }
    
    /* examples of usage: */
    
    struct { int a; unsigned char b; } s;
    unsigned int c;
    float d;
    unsigned char array[10];
    
    eeprom_write_block(&s, 10, sizeof s);
    eeprom_write_block(&c, 20, sizeof c);
    eeprom_write_block(&d, 30, sizeof d);
    eeprom_write_block(array, 40, sizeof array);
    

    Note that this code takes advantage of C's void type to eliminate casting or the need for different functions for saving different types.

  10. How can I share a global struct or other type among different C files?

    In globals.h:

    /* declare an int */
    
    extern int global_int;
    
    /* declare a struct type */
    
    struct tag {
        int a;
        int b;
    };
    
    /* declare a struct */
    
    extern struct tag global_struct;
    
    /* declare a string that's initialized elsewhere */
    
    extern const char string[];
    
    In globals.c:
    #include globals.h
    
    /* define the int */
    
    int global_int;
    
    /* define the struct */
    
    struct tag global_struct;
    
    /* define the string */
    
    const char string[] = "Hello, world.";
    
    In main.c:
    #include globals.h
    #include <stdio.h>
    
    void main(void)
    {
        /* use the globals */
    
        global_struct.b = 42;
    
        global_int = 0x4242;
    
        printf("%s\n", string);
    }
    

  11. How can I find what's causing the weird behavior in my MPLAB C18 program?

    Some common causes of strange behavior:

  12. How can I debug a software stack overflow in MPLAB C18?

    Start by reading about how the software stack is implemented in the Calling Conventions chapter of the user's guide.

    Generally, PICs with at least 512 bytes of RAM have a 256 byte stack by default, and PICs with less RAM have a 64 byte stack by default. Check your PIC's generic linker file in bin/LKR to see how large the stack is and where it's located.

    Look for large objects that use stack space. Things that are allocated on the stack include function arguments and local variables in functions which do not have the static or rom qualifiers. Identify large structures and arrays and move them off of the stack by making them static or global, or pass a pointer to the object rather than passing the entire object. Some library functions, such as the printf() family, can use more than 64 bytes of stack space, which alone can blow the stack on parts with 64 bytes of stack space.

    If you're still getting a stack overflow, here are some techniques to find it:

  13. What is the best way to declare a C18 interrupt service routine (ISR)?

    #pragma code isr=0x08
    #pragma interrupt isr
    
    void    isr(void)
    {
       /* interrupt handling code here */
    }
    
    #pragma code
    
    This avoids the assembly language springboard function which

    This technique can generally only be used when not using interrupt priorities, which are generally not needed.

    If you need interrupt priorities, this is how to declare the two interrupt handlers:

    #pragma interrupt high_isr
    
    void    high_isr(void)
    {
       /* high priority interrupt handling code here */
    }
    
    #pragma interruptlow low_isr
    
    void    low_isr(void)
    {
       /* low priority interrupt handling code here */
    }
    
    #pragma code high_vector=0x08
    
    void    high_vector(void)
    {
       _asm GOTO high_isr _endasm
    }
    
    #pragma code low_vector=0x18
    
    void    low_vector(void)
    {
       _asm GOTO low_isr _endasm
    }
    
    #pragma code
    
    Yes, you really need five pragmas and four functions to declare two ISRs in C18. And because the vectors have to be defined as functions, there will be a wasted RETURN instruction in each of them -- C18 isn't smart enough to optimize the RETURNs out.

  14. Should I use interrupt priorities on the PIC18?

    Probably not. By default (IPEN = 0), the PIC18's interrupts operate in PIC16 compatibility mode. This means there is a single interrupt vector at address 0x08, and no interrupts will vector to address 0x18. Even in compatibility mode, interrupts take advantage of the fast register stack, which makes zero-cycle context save/restore possible, so there is no inherent disadvantage to using compatibility mode. The disadvantages to using interrupt priorities are many:

    Even ignoring the disadvantages of interrupt priorities, few applications actually need them to perform correctly. A well-written interrupt handler can process all of its interrupts quickly enough to prevent any interrupts from being lost.

    A typical example of a situation that needs interrupt priorities is high-speed clock coming in on an INT pin that has very tight timing requirements. In such a case, it makes sense to enable interrupt priorities, and configure the INT interrupt as the only high-priority interrupt. Using multiple high-priority interrupts is not much different than using compatibility mode, and thus is generally pointless.

  15. Why is my C18 interrupt behaving strangely or not running as fast as I expect?

    The most common cause is calling a function from the interrupt handler. In C18, calling a function from the interrupt handler triggers a worst-case context save. This results in interrupt latency of 50 instruction cycles or more, and total interrupt context save/restore overhead of 100 cycles or more. This makes high-speed interrupts impossible.

    Note that the worst-case context save occurs even if the called function has no parameters or local variables, or even if the function is never actually called. The mere presence of a function call statement in the ISR triggers the worst-case scenario on every interrupt.

    The easiest solution to this is simply not to call any functions from the interrupt handler, which will give best-case context saving. There is a more involved solution available if you absolutely must call functions from the ISR (local mirror).

    Other possible causes are listed here.

  16. When should I use volatile?

    The volatile qualifier originates with the as-if rule in C:

    In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
    The critical part here is, An actual implementation need not evaluate part of an expression if it can deduce that its value is not used. For example,
    int i;
     
    PORTA = 1;
    
    for (i = 0; i < 10; i++)
        ;
    
    PORTA = 2;
    
    Your expectation might be that 1 would be written to PORTA, then a delay, then 2 would be written to PORTA. But the compiler is free to just do PORTA = 2, or even to generate no code at all. This is because a compiler can look at the code and realize there are no needed side effects from assigning 1 to PORTA or from the loop.

    But PORTA works as expected because it is declared as volatile in the compiler's header files; every operation on PORTA must be carried out exactly as stated by the programmer:

    An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine
    volatile tells the compiler to do exactly what the programmer said to do. To correct the code fragment above, i must also be volatile to ensure the delay loop is executed exactly as stated.

    volatile is also relevant in interrupt handlers in cases like this:

    int interrupt_flag;
     
    int main(void) {
        interrupt_flag = 0;
     
        while (interrupt_flag == 0)    /* wait for interrupt */
            ;
     
        /* do stuff */
    }
    
    The compiler can look at that code and realize that interrupt_flag is 0, and will always remain 0, so there is no need for it to generate any code at all for main(), even though you might have an interrupt handler somewhere that sets interrupt_flag to 1. The solution is to make interrupt_flag volatile, so that the compiler understands that it may be modified in ways unknown to the implementation.

    Note that volatile does not provide atomic access. In the previous example, it will take multiple CPU instructions to compare interrupt_flag against 0 on an eight-bit processor. The volatile keyword does not prevent an interrupt from occurring in the middle of this sequence of instructions, which means interrupt_flag may take on an unexpected value with a properly timed interrupt. Atomic access requires additional protection, such as disabling interrupts around the comparison.

  17. Should I just make everything volatile to be safe?

    No. With a modern C compiler, you have to forget about the idea that the compiler is going to blindly follow your instructions step by step. You have to think in terms of what side effects your program generates.

    In an embedded program, side effects are almost exclusively caused by reading or writing the processor's special function registers. If you're blinking an LED, sending data over a UART, or writing to internal EEPROM, you are accessing a special register that is declared as volatile, so all of these things are guaranteed to happen. But other concepts, like assigning a value to a variable, or the passage of time caused by a delay loop, are not side effects. For example:

    int main(void)
    {
        int a, b;
    
        a = 1;
        a *= 2;
        b = 3;
    
        PORTA = a + b;
    }
    
    The only side effect in this program is writing 5 to PORTA. Since PORTA is volatile, we are guaranteed that will happen. What we aren't guaranteed is that some memory location called a will have the value 1 written to it, or that the processor's multiply instruction will be used in calculating the value to be written to PORTA. If the compiler is smart enough to replace the above with
    int main(void)
    {
        PORTA = 5;
    }
    
    we've achieved the same result with less code space and faster execution time.

    There is one situation where you'd want to declare a and b volatile in the above code: when you're debugging. If your compiler is smart enough to optimize all of the assignments away, you won't be able to step through the assignments in the debugger to make sure they're working the way you want. Using the volatile keyword temporarily during debugging can work around this, as can choosing a lower optimization level during debugging.

  18. I just upgraded C18 and there's no linker script for my processor?

    Recent versions of C18 (after 3.20 or so) include a slightly more powerful linker which does not require including a linker script in your project. The linker now has a single generic linker script per processor type, which can be used to build a standard, extended mode, or ICD debug build; the IDE passes options to the linker describing the build type and processor type, and the linker chooses the appropriate script automatically.

    If you have a non-standard requirement like a bootloader, you can still include a linker script in your project. The generic linker scripts are located in the bin/LKR subdirectory of the compiler.

  19. How can I pass a pointer to a two-dimensional array to a function, or declare a pointer to a two-dimensional array?

    With a two-dimensional array, the compiler needs to know how many elements are in a column to move from one row to the next, but movement within a row is always by a single element, so the compiler doesn't need to know the number of rows. Thus the pointer declaration must include the number of columns in the array:

    char array[2][3] = { "19", "42" };
    
    void func(char (*p)[3])
    {
       puts(p[1]);      // prints "42"
    }
    
    func(array);
    
    /* or */
    
    char (*p)[3];
    p = array;
    

  20. What is Fosc? How is Fosc related to the clock speed? Does the PLL change Fosc?

    Fosc is a term that's not used consistently throughout the data sheet.

    When you're looking at the electrical specifications, Fosc means the actual frequency of the external (or internal) clock.

    When you're looking at the configuration bits, Fosc means the type of the oscillator (e.g., XT, HS), which isn't related to the clock speed at all.

    When you're looking at something that affects software, such as the equations for setting up the PWM or UART or I2C baud rate, Fosc means the CPU clock speed, which might or might not be the same as the oscillator speed. The CPU clock speed is the oscillator speed scaled up or down by the chain of oscillator dividers and multipliers. For example, if you're using a PIC with a 4 MHz external oscillator and no PLL, Fosc is 4 MHz. If you're using a PIC with an 8 MHz internal oscillator and a 4x PLL, Fosc is 32 MHz. Fcy is the instruction clock, which is Fosc / 4 on an 8-bit PIC (each instruction takes four clock cycles).

  21. Why does the UART suddenly stop receiving data? Transmit still works.

    You are getting overrun errors. An overrun error occurs when the UART's receive FIFO is full, and another byte of data arrives. When this happens, the UART turns off its receiver, and sets the OERR bit. The OERR bit is read-only and cannot be cleared in software. To restart the UART and clear OERR, it is necessary to clear and then set CREN.

    Overrun errors are caused by software bugs. Unlike framing errors, they are not caused by noise, baud rate mismatches, or other external events. Contrary to popular belief, you do not need to check for overrun errors in your software. Overrun errors are not possible in correctly-written software.

    To avoid overruns, receive serial data in an interrupt, not in a busy loop, and ensure your interrupt handler has low latency.

  22. Why is my port pin not behaving the way I expect?

    Most likely because you haven't disabled the analog features on the port. When a pin is attached to the A/D converter, it will default to analog mode. Such pins are generally labeled as ANxx. Pins on PORTA are almost always analog, while pins on other ports might also be analog. Some 18F PICs, such as the 18F4550 family, have analog pins on PORTB, but these pins can sometimes be made to default to digital with one of the configuration words.

    Some PICs also have comparators that default to enabled and need to be disabled to allow use of their pins in digital mode.

    When a pin is in analog mode, a digital read from the port will always return 0. The pin will still work as a digital output, but because the pin reads 0, it will be particularly susceptible to read-modify-write effects.

    Consult the A/D converter chapter of your data sheet on how to disable analog features. There is no single method; Microchip has used a wide variety of techniques in different PIC families. Note that simply turning off the A/D converter is not enough; each analog pin must be configured for digital use.

    If you have disabled analog features and are still not seeing the expected behavior, it might be the read-modify-write effect.

  23. Can I sleep in an ISR?

    When an interrupt occurs on a PIC, two things happen:

    1. The program counter is pushed on the stack
    2. The PIC branches to the interrupt vector address.

    That's it. The PIC does not store any internal state indicating that it's in an ISR; it continues executing code exactly the same way as it did before it entered the ISR. The ISR is purely a software concept that the PIC knows nothing about. This means you can execute a SLEEP instruction or do anything else in an ISR that you could do in your non-ISR code. Whether sleeping in the ISR is a good idea or not depends on the application.

  24. With C18, how can I place some data at a specific address in ROM without using a linker script?

    Use #pragma romdata. For example:

    #pragma romdata mac_addr=0x800
    rom unsigned char mac_addr[] = { 1, 2, 3, 4, 5, 6 };
    #pragma romdata
    

  25. Why am I getting a syntax error at the end of my header file?

    Because the file doesn't end in a newline.

  26. What do the different MPLAB C18 memory models do?

    They do not make more RAM or ROM available. They will not fix a can not fit the section error, or any other compiler or linker error.

    The default model is the small model (MPLAB IDE refers to this as the small code model). This should be used on any PIC with 64KB or less of program memory.

    The large model should be enabled when using a PIC with more than 64KB of program memory. The only effect the large model has is to make pointers to rom 24 bits instead of 16 bits. If you don't use the large model on a PIC with more than 64KB, the code will compile and link successfully, but if you have pointers to any objects at addresses above 64K, accesses to those objects will fail, since a 16-bit pointer can't point at an address above 64K.

    The large model increases the amount of code space used since the larger pointers have more overhead.

    MPLAB IDE also incorrectly refers to a data model option. C18 does not have a data model; this option refers to an optimization that places all data in the access bank (small data model). By default, C18 uses all available banked RAM (large data model). While certain unusual applications that use very little RAM could benefit from this optimization, it should normally be at the default (off) setting.

  27. What is read-modify-write (RMW)?

    Consider the following code:

    RB0 = 1;
    RB1 = 1;
    
    A reasonable compiler will generate a BSF (bit set file register) instruction for each of these C statements. The BSF instruction does not simply set the value of a single bit. Internally, the PIC reads all eight bits from the port, sets the desired bit, and writes all eight bits back out to the port. For a single BSF instruction by itself, this doesn't cause an issue. But when two BSFs on the same port happen back-to-back, the first BSF may not work as expected.

    The problem occurs when the second BSF reads back the state of the port. If the voltage on RB0 has not yet risen to the level the PIC considers a logic 1 (Vih), the PIC will read back a 0 for RB0, set bit 1 for RB1, and write the result back out to the port. This leaves RB0 at 0, and RB1 at 1, which is not the desired result.

    RMW can become more of an issue with higher PIC clock frequencies, because the amount of time between instructions is smaller, requiring the port output to have a faster rise time to avoid being read back in the wrong state.

    There are several different fixes for the RMW issue; these are listed in order of best to worst:

    Section 9.10.2 of the PICmicro Mid-Range MCU Family Reference Manual (local mirror) has additional discussion and timing diagrams that explain what happens in each clock cycle.

  28. Why does the PIC's flash and/or EEPROM get erased, have all FFs, or contain corrupted values when powering the PIC on or off?

    Because the PIC is browning out: operating at a voltage below the allowed minimum. The brownout itself does not cause flash or EEPROM corruption, but when the PIC is in brownout, it is operating in an undefined state. This means that the program counter can take on a random value which will cause random code to be executed. Murphy's Law says that the most likely place for this random code execution to occur is in your flash or EEPROM write routine.

    The fix is to prevent the PIC from browning out. This can be done with an external supervisor chip such as the MCP100, or by enabling the PIC's brownout reset feature. If you use the PIC's brownout reset, ensure that you select a reset voltage that is within the PIC's rated operating range. Some PICs, especially those that come in low-voltage variants, have a wide range of BOR voltage options, some of which will not actually protect against brownout.

    Brownout cannot be worked around in software.

  29. How does the PORTB change interrupt work?
  30. How can I tell which pin caused the PORTB interrupt?
  31. How can I clear the RBIF flag?

    When the PORTB change interrupt is enabled, the PIC continuously compares the value on each of the PORTB change pins with the the last PORTB values read by your software. When the current pin value of any pin doesn't match the last read value, RBIF is set.

    On most PICs, the PIC does not keep track of which pin change set RBIF (enhanced midrange parts are the exception); this has to be determined in software. One common method is to XOR the current PORTB value with the previous value:

    if (RBIF) {
       unsigned char changes, portb;
       static unsigned char last_portb;
    
       portb = PORTB;
       RBIF = 0;
       changes = portb ^ last_portb;
       last_portb = portb;
    
       /* process the bits in "changes" */
    }   
    
    This code leaves a bit set in changes for each pin that changed state.

    Note that the RBIF flag is cleared after reading the value of PORTB. Reading PORTB updates the PIC's change-detection latch with the current value of PORTB so that it no longer sees a mismatch between the current pin state and the last-read state. As long as this mismatch condition is present, RBIF cannot be cleared.

    The PORTB change interrupt has a major design error in most PICs and is not recommend for use as a general-purpose interrupt. It should only be used as a wake-from-sleep source.

  32. What does higher order function calls not supported yet in the presence of the overlay storage class mean?

    C18 does not permit using function pointers while compiling in the overlay mode. If you want to use function pointers, don't compile in overlay mode (-sco) or don't use the overlay keyword. The not supported yet in the error message is meaningless; they will not ever be supported, since C18 development has been halted.

  33. Where can I find the settings for the MPLAB C18 #pragma config directive?

    In MPLAB 8 IDE, look under Help...Topics...PIC18 Config Settings. In MPLAB X IDE, look under Help...Help Contents...C18 Toolchain...C18 Configuration Settings.

    Another option is to run the compiler from the command line:

    mcc18 --help-config -p=18f87k22 | more
    
    Note the the configuration settings are built in to the compiler and are not in the processor's header file.

  34. Why am I getting a syntax error on my #pragma config FOSC = ... directive?

    Because you have a #define FOSC ..." directive somewhere in your code.

  35. How can I fix an undefined symbol: _putch error when using printf() with a Hi-Tech/XC8 compiler?

    Hi-Tech's printf() does not send its output to any particular device by default. It calls putch() for that purpose, and you must provide an implementation of putch() that sends the output where you want. This can be a UART, LCD, etc. A simple implementation that sends the output to the UART is:

    void    putch(char c)
    {
        while (!TXIF)
            ;
    
        TXREG = c;
    }
    
    printf() does not initialize the UART or any other hardware; you must do that yourself.

  36. Why does a simple function return an incorrect value in MPLAB C18?

    Because there is no function prototype in scope at the point the function is called. This means C18 assumes the return value of the function is int (this is standard C behavior), and if the return value isn't actually int, you'll get garbage as the return value.

    C18 has an interesting calling convention; rather than passing all return values on the stack, it uses different registers depending on the type of the return value. So the garbage value that's returned probably won't even be related the correct return value at all.

  37. How can I combine two bytes into an integer?

    There are several different ways to do this. The best way is to use shifting:

    unsigned int i
    unsigned char a, b;
    
    a = 42;
    b = 24;
    
    i = (a << 8) | b;
    
    This creates an int with a as the most significant byte (MSB) and b as the least significant byte (LSB). This has two major advantages over other approaches:

    On the surface, this code might appear to be inefficient since it involves shifting the MSB eight bits -- an especially bad idea on a PIC. But because of C's as-if rule, this code does not have to be interpreted literally by the compiler. Any reasonable compiler will recognize this common idiom and simply assign a to the upper byte of i.

    If you're using MPLAB C18 in its default non-standard mode (integer promotions disabled), the above code won't work, because C18 doesn't convert a to an int as standard C requires. You can either enable integer promotions, or add a cast:

    i = ((unsigned int)a << 8) | b;
    

    There are two additional approaches:

  38. How can I split an integer into two bytes?

    unsigned int i
    unsigned char a, b;
    
    i = 4242;
    a = (i >> 8) & 0xFF;
    b = i & 0xFF;
    
    Just like the code to combine bytes into an integer, this code does exactly the same thing on any compiler and processor, and it's obvious which byte is the LSB (b) and which is the MSB (a). The union and pointer approaches described in that link can also be used for this task, but they suffer from the same disadvantages.

  39. Why am I getting call of function without prototype even though I have a prototype?

    Because you really don't have a prototype. You might have something like

    void func();
    
    which is not a function prototype. C requires that a function prototype specify the number and types of the function's parameters. If there are no parameters, void must be explicitly specified:
    void func(void);
    
  40. What does can't find 0x108 words (0x108 withtotal) for psect bss in segment RAM mean?

    Hi-Tech's STD compilers for the PIC18 sometimes require a certain amount of manual memory management (this is not true with the PRO compilers or XC8 compilers). Here is some background and a workaround for the error:

    Given these qualifiers:

    1. uninitialized
    2. static storage duration (at file scope and/or using the static keyword)
    3. type char (including arrays)
    4. size greater than 256 bytes

    If an object is ((1 and 2 and 3) or (1 and 2 and 4)), it is placed in the bigbss psect. Any remaining objects which are (1 and 2) are placed in a bss psect.

    Each C module has one bss psect of at most 256 bytes. Thus each module can have no more than a total of 256 bytes of bss objects; this restriction may require moving objects into separate modules. E.g., two int[65] arrays could not appear in one module, because their total size is 260 bytes, and they do not meet the requirements that would place them in the bigbss psect. But if each array is placed in a separate .c file, the program should build successfully.

    Another restriction is that a bss psect cannot straddle a bank boundary. Thus e.g. a PIC with 512 bytes of RAM could not have three int[65] arrays, even in separate modules. Even though their total size is only 390 bytes, there is no way to place them without straddling banks. Objects in bigbss can straddle banks, and do not have per-module size restrictions. But there is no way to force an object into bigbss.

    If your error relates to the data psect (initialized data) rather than a bss psect, the above workaround doesn't apply. The compiler has a hard limit of 256 bytes for the entire program's data psect. The workarounds for this issue are:

  41. What does stack frame too large mean?

    MPLAB C18 has a limitation on how much stack data can be accessed in a single frame (one function). This is about 120 bytes in standard mode and about 95 bytes in extended mode. This limitation cannot be overcome by increasing the stack size. One workaround is to make any large auto objects in the function static, which takes them off the stack.

  42. Why is the Hi-Tech/XC8 compiler issuing variable 'x' is not used warnings even though the variables are being used?

    This warning might be more clear if it stated that the variable's value is not used.

    As was noted in the discussion of the volatile keyword, the compiler is only looking for the side effects caused by your program. Side effects are limited to reads and writes to the processor's volatile special function registers. Any other code that doesn't cause side effects may be optimized away, resulting in unexpected warnings.

    For example, given this code:

    void main(void)
    {
        unsigned char a, b;
    
        a = 1;
        b = a;
    }
    
    PICC-18 9.80 will warn that b is not used and that the b = a assignment generates no code. Looking at the list file, no code is generated for main(). This is because the program has no side effects (it does not write to any volatile special function registers), so the compiler is permitted to optimize the entire program away. Even though b is assigned to and appears to be used in the code, its value is never used, so there is no reason to generate code for it.

    Now consider this code:

    void main(void)
    {
        unsigned char a, b;
    
        a = 1;
        b = a;
    
        PORTA = b;
    }
    
    This compiles with no warnings, because the value of b is used to write to PORTA, which is a volatile object that causes side effects. However, if you look at the list file, you'll see that compiler still didn't blindly follow the code:
        52                           ;t.c: 71: PORTA = b;
        53  001FF8  0E01                movlw   1
        54  001FFA  6E80                movwf   3968,c  ;volatile
    
    All of the references to a and b have been removed, and the compiler correctly generates code only for the necessary side effect: loading 1 into PORTA.

  43. What does 'The Extended CPU Mode configuration bit is enabled, but the program was not built using extended CPU instructions.' mean?

    Newer PIC18s have a feature called the extended instruction set. When this feature is enabled, it adds some new instructions and changes the behavior of many existing instructions. This feature was added in an attempt to reduce the code size generated by the C18 compiler. While it did succeed in that goal, Microchip has deprecated the C18 compiler, and now supports only the XC8 compiler for 8-bit PICs. XC8 does not support the extended instruction set, and probably never will, meaning the extended instruction set is effectively obsolete.

    The extended instruction set is enabled/disabled with the configuration fuse bit XINST, which may be enabled by default on certain PICs. As a consequence, if XINST is not explicitly disabled, the PIC will operate in extended mode. Non-extended code running in extended mode will have various 'weird' behaviors that will depend on various factors, such as where the compiler happens to place objects in RAM. With XC8, extended mode should always be disabled with

    #pragma config XINST = OFF
    If you are using the free/lite/academic version of C18, it also does not support the extended instruction set, and it should be disabled as shown above. Only the paid version of C18 supports the extended instruction set. When the extended instruction set is enabled, it is also necessary to provide the --extended command-line option (or the equivalent in the IDE) to enable the generation of the appropriate code.

  44. Why is the preprocessor calculating an incorrect value?

    It isn't. The preprocessor doesn't evaluate expressions in C code; it simply replaces macro(s) with their associated text. The compiler evaluates the resulting replaced text.

    There are two common errors when using the preprocessor to insert numeric expressions into code:

    1. Integer overflow:
      #define SECONDS_PER_DAY    (60 * 60 * 24)
      
      long week = SECONDS_PER_DAY * 7;
      
      This expands to:
      long week = (60 * 60 * 24) * 7;
      
      The expected result is 604800, which fits easily into a long, but the actual result is something else. This is because the expression 60 * 60 * 24 is 86400, which does not fit into a 16-bit int, and thus overflows giving a garbage value. The fact that the result is being assigned to a long isn't relevant; 60 and 24 are ints, so the multiplication is done with int arithmetic. Use 60L to force long arithmetic.

      Note that MPLAB C18 has integer promotions disabled by default, which means expressions may be evaluated with char arithmetic, making overflows even more likely. You can enable integer promotions to get standard C behavior.

    2. Missing parentheses:
      #define CATS     2
      #define DOGS     42
      #define ANIMALS  CATS + DOGS
      
      int pairs = ANIMALS / 2;
      
      This expands to:
      int pairs = 2 + 42 / 2;
      
      giving the unexpected result 23 instead of 22. Since the preprocessor does simple text substitution, ANIMAL is not any sort of variable, and doesn't get any special treatment when the compiler evaluates it. C's precedence rules cause the unexpected result. The fix is to always enclose expressions in parentheses:
      #define ANIMALS  (CATS + DOGS)
      
      giving
      int pairs = (2 + 42) / 2;
      

Copyright © 2011-2015 John Temples (pic at xargs dot com)