CodeWarrior C
CodeWarrior C
1 Introduction
This document discusses the use of the CodeWarrior for Microcontrollers, Special Edition software for pro-
gramming the Freescale HC08 microcontrollers in C. Much if this document is generic and applies to any
processor in the HC08 family. However parts that discuss things like memory layout are specific to the
processor currently being used in the class. The class is currently using the MC908JL16 (sometimes listed as
MC68HC908JL16.) If the CodeWarrior compiler is used for other HC08 processors the information in these
areas will have to be adjusted to match the other processor.
2 Installation Instructions
The CodeWarrior installation software can be obtained from the instructor if you wish to install the software
on your own system. It should run on any Windows XP or Windows 7 system. To install the software:
• Copy the installation file to your hard drive. Depending on the version it will have a name like
“CW_MCUs_V6_3.exe”.
• Double-click on the file you just copied. There may also be some patch or updates file with it.
• Proceed through the series of questions and agreements posed to you. The program will try to install
itself under the Program Files folder of your C drive. Feel free to change that if you desire. When you
are asked what type of installation you desire, choose “Complete”.
• If there are any update files these can also be installed.
• After the installation is complete, reboot your system.
The CodeWarrior application can be found in the Start menu under “All Programs”, “Freescale Code-
Warrior”, “CW for Microcontrollers V6.x”, “CodeWarrior IDE”. You should now be ready to run the Code-
Warrior development system to create software for your microcontroller.
• File. . . New Project. . . . This will bring up the new project wizard to gather information about your
project.
At this point CodeWarrior will create the project folder and bring up the project window.
• The “Sources” folder contains your source files. In that folder you should see the files you asked to be
added to your project. If you didn’t add any and selected the option to have CodeWarrior create a
main.c it will show up here and you can either edit main.c or replace it with another file. If you choose
to replace it, use the “Remove” command under the Edit menu to delete main.c from the project, then
use the “Add Files” command under the Project menu to add a different main program file. Other C
files can also be added to the project. Double-clicking the file will open it in the CodeWarrior editor.
• The “Includes” folder contains files specific to model of microcontroller you are using. Your program
should reference the “derivative.h” file which then references the correct include file for your micro-
controller. These files declare variable names you can use to read and write the internal registers of
your microcontroller. It also defines bit names that allow you to read, set, or clear any bit in one of
the internal registers. You should use these variables rather than trying to create your own system of
addressing the internal registers.
• The “Libs” Folder contains files to support the specific model of microcontroller being used as well as
the ANSI-C libraries you may use. There should not be any reason to modify the files in this folder.
• The “Startup Code” folder contains code used to allow the processor to run C-language code. You
should NOT modify this code.
• The “Linker Files” folder, contains files used the CodeWarrior program to compile and link your
program. The only file you should be interested in is the “.prm” file that specifies the overall memory
map of your program and also provides the ability to register your interrupt service routines (ISR’s).
Initially a new project has a file named “Project.prm” in this folder. As with the main C program file,
you can either edit this file or remove it and replace it with another. See Sec. 6 for more information.
5 Writing Code
Writing C code to be run on a microcontroller requires you to manage some of the low-level details of the
system since a microcontroller has no operating system. Not only are you writing the application to be run on
the system but also the essential portions of OS code. The low-level details you must manage include reading
and writing internal registers, writing assembly routines, registering interrupt service routines, dealing with
memory addressing issues, and some basic optimization issues.
0x0260
0x025F
RAM
(512 bytes)
Direct Page
Region
0x0060
Global variable area 0x0000 - 0x00FF
Device Registers
0x0000
of 80 bytes, perhaps one that can be shared with some other functions that is not called at the same
time. Even though good programming practice suggests not using global variables, the use of global
variables is often the best way to prevent problems with the stack.
• Avoid passing a data structure by value to a function. If the “struct” is passed by value, then
the calling routine puts all the values in the structure on the stack before jumping to the start of
the function. A better way is probably to pass the structure by reference by passing a pointer to the
structure rather than the values.
• Do not write programs that use recursion. Recursion is when a function calls itself and this
practice can lead to excessive use of stack memory. If you think your program needs to operate
recursively, try to find another way to implement the program.
There is a parameter in the linker parameter file (.prm file) that determines how much space is allocated
for the stack (see Sec. 6). This value can be increased if your program needs more stack space but be
aware that it doesn’t fully solve the problems described above. The parameter defines the size of the stack
and causes the linker to not allocate variables above what should be the lower boundary of the stack. For
example, the default stack size is 0x050 (80 bytes) and this means the stack is expected to occupy the space
from 0x0FF down to 0x0B0. The linker will then not allocate any variables above the address of 0x0B0.
However, this doesn’t prevent the stack from growing downward into the area below 0x0B0 if the program
causes that to happen. In other words, the stack size parameter allocates space for the stack, but it is up to
the programmer to make sure it stays within that area.
when using the “Small” memory model is from 0x100 to 0x25F. Unfortunately this means that there will
most likely be available RAM in the zero page (from 0x60 to 0x100) that is not being used. In order to use
this part of the RAM, it is necessary to explicitly tell the compiler to allocate variables into the zero page.
This is done with the statement
# pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE
Any variables declared after this statement will be allocated in the zero page. To return to allocating
variables in the default area of RAM, use the statement
# pragma DATA_SEG DEFAULT
you think you have run out of space in RAM for storing variables, this the section to check to see how much
space is being used.
Towards the end of the map file is a section called “DEPENDENCY TREE” that shows a map of how
the various functions in the program call each other. This diagram can be very useful to get an idea of
how deep the program goes with functions calling other functions. As discussed above each function pushes
arguments on the stack and allocates data in the stack space so a dependency tree that shows a very deep
tree of functions calls may indicate potential problems with using too much stack memory.
The dependency tree will also indicate if there are any recursive functions calls where a function is calling
itself. Recursive functions can be used on the JL16 but due to their potential to use up large amounts of
stack space they can be a source of problems. If you see an function flagged in the map file as being called
recursively, first make sure that this is something you intended to do. It is easy to make an error in the
program that results in recursive calls that the programmer is not aware of. Unless there is a very strong
reason to use recursive function calls, it is highly recommended that they be avoided.
For example, ports A, B, and D are defined by variables named PTA, PTB, and PTD. You may perform
operations on these variables just as you would any other.
PTA = 0 xf4 ;
PTD = 5 * PTB + 2;
unsigned char my_var = PTA ;
PTB += 1;
Individual bits in the registers can be accessed using the names defined for each bit. For example, the name
PTA_PTA0 will access the LSB of Port A.
PTB_PTB4 = 1;
if ( PTD_PTD2 == 0) { ... }
Alternatively, you can use bitwise operations such as AND and OR.
bit0 = PTA & 0 x01 ;
bit3 = PTA & 0 x08 ;
When writing individual bits in the registers it is important to be aware that this will cause the micro-
controller to first read the full eight-bit byte, modify the byte, and then write the full byte back. Some of
the internal functions of the microcontroller are affected by reading a register so it is important to be aware
of any potential side effects when accessing the registers.
• Within the brackets you may write one assembly instruction per line.
• Labels must be defined on their own line.
8 Sample Programs
On the computers in the EE 459 lab, in the “JL16 Samples” folder in the EE 459 account are the following
files that may be of interest to those writing C programs. These programs are also available on the EE 459
library web site.
jl16.c This is a template file with the required declaration and initialization code but nothing else. It can
be used as a starting point for writing a program in C.
jl16-0.c A very simple program for showing the micro is working. It loops forever turning bit zero in port
A (PTA0) on and off as rapidly as possible.
jl16-1.c This program reads a switch input and turns an LED on and off.
jl16-2a.c This program counts up and down on a seven-segment display. The program uses nested loops to
implement the counter delay.
jl16-2b.c Similar to jl16-2a.c but shows how to allocate variables into the zero-page of RAM for more
efficient access. See Appendix A for a listing.
jl16-3.c Similar to jl16-2a.c but uses an internal timer and interrupts to implement the delay. Use linker
parameter file jl16-3.prm for this sample program. See Appendix B for a listing.
jl16-4.c Demonstrates interfacing to an LCD display using an 8-bit interface. Puts up a short message on
the display.
jl16-5.c Similar to jl16-4.c but uses a 4-bit interface to the LCD
jl16-6.c This program demonstrates using an RS-232 serial interface to control an LCD display.
jl16-7.c Examples of reading and writing data to an EEPROM using an I2C bus.
jl16-8.c Shows how to stores and retrieves data in the JL16 FLASH memory under program control.
1. If CodeWarrior is not running start it from from the desktop icon and open the project. It can actually
be started any time during the first three steps but must be running in order to do step 4.
2. Connect the programmer’s USB cable to one of the computer’s USB ports. If connecting to a Macintosh
running CodeWarrior via the Parallels virtual machine software, the programmer must be connected
to a USB 1.1 hub first and then the hub is connected to the Macintosh. The programmer will only
work with the Macs if the programmer is connected as a USB 1.1 device and the hub acts to establish
this type of connection.
3. Lift up the lever on the zero-insertion force (ZIF) socket and insert the chip into the socket. Pin one
should be closest to the lever. Push the lever to the down position to lock the chip in. The lever must
be in the down position when the chip is in the socket to make good electrical contact with the pins.
4. Start the CodeWarrior debugger by selecting “Debug” from the Project menu or by clicking on the
icon of a green arrow and bug at the top right above the project box. This will bring up a dialog box
(Fig. 7) that defines the connection between CodeWarrior and the development board.
The first time this is done in a project CodeWarrior has to be told how to establish the connection
to the programmer. At the top, under “Interface Details” CodeWarrior may have figured out that
the USB programmer is present and it will say ”P&E USB MON0 Multilink on USB1” or something
similar. If CodeWarrior has not found the USB programmer the connection interface will be blank
so click on “Add a Connection”. In the “Interface Selection” dialog box, select “P&E USB MON0
Multilink on USB1” and then click “OK”.
Back on the Connection Manager screen in the “Power/Clock Detail” area, set the Device Power to “5
Volts, Provided by P&E Interface”. Also set Device Clock to “Clock Driven by P&E Interface on Pin
13”.
5. Once the connection settings are correct, click on “Contact Target with These Settings. . . ” and it
should open a connection to the programmer and chip.
6. If the debugger finds your chip it should put up dialog saying “Load image contains flash memory
data. Erase and Program flash?” This means the debugger is ready to write the new binary program
data you created when building the application into the microcontroller. Click on “OK” and it will go
through several steps automatically programming and verifying the data.
/*
Some of the RAM on the JL16 is in the range 0 x60 to 0 xff and can be
accessed using the direct addressing mode which is the most efficient way
to get at RAM in the " zero page " of 0 x00 to 0 xff . The rest of RAM is from
0 x100 to 0 x25f and must be accessed using extended addressing . For C it
doesn ’ t make much difference since the compiler will generate the correct
instructions . However to make the best use of assembly code you need to
know where the variables have been stored so you know whether or not direct
addressing can be used . The follwing pragma puts the variables in the zero
page and the wait_100ms routine uses direct addressing .
*/
/*
This pragma sets the area for allocating variables back to default area
of RAM as defined in the PRM file .
*/
up = PTA_PTA1 ;
if ( up ) { // if button is not pressed , up = 1
if (++ cnt > 15) // and we count up in hex
cnt = 0;
}
else { // if button is pressed , up = 0
if ( - - cnt < 0 || cnt > 9) // and we count down in decimal
cnt = 9;
}
}
/* please make sure that you never leave this function */
}
/*
Note : this delay routine only works if the the delay0 and delay1
variables are located in the RAM zero page ( direct addressing used ).
*/
/*
wait_100ms - Delay about 100 msec .
*/
void wait_100ms ( void )
{
asm {
/* Define PTB0 through PTB6 as " Segments " , and bit PTA5 as " RegClock " */
volatile struct {
byte Segments :7; /* PTB0 -6 = Segments */
byte :1; /* PTB7 = unassigned */
} MyPTB @0x0001 ;
# define Segments MyPTB . Segments
volatile struct {
byte :5; /* PTA0 -4 = unassigned */
byte RegClock :1; /* PTA5 = RegClock */
} MyPTA @0x0000 ;
# define RegClock MyPTA . RegClock
/*
Store the 7 - segment display codes in ROM to save on RAM space
*/
/*
The demo board has a 9.8304 MHz clock which makes the processor run
at 2.4576 MHz . We want the timer to interrupt every half second
(2 Hz ) so we need to count clocks to 2.4576 MHz /2 Hz = 1 ,228 ,800. This
is too big for the 16 bit counter register so use the prescaler to
divide the clock by 32 and then count that clock to 38 ,400 (0 x9600 ).
*/
T1SC_PS = 5; // set prescalar for divide by 32
T1SC_TOIE = 1; // enable timer interrupt
T1MOD = 38400; // store modulo value in T1MODH : T1MODL
T1SC_TSTOP = 0; // start timer running
display_digit ( cnt );
# pragma TRAP_PROC
void timerISR ( void )
{
up = PTA_PTA1 ;
if ( up ) { // if button is not pressed , up = 1
if (++ cnt > 15) // and we count up in hex
cnt = 0;
}
else { // if button is pressed , up = 0
if ( - - cnt < 0 || cnt > 9) // and we count down in decimal
cnt = 9;
}
display_digit ( cnt );
}