Encoder Front Page
SRS Home | Front Page | Monthly Issue | Index
Google
Search WWW Search seattlerobotics.org

GCC and the Motorola 681x

Barrie B. Campbell

It didn't take long before I got sick of writing all my '12 apps in assembly. It didn't help that the assembler/loader I was using only ran under DOS. Soon I was looking for a better solution.

Since I know C/C++, that was the logical path to follow. I could have learned Sbasic, or another language, but long term, I wanted to know more about C/C++, and I already had a basic understanding of the language.

I looked for a compiler, and eventually decided on ImageCraft's ICC. However, the $200 price tag was a bit more than I could afford. I eventually ran into the GCC for 6812 port by Stephane Carrez at http://home.worldnet.fr/~stcarrez/m68hc11_port.html. As GCC is free, and widely used, I was intrigued.

Note that the port web page is titled "GNU Development Chain for 68HC11 & 68HC12". Indeed the port is not just the GCC compiler, but the GNU assembler, linker, and a host of other tools. You could use GAS and the other tools to create programs for your board as well.

Getting Started

The first step is downloading and installing the tools. This process is well documented on the download and install pages. Make sure you download and install all the packages, Binutils, GCC, GDB, Newlib, and the examples. I have built the sources without trouble a number of times. I would recommend that you install from source unless you are running a Windows system, in which case, install the zip version. A graphical Windows installation is in the works and should be released shortly.

You will also need a way to communicate to the micro and transfer files to it. I use CKermit, and DBug12. CKermit is available from http://www.columbia.edu/kermit.

Finally, make sure that the binaries are in your path.

The Basics

A simple program will get us started:

/* The Earl 
first attempt 
*/ 

extern volatile char ports[]; 
#define COPCTL  0x16 
 
#define IO_BASE	0 
#define PORTA	*(volatile unsigned char *)(IO_BASE + 0x00) 
#define PORTB	*(volatile unsigned char *)(IO_BASE + 0x01) 
#define DDRA    *(volatile unsigned char *)(IO_BASE + 0x02) 
#define DDRB  	*(volatile unsigned char *)(IO_BASE + 0x03) 

#define OUT     0x55 
 
int main() 
{ 
  unsigned long int x; 
  DDRA=OUT; 
  PORTA=OUT; 
  while (1) { 
    x=0xFFFFF; 
    while (x--) 
    PORTA=0; 
    x=0x8FFF; 
    while (x--) 
    PORTA=OUT; 
  } 
  return x; 
} 
 
void __premain() 
{ 
  ports[COPCTL]=0; 
  __asm__ __volatile__ ("clr 0x16"); 
 
} 

Ok, Lets break it down...


extern volatile char ports[]; 
#define COPCTL  0x16 
 
#define IO_BASE	0 
#define PORTA	*(volatile unsigned char *)(IO_BASE + 0x00) 
#define PORTB	*(volatile unsigned char *)(IO_BASE + 0x01) 
#define DDRA    *(volatile unsigned char *)(IO_BASE + 0x02) 
#define DDRB  	*(volatile unsigned char *)(IO_BASE + 0x03) 

Ignoring the comment, there are (among others) two ways to read and write to known addresses. The first is used by Stephane in the examples. This defines the IO register space as an array, then uses offsets to address individual registers. Lower in the program, I use ports[0x16] to access the COPCTL register. The base address of ports is set at compile time.

I tend to use the second method:
#define PORTA *(volatile unsigned char *)(0x00)
This defines a pointer to a char (8 bits) whose address is 0. This allows me to write things like:
PORTA=0;
16 bit registers like the timers can be addressed as "short int".

These definitions would be more appropriate in a header file included in all of your projects, but we are just getting started.


#define OUT     0x55 
 
int main() 
{ 
  unsigned long int x; 

These lines define the constant OUT as 0x55, start our main() function, and define a 32 bit integer x.


  DDRA=OUT; 
  PORTA=OUT; 

This line takes advantage of our macros for simplification. After passing through the preprocessor, these lines would look like:

*(volatile unsigned char *)(0x00)=0x55;
*(volatile unsigned char *)(0x02)=0x55;

These simply write 0x55 (the value of OUT) to the io registers DDRA and PORTA at 0x2 and 0 respectively. This sets alternating lines of PORTA on the micro.


  while (1) { 
    x=0xFFFFF; 
    while (x--) 
      PORTA=0; 
    x=0x8FFF; 
    while (x--) 
      PORTA=OUT; 
  } 

Here is where the "work" is done. while(1) runs forever. We then set x to a large value. while(x--) runs until x=0, in the mean time, we turn off PORTA. When x =0, we set x to 0x8FFF, then waste some more time. This time however, we turn PORTA on! At x=0, we jump back to the top (remember the while(1)?) and do it all over again. When the processor reaches this loop, it will blink an LED at A1 forever (or at least until an interrupt or reset).


  return x; 
} 

Nothing interesting here, since we declared main() as an int, we have to return something else GCC will give us a warning. The brace closes main().


void __premain() 
{ 
  ports[COPCTL]=0; 
  __asm__ __volatile__ ("clr 0x16"); 
 
} 

We must define __premain(), else the default libraries will do strange things like enable interrupts and NOT disable the COP timer. The two lines do exactly the same thing, later we will see that both generate the same instructions. Since I was just starting, I wanted to make sure I disabled the COP timer, hence the repetition. This also illustrates embedding assembly in C. We will visit this again later in more depth.

Compiling

So now we have first.c, but how do we get that down to our board?

First we have to compile our file. To do so, we have to tell GCC where to load our program in memory. GCC uses a default memory map unless we explicitly tell it to use ours. We start by creating a memory.x file that contains a definition of the map of our chip. For the 6812B32 in single chip mode without external memory, that would look something like:


MEMORY
{
  page0 (rwx) : ORIGIN = 0x0800, LENGTH = 1K 
  text  (rx)  : ORIGIN = 0x8000, LENGTH = 32K
  data        : ORIGIN = 0x0D00, LENGTH = 768
}
/* Setup the stack on the top of the data memory bank.  */
PROVIDE (_stack = 0x0C00);

MEMORY tells the linker we are defining our memory spaces. page0 is volatile memory used for variables and the stack. This needs to be RAM. text is the program code area. This can be FLASH, EEPROM, PROM or a similar non-volatile, memory. It does not have to be writeable during program execution. data contains initialized variables. It can be in FLASH, or another write-once area. I put it in EEPROM so that I can change the values without re-flashing the program area. Lastly, we have to provide a stack pointer. The stack grows downward for both the '11 and '12, but the '12 stack pointer points to the last used memory location, whereas the '11 points to the next free, keep this in mind when setting the stack pointer.

Next, we must call GCC to create our program. GCC by default creates an ELF file, a file that GDB needs to debug our program. We will create the ELF program, then convert it to S19 to download to our board.

To create an ELF file, we use the following command:


m6812-elf-gcc -g -Wl,-defsym,ports=0x0,-u,-mm68hc12elfb -o first.elf first.c


m6812-elf-gcc is the name of the compiler, it may be different depending on your install of GCC. -g includes debugging info for GDB, it doesn't change the size of our resulting S19 file, but makes it easier to debug on the GDB simulator.-Wl starts a comma separated list of linker options. The first, -defsym,ports=0x0 tells the linker to set the base address of ports[] to 0x0, allowing us to use the ports[COPCTL]=0; construct as above. Since this is set at link time, I prefer to define each io port as a separate entity. Next, we tell GCC to use the linker script that will include our memory.x file with -u,-mm68hc12elfb. Again, this is specific to the '12, use mm68hc11elfb for the '11. -o first.elf names our output file. Lastly, we must tell GCC what to compile.

Now that we have our first.elf file, we need to convert it to an S19 file. Objcopy does this with:


m6812-elf-objcopy -O srec first.elf first.s19


Again, the name of the program will depend on your install options.

Disassembly

We can also dump the assembled code from our ELF file. This is useful to see exactly what the compiler is doing with our source.


m6812-elf-objdump -xdC first.elf >first.dump


This dumps just about everything from the compiled source. I will just touch on the sections.


first.elf:     file format elf32-m68hc12
first.elf
architecture: m68hc12, flags 0x00000113:
HAS_RELOC, EXEC_P, HAS_SYMS, D_PAGED
start address 0x00008000

This first part tells us the format of the file, some compilation flags and the start address (0x800)


Program Header:
    LOAD off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**12
         filesz 0x00000d00 memsz 0x00000d04 flags rw-
    LOAD off    0x00001000 vaddr 0x00008000 paddr 0x00008000 align 2**12
         filesz 0x000000c3 memsz 0x000000c3 flags r-x
    LOAD off    0x00001d00 vaddr 0x00000d00 paddr 0x000080c3 align 2**12
         filesz 0x00000000 memsz 0x00000000 flags rw-

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000c3  00008000  00008000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000d00  000080c3  00001d00  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  00000d00  00000d00  00000d00  2**0
                  ALLOC
  3 .ctors        00000000  00000d04  00000d04  00001d00  2**0
                  CONTENTS
  4 .dtors        00000000  00000d04  00000d04  00001d00  2**0
                  CONTENTS
  5 .comment      00000037  00000000  00000000  00001d00  2**0
                  CONTENTS, READONLY

Here we have our memory sections, where they are, and their size. Here we need to make sure we don't overlap. We can also infer that our start address is the beginning of the .text section.


SYMBOL TABLE:
00000000 l    df *ABS*	00000000 crt0.s
00000000 l    df *ABS*	00000000 ./config/m68hc11/m68hc11-crt0.S

~SNIP~

00000d00 g       .bss	00000000 _.d1
00000d02 g       .bss	00000000 _.d2
00000000         *UND*	00000000 -mm68hc12elfb
000080c3 g       .text	00000000 _etext
00000004 g       *ABS*	00000000 __bss_size
00000d04 g       .dtors	00000000 __DTOR_END__
000080c3 g       *ABS*	00000000 __data_image
00000d00 g       .data	00000000 __data_section_start
00008018 g       .text	00000000 __init_bss_section
000080b7 g     F .text	00000007 __premain
00000d04 g       .ctors	00000000 __CTOR_LIST__
00000000 g       *ABS*	00000000 __data_section_size
00008000 g       .text	00000000 _start
00000d00 g       .bss	00000000 __bss_start
00008031 g     F .text	00000086 main
00000d04 g       .ctors	00000000 __CTOR_END__
00008006 g       .text	00000000 __map_data_section
000080c3 g       *ABS*	00000000 __data_image_end
00000d04 g       .dtors	00000000 __DTOR_LIST__
00000000 g       *ABS*	00000000 ports
00000d00 g       .data	00000000 _edata
000080be  w      .text	00000000 exit
000080be g       .text	00000000 _exit
00000c00 g       *ABS*	00000000 _stack

The interesting parts of the symbol table are near the end. I only included some of the highlights. Symbols like __premain tell us where functions in our program start (main() starts at 0x8031). Symbols like _data, _edata and the like tell us where our memory sections start and end.


Disassembly of section .text:

00008000 <_start>:
    8000:	cf 0c 00    	lds	#c00 <_stack>
    8003:	16 80 b7    	jsr	80b7 <__premain>

00008006 <__map_data_section>:
    8006:	cc 00 00    	ldd	#0 <__data_section_size>
    8009:	27 0d       	beq	8018 <__init_bss_section>
    800b:	ce 80 c3    	ldx	#80c3 <__data_image>
    800e:	cd 0d 00    	ldy	#d00 <__data_section_start>

00008011 :
    8011:	18 0a 30 70 	movb	1,X+, 1,Y+
    8015:	04 34 f9    	dbne	D,8011 

00008018 <__init_bss_section>:
    8018:	cc 00 04    	ldd	#4 <__bss_size>
    801b:	27 08       	beq	8025 
    801d:	ce 0d 00    	ldx	#d00 <__data_section_start>

00008020 :
    8020:	69 30       	clr	1,X+
    8022:	04 34 fb    	dbne	D,8020 

00008025 :
    8025:	16 80 31    	jsr	8031 
00008028 : 8028: 16 80 be jsr 80be <_exit> 802b: 20 fb bra 8028 802d: 80 06 suba #6 802f: 80 18 suba #24 00008031
: 8031: 18 01 ae 0d movw d00 <__data_section_start>, 2,-SP 8035: 00 8036: 18 01 ae 0d movw d02 <_.d2>, 2,-SP 803a: 02 803b: ce 00 02 ldx #2 <__data_section_size+0x2> 803e: 18 08 00 55 movb #85, 0,X 8042: ce 00 00 ldx #0 <__data_section_size> 8045: 18 08 00 55 movb #85, 0,X 8049: 18 03 ff fe movw #fffe <__data_image+0x7f3b>, d02 <_.d2> 804d: 0d 02 804f: 18 03 00 0f movw #f <__bss_size+0xb>, d00 <__data_section_start> 8053: 0d 00 8055: ce 00 00 ldx #0 <__data_section_size> 8058: b7 c5 xgdx 805a: 6b 00 stab 0,X 805c: b7 c5 xgdx 805e: fc 0d 02 ldd d02 <_.d2> 8061: fe 0d 00 ldx d00 <__data_section_start> 8064: 83 00 01 subd #1 <__data_section_size+0x1> 8067: 24 01 bcc 806a 8069: 09 dex 806a: 7c 0d 02 std d02 <_.d2> 806d: 7e 0d 00 stx d00 <__data_section_start> 8070: fe 0d 00 ldx d00 <__data_section_start> 8073: 8e ff ff cpx #ffff <__data_image+0x7f3c> 8076: 26 dd bne 8055 8078: fe 0d 02 ldx d02 <_.d2> 807b: 8e ff ff cpx #ffff <__data_image+0x7f3c> 807e: 26 d5 bne 8055 8080: 18 03 8f fe movw #8ffe <__data_image+0xf3b>, d02 <_.d2> 8084: 0d 02 8086: 79 0d 01 clr d01 <__data_section_start+0x1> 8089: 79 0d 00 clr d00 <__data_section_start> 808c: ce 00 00 ldx #0 <__data_section_size> 808f: 18 08 00 55 movb #85, 0,X 8093: fc 0d 02 ldd d02 <_.d2> 8096: fe 0d 00 ldx d00 <__data_section_start> 8099: 83 00 01 subd #1 <__data_section_size+0x1> 809c: 24 01 bcc 809f 809e: 09 dex 809f: 7c 0d 02 std d02 <_.d2> 80a2: 7e 0d 00 stx d00 <__data_section_start> 80a5: fe 0d 00 ldx d00 <__data_section_start> 80a8: 8e ff ff cpx #ffff <__data_image+0x7f3c> 80ab: 26 df bne 808c 80ad: fe 0d 02 ldx d02 <_.d2> 80b0: 8e ff ff cpx #ffff <__data_image+0x7f3c> 80b3: 26 d7 bne 808c 80b5: 20 92 bra 8049 000080b7 <__premain>: 80b7: 79 00 16 clr 16 <__bss_size+0x12> 80ba: 79 00 16 clr 16 <__bss_size+0x12> 80bd: 3d rts 000080be <_exit>: 80be: 10 ef cli 80c0: 3e wai 80c1: 20 fb bra 80be <_exit>

Hmm, I wonder what this section called "Disassembly of section .text:" is. Some of the routines like _exit, fatal are built in to the C runtime environment. Note that the stack pointer is set to 0xC00 as we specified in memory.x. Also note that __premain clears the COPCTL register (0x16) twice as specified, and that ports[COPCTL]=0 and asm(clr 0x16) generate the same instructions.

A caveat, the compiler can't yet optimize


    803b:	ce 00 02    	ldx	#2 <__data_section_size+0x2>
    803e:	18 08 00 55 	movb	#85, 0,X

to


    movb	#85, 0x2

Loading

We also want to load our micro. I use C-Kermit. Follow the install instructions that come with the package.

Fire up kermit, and set your run environment to connect with the DBug12 monitor on your micro.

You can dump the S19 file to the micro with:


cat first.s19 >/dev/ttyS0

Of course you can't just take the files and load the FLASH without the proper programming voltage. You probably will want to use the bootload mode of DBug12 for that, or a BDM pod. I modified the memory.x file to put the entire program in RAM, then loaded it and ran it. Be careful doing this that you don't overlap memory sections. Setting the stack pointer to the middle of .text is a bad thing as well (don't ask...).

More Fun

The ELF files are able to run under the GDB port as well, but you can't hook LED's to GDB and have them flash. I will write another article on debugging with GDB when I do more of it.

Look at the examples from the port web page. They contain basic routines to do serial IO, A to D, and a host of other things. Keep in mind that these are written for the '11, and will not work as is for the '12. The timer and A to D routines as well use 8 bit accesses, which will yield inconsistent results on the 16 bit '12, redefine these registers as "short int" to read the whole word. I am working on a set of libraries for the '12, but have not finished them.

Assembly

I promised you a deeper look at using assembly in GCC.

In GCC, assembly can be written inside an __asm__() call. We saw a simple example above. GCC wants to have information about the input, output and side-effects of asm statements. In our above example, we write a constant to a volatile memory location. Since there are no input or output parameters, and we already told GCC that ports[COPCTL] was volatile, we don't need to list any of this information. However, more involved instructions require more effort. Our references are from the GCC documentation on Stephane's web page, the main GCC documentation may not have the latest information on the port. The important references are "Assembler Instructions with C Expression Operands" and "[Operand] Constraints for Particular Machines" (the 681x section) from the GCC manual. The constraints document is especially important.

If we want to call a subroutine that starts at 0xF684 with an 8 bit character in register B as and argument, we use the following:


__asm__ __volatile__ ("ldx 0xF684 \n jsr 0,x" : "+d" (character) : "d" (character) : "x","y");

__asm__ tells GCC that this is an assembly command. __volatile__ prevents GCC from "optimizing" our code (ie. removing it, moving it etc.). ldx 0xF684 \n jsr 0,x is our assembly instruction. We need the \n to insert a newline for the assembler. The first colon separates the "assembler template" from the "first output operand constraint". The first (and only) output operand constraint is "+d" (character). This tells GCC that after the execution of the ASM instruction, it should get the value of the C variable character from register D. The + sign means that the variable is both an input and an output. The second colon separates the output and input operand constraints. "d" (character) tells GCC that character needs to be in register D before the ASM is called. Finally, after the third colon, "x","y" tell GCC that registers X and Y are "clobbered" by the ASM instruction. These are called the "clobber constraints".

The above ASM instruction calls putchar() as defined for DBug12 versions 2.x. Since DBug12 uses the same argument passing rules, this function can be called using C as:


((char (*)(char))*(short *)(0xF684)) (character)

See the application note titled "Using The Callable Routines In D-Bug12" from Motorola's web site. A study of the printf() function will help us delve deeper in.


__asm__ __volatile__ ("pshx \n ldx 0xF686 \n jsr 0,x \n pulx" : "=d"(print) : "d"(the),"x" (the2) : "y");

Or


((int (*)(char *,...))*(short *)(0xF686)) ("The Earl is: %s\n\r","KING!")

The beginning is the same, but we have 2 input operands. Note that we have to pull our argument off the stack after the subroutine returns. Again, I like to use the C definition, especially for printf() because in ASM, we would have to try to accommodate that printf() can take different numbers of arguments. More information is available from the GCC documentation. This would be very useful for defining routines that use some of the more esoteric instructions especially for the '12 like TBL and WAV.