sdcc for PIC HowTo


A simple walkthrough for getting started with C programming on PIC micros using the sdcc C compiler.


1. Introduction

One of the greatest perceived drawbacks of the PIC microcontroller (at least for open source software enthusiasts) is the lack of a free, open-source and user-friendly compiler that supports a major high-level language, and works across a variety of platforms.

The sdcc C compiler is frequently overlooked by PIC newcomers, and has earned the dubious reputation for being:
amongst other deprecating remarks.

While I don't have the time to dive into the sdcc project and help tidy it up, I can offer this document to help eliminate some of the major barriers sdcc imposes on new users.


1.1. Intended Audience

I have written this document for people who:

1.2. Current Problems with sdcc

A quick look at sdcc code and documentation reveals that the PIC targetting is way less developed than that for other chips such as Z80, Atmel etc.

In particular, sdcc for PIC presently has problems such as:
Together, these issues convey a strong impression that sdcc is way pre-alpha, and nowhere near ready for production or even hobbyist usage. Fortunately these impressions are not entirely accurate. sdcc is actually a good working compiler, which works in very nicely with gputils.

I'm glad I've persisted with sdcc, because with a bit of persistence, I've ended up with quite a usable development system. It's satisfying not to be insulted and squashed by crippleware limitations, or guilty/paranoid about using a warez'ed version, or hundreds of dollars poorer and imprisoned in a proprietary environment.


1.3. About this Document

I have written this HowTo as a hands-on practical walkthrough.

If you have a *nix system and a PIC 18Fxx2 microcontroller, you will be able to follow this document step by step, and end up with a fully working sdcc for PIC toolchain, and have sdcc C programs working on the PIC.

I have deliberately focused on the 18F series because:
If you've been using 16F devices, please seriously consider giving them to your kid brother/sister.  They're a step back towards the old valves and relays days.


1.4. Prerequisites Checklist

Before starting on this walkthrough, please ensure you already have:
  1. A PC running Linux or *BSD operating system
  2. The standard gcc toolchain
  3. The pic gputils toolchain
  4. A PIC 18Fxxx device, preferably an 18F252 or 18F452 microcontroller (since this will be the chip used throughout this walkthrough)
  5. A working RS232 serial port and serial cable on your PC
  6. RS232 <-> TTL level converter circuitry on your PIC development board (or at least a MAX232 with the requisite 5 capacitors on your breadboard)
  7. One or both of:
  8. No existing installation of sdcc on your system (it could interfere with the sdcc installation we'll be setting up in the course of this walkthrough)
This document is not written for windows usage (apart from Cygwin users, of course). There are plenty of proprietary PIC compilers that run on that proprietary operating system.


2. Downloading sdcc

We will download and build from an sdcc source package. Please do not install a binary version.

I recommend you download a source snapshot for sdcc.

Look under the heading 'SDCC Source Code (sdcc-src)'  and click on the latest source snapshot tarball. Download it and save it in a directory of your choice.

Once downloaded, untar the package.


3. Building sdcc for PIC micros

3.1. Configure the Build

Here, we will configure sdcc for building PIC code only.

cd into the top-level of the sdcc source directory, and run the configure script.

The command I used was:

./configure \
   --prefix=/usr \
   --disable-mcs51-port \
   --disable-gbz80-port \
   --disable-z80-port \
   --disable-avr-port \
   --disable-ds390-port \
   --disable-ds400-port \
   --disable-hc08-port \
   --disable-xa51-port


3.2. Compiling the Main Toolchain

Once the configure script finishes, you can then type:

make

And then, become root and type:

make install


3.3. Compiling the PIC Libraries

Now, we'll have to build the PIC-specific libraries.

Recall that this guide is based on PIC 18Fxxx[x] chips.

While still in the toplevel sdcc source directory, execute the following commands:

cd device/lib/pic16
./configure
make
cd ..
make model-pic16


This will have built most of the PIC-specific libraries.


3.4. Installing the PIC Libraries

Become root and type:

make install


3.5. Compiling/Installing the Remaining Device-specific PIC Libraries

We're almost done with building sdcc.

Make sure you're once again a normal user, then type:

cd pic16

You should see in that directory a file called pics.build. Edit that file, and delete every line except the line listing the PIC chip you're targetting (I've been compiling for 18fxx2, so I just left the line 452

Now, type:

make lib-io

The prior make install command installed most of the needed PIC18-specific library files into /usr/share/sdcc/lib. However (please someone correct me if I'm wrong) the USART drivers won't have ended up in any of the lib files you see in that directory.

So, as a hack, we'll manually 'install' them, via the command:

cp libio/usart/*.o /usr/share/sdcc/lib/pic16

This will group the usart driver object files with your main PIC-specific device files, making life easier when you link your program.


4. Our First 'Hello, World' program

Phew!

We've finally got sdcc sufficiently well set up to let us do some actual programming.

In this guide (or at least this version, anyway), we'll just create/build/run two simple 'hello, world' demos:
The idea is to help you get your hands dirty with successfully compiled and running programs, so you can then go further and experiment. Also, with the confidence gained, you'll be able to make a start on studying the official sdcc documentation, and where the documentation lacks, reading header files and (gasp!) source code.

4.1. The program source

Copy the source below, and save it as helloled.c:

// helloled.c
//
// simple minimal 'hello, world' program that blinks a LED


// ------------------------------------------------
// configuration
// change these if you're wiring the LED to a pin other than RA1

#ifndef LED_TRIS
#define LED_TRIS  TRISAbits.TRISA1
#endif

#ifndef LED_PIN
#define LED_PIN   PORTAbits.RA1
#endif

// ------------------------------------------------
// this #include pulls in the correct processor-specific registers
// definition file

#include "pic18fregs.h"

// ------------------------------------------------
// a simple delay function

void delay_ms(long ms)
{
    long i;

    while (ms--)
        for (i=0; i < 330; i++)
            ;
}

// --------------------------------------------------
// and our main entry point

void main()
{
    // set pin to output
    LED_TRIS = 0;

    // sit in an endless loop blinking the led
    for (;;)
    {
        LED_PIN = 0;
        delay_ms(250);
        LED_PIN = 1;
        delay_ms(250);
    }
}


A few notes on the above source:


4.2. The makefile

Copy/paste the following, and save it into a Makefile. Please take care to tab-indent the commands under each target:

helloled.hex: helloled.o crt018.o
    gplink \
        -c \
        -s 18f452.lkr \
        -o $@ \
        -m \
        $^ \
        -I /usr/share/sdcc/lib/pic16 \
        pic18f452.lib

helloled.o: helloled.asm
    gpasm -c $<

helloled.asm: helloled.c
    sdcc -S -mpic16 -p18f452 $<


Don't try to run the makefile yet.


4.3. Replacing crt0 module and linker script

My PIC setups use a bootloader, which expects the first four program words to contain a jump to the real start of program.

I'm not sure if this was the cause, but when I first compiled my hello,world led blinker program using the default crt0 and linker script, the program just hung.

With a bit of tinkering, I cooked up linker control scripts, as well as a modified version of the crt0 module that makes things work just fine.

Here's my crt0.asm, which you can copy/paste verbatim into the a file called crt018.asm into the same directory where you created the helloled.c file:

    processor  18F452
    radix  DEC
    include "p18f452.inc"

config CODE
    DATA 3F32H

IDLOCS CODE
    DATA 1234H

;;  imports

    EXTERN _main

; defines of interrupt registers

; vars for saving/restoring registers
;STATUS      EQU   0x03         
;PCLATH      EQU   0x0A
;RP0         EQU   5
;RP1         EQU   6

;BANK0    UDATA
;int_save       RES   2
;  GLOBAL int_save

;svrWREG     RES 1
;  GLOBAL svrWREG

; this makes sure that we have a jump to int handler
;    EXTERN inthandler
;INTVEC code
;    goto inthandler


_reset code
    pagesel _main
    goto _main

; this makes sure we jump to main
startup code
    pagesel _main
    goto _main


    END

And here's my custom linker script, which you should copy/paste verbatim into a file 18f452.lkr in the same directory as the other files, helloled.c, Makefile and crt018.asm:

LIBPATH .

CODEPAGE   NAME=vectors    START=0x0            END=0x29           PROTECTED
CODEPAGE   NAME=page       START=0x2A           END=0x7FFF
CODEPAGE   NAME=idlocs     START=0x200000       END=0x200007       PROTECTED
CODEPAGE   NAME=config     START=0x300000       END=0x30000D       PROTECTED
CODEPAGE   NAME=devid      START=0x3FFFFE       END=0x3FFFFF       PROTECTED
CODEPAGE   NAME=eedata     START=0xF00000       END=0xF000FF       PROTECTED

ACCESSBANK NAME=accessram  START=0x0            END=0x7F
DATABANK   NAME=gpr0       START=0x80           END=0xFF
DATABANK   NAME=gpr1       START=0x100          END=0x1FF
DATABANK   NAME=gpr2       START=0x200          END=0x2FF
DATABANK   NAME=gpr3       START=0x300          END=0x3FF
DATABANK   NAME=gpr4       START=0x400          END=0x4FF
DATABANK   NAME=gpr5       START=0x500          END=0x5FF
ACCESSBANK NAME=accesssfr  START=0xF80          END=0xFFF          PROTECTED

SECTION    NAME=_reset    ROM=vectors
SECTION    NAME=CONFIG     ROM=config
SECTION    NAME=code ROM=page



4.4. Compiling our hello, world

If you've followed the above steps, you should be able to type:

make helloled.hex

and end up with a fully compiled program, ready to run.


4.5. Running the hello, world

Transfer helloled.hex into your PIC, using your PIC programmer or bootloader.

With all going well, you should see the LED on your PIC blinking away happily.


5. Getting Serial I/O Working

sdcc's libraries support many of the standard C stdio functions such as printf(), getchar() etc. However, your program needs to do a bit of basic setup before these will work.

In this walkthrough, we will be running the stdio through the PIC's USART at 115kb/s, and assuming that your PIC has a 20MHz crystal or resonator.

If you desire a different baudrate, or are using a different clock frequency, you will need to refer to your PIC datasheet - the USART chapter, and work out the required values of BRGH and SPBRG to suit your situation.


5.1. The 'Hello, World' stdio program

We'll put the whole program here first. Copy and save it into a file called hellotty.c:

// hellotty.c
//
// simple minimal 'hello, world' program that printf's through the usart


// ------------------------------------------------
// this #include pulls in the correct processor-specific registers
// definition file

#include "pic18fregs.h"

#include "stdio.h"
#include "usart.h"

void stdio_init()
{
    usart_open(
        USART_TX_INT_OFF
            & USART_RX_INT_OFF
            & USART_BRGH_HIGH
            & USART_ASYNCH_MODE
            & USART_EIGHT_BIT,
        10
        );

    stdout = STREAM_USART;
}

void main()
{
    stdio_init();
    printf("hello, world\n");
}

A couple of notes:
  1. If you're using a clockspeed and/or baudrate that requires BGRH=0, change USART_BGRH_HIGH to USART_BGRH_LOW
  2. If your clockspeed or baudrate require an SPBRG value other than 10, change the second parameter of the usart_open() call to what you need.

5.2. The Hello, World stdio Makefile

Here's the Makefile we're using for the tty hello world:

hellotty.hex: hellotty.o crt018.o
    gplink \
        -c \
        -s 18f452.lkr \
        -o $@ \
        -m \
        $^ \
        -I /usr/share/sdcc/lib/pic16 \
        pic18f452.lib libc18f.lib libm18f.lib libsdcc.lib \
        uopen.o uputs.o uputc.o ubusy.o usartd.o

hellotty.o: hellotty.asm
    gpasm -c $<

hellotty.asm: hellotty.c
    sdcc -S -mpic16 -p18f452 $<


5.3. Building/Running the Hello, World stdio example

As before, just type make

You should end up with a hellotty.hex file, which you should be able to download or burn into your PIC. Using a program such as minicom or gtkterm, you should see the incoming 'Hello, world'.


5.4. Some General Observations

Reading the above programs will reveal that:

6. Where To From Here?

Through this walkthrough, we have:
By now, you will be able to get the PIC doing pretty much what you want via direct register manipulation.

The sdcc PIC environment has a reasonable but undocumented API, which you can peruse by reading through the header files in /usr/share/sdcc/include/pic16.
I do lament though that the authors of sdcc's PIC backend haven't yet taken the trouble to document this API, or even add Doxygen markups to the source. I really hope the developers can be persuaded to put in even just a few short hours work into sdcc and its PIC portions, to make it at least as welcoming to new users as the commercial compilers.


7. Contributing Your Knowledge

If you're a PIC expert, It's quite possible you've popped several aneurisms while reading this, thinking why the fsck are you doing this? Don't you know it'd be way easier/cleaner/better doing that?

If so, I'd value your contributions. My address is david at rebirthing dot co dot nz (hopefully email address de-obfuscation isn't yet in widespread use amongst the spammers yet). I'd prefer you don't tell me what to add - rather, edit a copy of this doc, gzip it and send it to me.

If a lot of contributions come in, I'll probably have to look into putting up a wiki, in which case I'd welcome your recommendations of a publicly-hosted wiki suited for tech doc collaboration.