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:
- hard to use
- not ready for usage
- not suitable for PIC
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:
- Enjoy developing and/or experimenting with PIC microcontrollers
- Know and like the C language, or are keen to know more about it
and try it out with PICs
- Are reluctant to shackle their hard-written codebases into the
environment of a proprietary toolchain, alienate thousands of other
developers, while lightening their wallets
by hundreds or thousands of dollars
- Prefer to use a
compiler toolchain to do useful and interesting PIC app development,
without getting dragged into the toolchain's esoteric entrails
- Want to get started with a free open source compiler, with the
barest minimum of complications and distractions
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:
- lack of example programs
- lack of demo makefiles
- PIC-specific portion of I/O libraries (eg the USART driver) is
not included in the
Debian distro (not even sid), and may be missing from other distros as
well
- depending on bootloader setup, even the simplest program could
fail, unless the startup crt0
module, as well as the linker
script, are modified
- severe lack of API doco, usage doco and PIC-specific doco in
general - users
have to read header files to find out what functions are available, and
may even have to read library source code to properly figure out how to
use these functions. Hopefully sdcc/PIC devs might take the hint, and
doxygen the headers at the very least
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:
- The 18F architecture sucks, but the 16F architecture sucks many
orders of magnitude worse
- Microchip's budgetary price for 18F is only around a dollar or
20% more than for equivalent 16F devices. Any retailer that doesn't
reflect this in their pricing (eg, charging twice as much for 18F chips
compared to 16F chips) is scamming you
- The 18F has much more memory
- The 18F's architecture allows compilers to generate much more
efficient code, which further magnifies the difference in program memory
- Assembler hacking (including interfacing C code with assembler)
is far more comfortable on 18F chips
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:
- A PC running Linux or *BSD operating system
- The standard gcc
toolchain
- The pic gputils toolchain
- A PIC 18Fxxx device, preferably an 18F252 or 18F452
microcontroller (since this will be the chip used throughout this
walkthrough)
- A working RS232 serial port and serial cable on your PC
- RS232 <-> TTL level converter circuitry on your PIC
development board (or at least a MAX232 with the requisite 5 capacitors
on your breadboard)
- One or both of:
- a working bootloader programmed into the PIC, and supported
by a bootloader client program on the host PC, and/or
- PIC programmer hardware and software
- 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:
- one which blinks a LED
- one which talks over a serial link via
printf()
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:
- You can address a register, whole byte at a time, by using the
register's name as a variable to read from or assign to, eg:
PORTB = 0x23;
Ditto for setting all the TRIS bits at once: TRISC = 0x7c;
- You can attress a register, a bit at a time, via <regname>.<bitname>,
such as TRISAbits.TRISA1 or PORTAbits.RA1above.
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:
- If you're using a clockspeed and/or baudrate that requires
BGRH=0, change
USART_BGRH_HIGH
to USART_BGRH_LOW
- 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:
- I/O ports and other registers can be accessed byte-wise by referring to them by
name, all uppercase, eg:
- I/O ports and other registers can be accessed bit-wise by using them as C
structures, the details of which are listed in
/usr/share/sdcc/include/pic16/pic18f452.h, for example:
- if (PORTAbits.RA1) ...
- f = PORTBbits.RB5;
- TRISCbits.TRISC3 = 0;
6. Where To From Here?
Through this walkthrough, we have:
- built and installed a working sdcc toolchain
- compiled and executed simple sdcc C pic programs, which
manipulate port pins, and also communicate over the serial USART port
- used modified versions of the crt0 module and linker script,
which together assist the building of bootloader-friendly program images
- accessed register files
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.