C vs. Assembly Language on the Microchip PIC

The C vs. assembly language debate arises regularly on various mailing lists, newsgroups, and forums where PICs are discussed. I won't attempt to argue that one is better than the other, since I'm sure anyone who is writing code on PICs can decide for themselves whether the tool they're using is appropriate for their own needs and abilities.

However, there is a theme which always comes up when this issue is being debated: C is not suitable for use on PICs such as the PIC10, PIC12, and PIC16 families. It's a rather baffling assertion, since clearly many people (myself included) are successfully developing commercial products using C on the smaller PICs. The arguments against C on PICs generally fall into one of these categories:

  1. C has too much overhead for use on such small devices. No one can deny that some things can be done more efficiently on a PIC in assembly language than they can be done in C. But generally, the claims of too much overhead are talking about something much more insidious. Take, for example, this question asked on the PICList mailing list: When you only have a three word hardware stack, I don't even think it's possible to use C, is it? There is a common belief among people who don't use C that vast quantities of the processor's resources are mysteriously consumed by overhead. I've included an example C program below for the 10F200 in an attempt to disprove this belief.
  2. The PIC architecture doesn't support C. It's true that the PIC's architecture was not designed with C in mind. The most problematic omission is a software stack: low-end PICs have only a small hardware call stack (2-8 words) that can't be accessed in software. Compiler vendors work around this by using a compiled stack, which analyzes stack usage at compile time, and statically allocates memory for function arguments and auto variables. This works fine for most applications, but it does generally preclude the use of recursion and of re-entrant functions. Another feature missing from the PIC's architecture is a shift instruction, but even the larger, C-friendly PIC18 family doesn't have this, so it's not a handicap unique to the low-end PICs.
  3. There are no standard C compilers for the PIC.This may be true, given the stack restriction noted above. In addition, the C standard's translation limits don't permit full compliance of a compiler for many small processors, regardless of their architecture. However, whenever I've asked anyone putting forward this argument why they'd want to use recursion on a processor having 16 bytes of RAM, or why they'd want to declare a 509 character string literal on a processor with 512 words of program memory, they don't have a answer. A vast number of problems can be solved with standard C on a platform that can't comply with every aspect of the standard, so I dismiss this claim as pedantry.

Below is an actual commercial application written for the PIC 10F200, which is the smallest PIC. This is a US$0.39, six-pin device with 256 words of program memory and 16 bytes of RAM. The application takes a pulse stream coming in on an input pin, changes its duty cycle, and sends the result to an output pin. It's an almost trivial application, but it serves to demonstrate the amount of overhead used by a C compiler on the tiniest of PICs.

void main(void)
{
    for (;;) {

        TRIS = 0x01;
        OPTION = 0x47;                  /* GPIO wakeup; PU off; 256:1 PS */

        if (GPWUF) {                    /* reset from pin change? */
            GPWUF = 0;
            if (PULSE_IN) {             /* act on rising edge */
                PULSE_OUT = 1;
                TMR0 = NO_PR_TMR(60, TMR0_PS) - 1;

                while (TMR0 != 0)       /* busy wait for new high period */
                    ;
            }
        }

        PULSE_OUT = 0;
        GPIO;                           /* clear change condition */

        SLEEP();                        /* wait for next edge */

        /* a pin change resets us; we should never get here, but loop
         * back to the top if we do.
         * */
    }
}
Here is the assembly language list file as generated by HI-TECH PICC 9.50PL2:
    23                           ;main.c: 16: void main(void)
    24                           ;main.c: 20: TRIS = 0x01;
    25  002  C02                	movlw	1
    26  003  006                	tris	6
    27                           ;main.c: 21: OPTION = 0x47;
    28  004  C47                	movlw	71
    29  005  002                	option
    30                           ;main.c: 23: if (GPWUF) {
    31  006  7E3                	btfss	3,7
    32  007  A11                	goto	l7
    33                           ;main.c: 24: GPWUF = 0;
    34  008  4E3                	bcf	3,7
    35                           ;main.c: 25: if (GP0) {
    36  009  706                	btfss	6,0
    37  00A  A11                	goto	l7
    38                           ;main.c: 26: GP1 = 1;
    39  00B  526                	bsf	6,1
    40                           ;main.c: 27: TMR0 = (u16)(256 - (u16)((60) * (4
      +                          000000 / 4 / 256 / 1000.) - 1 + 0.5)) - 1;
    41  00C  C16                	movlw	22
    42  00D  021                	movwf	1	;volatile
    43                           ;main.c: 29: while (TMR0 != 0)
    44  00E                     l9
    45                           ;main.c: 30: ;
    46  00E  201                	movf	1,w	;volatile
    47  00F  743                	btfss	3,2
    48  010  A0E                	goto	l9
    49  011                     l7
    50                           ;main.c: 31: }
                                 ;main.c: 33: GP1 = 0;
    51  011  426                	bcf	6,1
    52                           ;main.c: 35: GPIO;
    53  012  206                	movf	6,w	;volatile
    54                           ;main.c: 37: asm("sleep");
    55  013  003                	sleep	;#
    56                           ;main.c: 42: }
    57  014  A02                	goto	l4
The list file doesn't show the C startup code at addresses 0 and 1:
        000   movwf OSCCAL
        001   goto main
The movwf OSCCAL is necessary to calibrate the oscillator on the 10F200, but the goto main is unnecessary overhead in this application. Note that the compiler automatically excludes standard C startup code such as data initialization, since this application does not require it. The entire application is 21 words in length; there are no vast amounts of missing code space or stack levels. Barring the one unnecessary instruction in the startup code, I doubt that an assembly language programmer could best this.
Copyright © 2007 John Temples (comparison at xargs dot com)