A Simple Code Warrior Project in C
 
Introduction
 
Programming embedded systems in C is similar to programming for a desktop computer, but there are some additional factors which must be considered in embedded systems. For instance, just as in assembly (see the assembly overview for a quick review), we do our work on a desktop computer, but must design for a target computer. We must therefore know the details about the target system memory, initialization procedure, etc. We also need to transfer the executable file to the target system, such as via a serial port to RAM or FLASH memory. In our C code, we use keywords such as "volatile" that we don't often need to use for desktop systems ("volatile" tells the compiler not to make any assumptions about the data stored at a memory location).
 
There are many references on using C in embedded systems. One good resource is the free, online book by Jonathan Valvano: Developing Embedded Software in C Using ICC11/ICC12/Hiware . Another recommended resource is the book Programming Embedded Systems in C and C++ by M. Barr (1999, O'Reilly), ISBN: 1-56592-354-5. An excellent tutorial for creating C projects using Code Warrior is the pdf file Getting started with the Motorola HCS12 family using Metrowerks CodeWarrior by Frank Voorburg.
 
 
Code Warrior C Project to Flash an LED
 
In this demo we will create a simple Code Warrior project in C that flashes an LED at a fixed rate. While the program is very basic, it does illustrate the key features of setting up a C project in Code Warrior.
 
The steps in building a C project in Code Warrior are much like that of building a project in assembly. We first create text files that have our C code. In our 'c_demo' project below, there are three source C files: (1) 'c_demo.c' which has our main source code; (2) 'Start12.c' which has startup code to initialize the system; and (3) 'vectors.c' which is used to set up the interrupt vectors. There is also a library C file called 'mc9s12dp256.c' which contains register data (using this file saves us having to type in a lot of information). These files are listed and described further below.
 
We also have to add utility files such as DBug12.prm and burner.bbl. These, too, are described below.
 
Once we have our C files and utility files, we compile these. Compilation basically creates assembly code from the C code. For instance, you can see the compiler-generated assembly instructions for 'c_demo.c' if you look at the 'c_demo.lst' file. Just as in assembly, the assembly code is then used to create a .o object file, which is then linked to create a .abs file. This is then converted to an s1 record, and downloaded to the target system.
 
As with assembly, we will use stationery to simplify the work. The stationery we use '_ee3376_C' should be located on your hard drive; if needed, a zip file of the stationery can be downloaded here (_ee3376_C.zip).
 
The screenshot below shows the project (c_demo.mcp) which is created using the _ee3376_C stationery, and which will be run in this demo.
 
 
 
 
 
Note that in the C programs and files below, we use "0x" to signify a hex number. If there is no leading character, the number is decimal (so 0x10 = 16). This number convention is common for C programs. The number $EB in an assembly program is now listed as 0xEB in a
C program.
 
Our main C file 'c_demo.c'
 
The main file which contains most of our functional code is 'c_demo.c', as shown below. The other files are basically setup files that support the actions of our main code. Note that you need to add the following Port J code for Rev. E boards to enable the Port B LEDs (add the code at the beginning of the program):
line 1: DDRJ = 0xFF; /* set Port J ddr to output */
line 2: PTJ = 0x00; /* set Port J low to enable Port B LEDs */.
 
/****************************************************************
*
* UTEP EE3376
* c_demo.c
* 3 Sept 2004
*
* This code will flash LED0 tied to Port B (PB0) every half second
* (half second on, half second off). The code below is for an
* oscillator clock (OSCCLK) running at 4 MHz, as it does on the
* Dragon12 board. The program is based on the RTI interrupt, and
* the interrupt vector table is remapped to $3E00 since we are
* running DBug12.
*
* Partially based on code from Frank Voorburg, www.feaser.com
*
****************************************************************/
 
 
/****************************************************************
* Include files
****************************************************************/
#include <mc9s12dp256.h>              /* standard CodeWarrior file with reg. defs */
 
 
/***************************************************************
* Function prototypes
****************************************************************/
__interrupt void RealTimeInterrupt(void);
 
 
/****************************************************************
* Local data definitions
****************************************************************/
static volatile unsigned char rtiCnt;        /* to count number of RTIs */
 
void main(void)
{
  DDRB = 0x01;                /* set portb pin 0 to output */
  PORTB = 0x01;                /* turn on LED connected to PB0 */
  RTICTL = 0x50;            /* 1/(4MHz/2^14) = 4.096 ms per RTI */
  CRGINT |= 0x80;            /* enable RTI interrupt */
  rtiCnt = 0;                /* RTI counter to 0 */
  asm("cli");                /* enable global interrupts */
 
  for (;;) {                /* infinite loop */
    if (rtiCnt == 122)
    {                        /* 500 ms passed? */
      PORTB = ~PORTB;        /* toggle LED on/off */
      rtiCnt = 0;            /* reset rti counter */
    }
  }
} /* end main() */
 
 
__interrupt void RealTimeInterrupt(void)
{
  rtiCnt++;                    /* increment the counter        */
  CRGFLG = 0x80;            /* clear rti flag               */
} /*end RealTimeInterrupt() */
 
 
/***** end lab9_main.c ****/
 
 
 
Interrupt file 'vectors.c'
 
The project code is based on using the Real Time Interrupt (RTI), so we need to set up an interrupt vector table. Note that since we are using chips running DBug12, we must map the interrupt vectors to start in RAM at $3E00 (see Memory Map for the HCS12 for more info). This is accomplished in the code below, and the PRM (.prm) file further handles the memory mappings.
 
/****************************************************************
*
* UTEP EE3376
* vectors.c
* 24 Jan 2004
*
* Based on vector code from Frank Voorburg, www.feaser.com
*
****************************************************************/
 
/***************************************************************
* Function prototypes
****************************************************************/
__interrupt void RealTimeInterrupt(void);
 
 
/****************************************************************************************
* Type definitions
****************************************************************************************/
typedef void (*tIsrFunc)(void);                       /* ISR function type             */
 
 
/****************************************************************************************
* Global constants
****************************************************************************************/
const tIsrFunc _vectab[] @0x3e00 =
{
  (tIsrFunc)0,                                        /* Reserved 0xFF80               */
  (tIsrFunc)0,                                        /* Reserved 0xFF82               */
  (tIsrFunc)0,                                        /* Reserved 0xFF84               */
  (tIsrFunc)0,                                        /* Reserved 0xFF86               */
  (tIsrFunc)0,                                        /* Reserved 0xFF88               */
  (tIsrFunc)0,                                        /* Reserved 0xFF8A               */
  (tIsrFunc)0,                                        /* PWM Emergency Shutdown 0xFF8C */
  (tIsrFunc)0,                                        /* PortP Interrupt 0xFF8E        */
  (tIsrFunc)0,                                        /* MSCAN4 Transmit 0xFF90        */
  (tIsrFunc)0,                                        /* MSCAN4 Receive 0xFF92         */
  (tIsrFunc)0,                                        /* MSCAN4 Errors 0xFF94          */
  (tIsrFunc)0,                                        /* MSCAN4 WakeUp 0xFF96          */
  (tIsrFunc)0,                                        /* MSCAN3 Transmit 0xFF98        */
  (tIsrFunc)0,                                        /* MSCAN3 Receive 0xFF9A         */
  (tIsrFunc)0,                                        /* MSCAN3 Errors 0xFF9C          */
  (tIsrFunc)0,                                        /* MSCAN3 WakeUp 0xFF9E          */
  (tIsrFunc)0,                                        /* MSCAN2 Transmit 0xFFA0        */
  (tIsrFunc)0,                                        /* MSCAN2 Receive 0xFFA2         */
  (tIsrFunc)0,                                        /* MSCAN2 Errors 0xFFA4          */
  (tIsrFunc)0,                                        /* MSCAN2 WakeUp 0xFFA6          */
  (tIsrFunc)0,                                        /* MSCAN1 Transmit 0xFFA8        */
  (tIsrFunc)0,                                        /* MSCAN1 Receive 0xFFAA         */
  (tIsrFunc)0,                                        /* MSCAN1 Errors 0xFFAC          */
  (tIsrFunc)0,                                        /* MSCAN1 WakeUp 0xFFAE          */
  (tIsrFunc)0,                                        /* MSCAN0 Transmit 0xFFB0        */
  (tIsrFunc)0,                                        /* MSCAN0 Receive 0xFFB2         */
  (tIsrFunc)0,                                        /* MSCAN0 Errors 0xFFB4          */
  (tIsrFunc)0,                                        /* MSCAN0 WakeUp 0xFFB6          */
  (tIsrFunc)0,                                        /* Flash 0xFFB8                  */
  (tIsrFunc)0,                                        /* Eeprom WakeUp 0xFFBA          */
  (tIsrFunc)0,                                        /* SPI2  0xFFBC                  */
  (tIsrFunc)0,                                        /* SPI1  0xFFBE                  */
  (tIsrFunc)0,                                        /* IIC Bus 0xFFC0                */
  (tIsrFunc)0,                                        /* DLC 0xFFC2                    */
  (tIsrFunc)0,                                        /* SCME 0xFFC4                   */
  (tIsrFunc)0,                                        /* CRG Lock 0xFFC6               */
  (tIsrFunc)0,                                        /* Pulse AccB Overflow 0xFFC8    */
  (tIsrFunc)0,                                        /* Mod Down Cnt Underflow 0xFFCA */
  (tIsrFunc)0,                                        /* PortH Interrupt 0xFFCC        */
  (tIsrFunc)0,                                        /* PortJ Interrupt 0xFFCE        */
  (tIsrFunc)0,                                        /* ATD1 0xFFD0                   */
  (tIsrFunc)0,                                        /* ATD0 0xFFD2                   */
  (tIsrFunc)0,                                        /* SCI1 0xFFD4                   */
  (tIsrFunc)0,                                        /* SCI0 0xFFD6                   */
  (tIsrFunc)0,                                        /* SPI0 0xFFD8                   */
  (tIsrFunc)0,                                        /* Pulse AccA Input Edge 0xFFDA  */
  (tIsrFunc)0,                                        /* Pulse AccA Overflow 0xFFDC    */
  (tIsrFunc)0,                                        /* Timer Overflow 0xFFDE         */
  (tIsrFunc)0,                                        /* Timer 7 0xFFE0                */
  (tIsrFunc)0,                                        /* Timer 6 0xFFE2                */
  (tIsrFunc)0,                                        /* Timer 5 0xFFE4                */
  (tIsrFunc)0,                                        /* Timer 4 0xFFE6                */
  (tIsrFunc)0,                                        /* Timer 3 0xFFE8                */
  (tIsrFunc)0,                                        /* Timer 2 0xFFEA                */
  (tIsrFunc)0,                                        /* Timer 1 0xFFEC                */
  (tIsrFunc)0,                                        /* Timer 0 0xFFEE                */
  (tIsrFunc)RealTimeInterrupt,                        /* RTI 0xFFF0                    */
  (tIsrFunc)0,                                        /* IRQ 0xFFF2                    */
  (tIsrFunc)0,                                        /* XIRQ 0xFFF4                   */
  (tIsrFunc)0,                                        /* SWI 0xFFF6                    */
  (tIsrFunc)0,                                        /* Unimpl Instr Trap 0xFFF8      */
  (tIsrFunc)0,                                        /* COP Failure Reset(N/A) 0xFFFA */
  (tIsrFunc)0,                                        /* COP Clk Mon Fail(N/A) 0xFFFC  */
  (tIsrFunc)0                                         /* Reset(N/A) 0xFFFE             */
};
 
/* end lab9_vectors.c */
 
 
Linker parameters file 'Dbug12.prm'
 
Unlike the absolute assembly projects we did in previous labs where we explicitly defined memory locations in the source code, when we use C (or relocatable assembly) we must tell the linker where to map the code using a PRM file. That is, the PRM file will give the linker specific addresses where the executable code will reside in our target embedded system. The memory location information in the PRM file depends on the specific chip, running mode, etc. For this project where we are running DBug12 on our chips, we can use the PRM file called DBug12.prm. The code is shown below. The Smart Linker Manual gives further information about setting up PRM files.
DBug12.prm:
 
NAMES
END
 
SECTIONS
    RAM        = READ_WRITE 0x1000 TO 0x17FF;
    PSEUDO_ROM = READ_ONLY  0x1800 TO 0x3BFF;
END
 
PLACEMENT
    _PRESTART, STARTUP,
    ROM_VAR, STRINGS,
    NON_BANKED,DEFAULT_ROM,
    COPY                   INTO   PSEUDO_ROM;
    DEFAULT_RAM            INTO   RAM;
END
 
STACKSIZE 0x100
 
Burner file 'burner.bbl'
 
Finally, once we have the executable 'c_demo.abs', we must convert it to a form suitable for loading into our Dragon12 board. To do this, we create an S1 record using the burner.bbl (bbl = batch burner language) file shown below. The burner will create a file called 'c_demo.abs.s1' that we will actually load into the Dragon12 using the DBug12 "load" command as we have done in previous labs.
 
 
Burner.bbl:
 
OPENFILE "%ABS_FILE%.s1"
format=motorola
busWidth=1
origin=0
len=0x10000
destination=0
SRECORD=S1
SENDBYTE 1 "%ABS_FILE%"
CLOSE
 
 
Target Settings
 
In addition to the code above, there are a number of target settings to be entered. These are already set up in the stationery we used, but are noted below for reference. The main selections are:
 
    * Target Settings: name="c_demo", and choose "linker for hc12"
 
   * Access Paths: Click on the "User Paths" button, verify path is set to {Project} and without recursion (ie, no small folder displayed). Click on the "System Paths" button, and set three paths as shown in the screenshot below. Note that {Project} refers to the base directory the project is in, and that {Compiler} refers to the base directory where the CodeWarrior C compiler is installed. The User Path gives the directory searched for ... #include "file.h"... type statements, and the System Path gives the directories searched for ...#include < file > ... type statements.
 
      
 
      
 
    * Build Extras, Runtime Settings, File Mappings, Source Trees and Assembler for HC12 can be left with the default values.
 
    * In the Burner for HC12 panel, set the options to "-Ns=78p" (Graphically: In the Options - Output tab, select "No S Records" and then in the pop-up selections, deselect the "No S-9 record" and "No S-0 record" so that only "No Path in S-0 Record", "No S-8 record" and "No S-7 record" boxes are selected). Then, in the Burner box selection (still in the Burner Panel), in the Input/Output tab, type in "%ABS_FILE%" for input file, select the "File" radio button and call the output file "%ABS_FILE%.s1". Then click on the Content tab, and choose "Format=Motorola S", "Data Bus = 1 Byte" , "S Record Configuration = S1" and the "Range to Copy, Origin = 0, Length = 10000, Destination Offset = 0". Click on the "Command File" tab and copy the contents to create the burner.bbl file.
 
    * In the compiler panel, choose "Options" and the "output" tab, and then select "Generate a listing". This will create a file called "c_demo.lst" which shows the assembly instructions corresponding to the C code. The listing file can be very helpful when troubleshooting.
 
    * Leave the Importer Panels as is.
 
   * In the Linker Panel, set options to "-B -M" (Options -> Output: Generate S record File, Generate Map File).
 
 
 
Building and Running the Project
 
The source code and parameters needed to build this project are all taken care of for you in the stationery. So, just open Code Warrior and create a new project called 'c_demo' using the _ee3376_C stationery. Make sure to save your project in your network drive as you did with assembly projects. All of the files, source code, target settings, etc. are already set up for you. Just click on the "make" button, and everything should compile and build fine.
 
Once the project is built, download onto the Dragon12 using the "load" command to send the file 'c_demo.abs.s1' to the board. Once loaded on the Dragon12, to start the program, type in ">g 1829". LED0 should begin flashing at a rate of 1/2 second. Note that you can see the entry point where the program starts is $1829 by looking at the MAP file, 'c_demo.map', to see the line "Entry point: 0x1829 (_Startup) ".
 
Note that the approach used here to create an executable file from C source code is appropriate for small projects. For larger projects, there are many other strategies that are used. For instance, it is common to use an include header (.h) file that lists all of the registers that might be used for a given chip. Thus, the programmer doesn't have to write these from scratch. An example of a C include file for the MC9S12DP256B chip we use in this lab is shown here.
 
 
C Programming for the HCS12