Monday, December 31, 2007

blinker1

Blinking an led. Part 1.

The Luminary Micro Stellaris (LMI) eval boards I have so far all come with a CDROM full of goodies. The most important of course is the documentation. And the two most important documents are the users manual for the eval board and the datasheet for the chip.

The schematic for the LM3S6965 evaluation board shows that the LED is connected to the PF0/PWM0 I/O pin. For strictly "blinking" the led this does not matter but for completeness the next page of schematic shows that the led is between the I/O pin and ground so to turn on the led we must turn the output on. To turn off the led turn the output off.

Switching to the data sheet for the 6965. LMI uses the term GPIO, general purpose I/O. Section 9.2 makes life easy, for starters you must enable the clock to the appropriate GPIO in the RCGC2 register, then you need to use the GPIO registers to configure the pin to do what you want it to do.

So lets get RCGC2 out of the way. This is in the System Control section of the datasheet. Section 6.2 states that the System Control base address is 0x400FE000. Table 6-1 shows the register map and RCGC2 is at offset 0x108. So this means the address to the RCGC2 register is 0x400FE108. In this particular datasheet had we gone straight to the RCGC2 register definition, both the base address and offset are given.
Seems like every undefined bit has the warning from LMI that you should preserve the state of this bit. Arguments could be made either way, but since I easily bricked my LM3S811 by not paying attention to other bits I am not sold on the idea of looking at these configuration registers as bits and not whole registers.

So we are going to need to set GPIOF in RCGC2 to enable that peripheral. Since setting and clearing of bits in registers is going to happen often we should make some functions to reuse. Arguably a macro is better here, but I am not macro savvy, probably because that means you have to adopt a particular assembler or remember syntax for each of the assemblers you use.

So a function that is compatible with C that takes the address of the register as the first argument (r0) and the second argument (r1) has the bits set that we want to set in the register at that address. Basically read the memory location, do a bitwise or and write it back to the memory location. To clear bits we use the bic instruction which you send it the bits you want to clear instead of the bits you want to set.


.thumb_func
PUTGETSET:
ldr r2,[r0]
orr r2,r1
str r2,[r0]
bx lr

.thumb_func
PUTGETRESET:
ldr r2,[r0]
bic r2,r1
str r2,[r0]
bx lr


Okay we can move forward, loading RCGC2:


ldr r0,=0x400FE108 ;@ SYS_CONTROL+RCGC2
mov r1,#0x0x000020 ;@ GPIO_F
bl PUTGETSET


Now we need to configure the GPIO pin. We want this pin to be a plain and simple GPIO, LMI has provided table 9-1 to make our lives easier. For a digital output we can quickly see that we need AFSEL 0, DIR 1, ODR 0, and DEN 1. If this is your first time with this part it is a good idea to look up each register. Hmmm, its not actually spelled out but each of these configuration registers acts on the 8 bits in that register (PF7 down to PF0), so PF0 is the 0x00000001 bit in each of these registers.

Lets get the first four out of the way:


ldr r0,=0x400FE108 ;@ SYS_CONTROL+RCGC2
mov r1,#0x00000020 ;@ GPIO_F
bl PUTGETSET
mov r1,#0x000000001 ;@ pin zero
ldr r0,=0x40025420 ;@ GPIO_F_BASE+AFSEL
bl PUTGETRESET
ldr r0,=0x40025400 ;@ GPIO_F_BASE+DIR
bl PUTGETSET
ldr r0,=0x4002550C ;@ GPIO_F_BASE+ODR
bl PUTGETRESET
ldr r0,=0x4002551C ;@ GPIO_F_BASE+DEN
bl PUTGETSET


For sake of example and argument I am going to assume quite a few things. Coming out of reset we assume the datasheet is correct in describing the state of the registers. And the reset state of the PUR, PDR, DR2R, DR4R, DR8R and SLR registers is probably fine for what we are trying to do here. Likewise when looking at the AFSEL register it mentions the GPIO Lock and Commit, we also assume these are set as-desired right after reset. For general purpose code you should probably manage the PDR, PUR, and DRxR registers at a minimum. Note, I dont think LMI's example board support package touches GPIO Lock or Commit but they do modify AFSEL, so they assume those are set as-desired.

So...To blink the led we need to use the GPIODATA register. This is the only tricky register, bits 9 down to 2 of the ADDRESS are a mask that enables the output bits you want to access, this is pretty neat actually because you can be sloppy. Had they done this everywhere we wouldnt need to read-modify-write so much, sigh. So bit 9 of the address we write to is related to PF7 and bit 2 of the address relates to PF0. If I were to write to this in C I would do something like:


PUT32(GPIO_F_BASE+GPIO_DATA+(pin<<2),data);


Since this is assembler and its all hardcoded we can just figure it out. Base address is 0x40025000, GPIODATA offset is 0x000. And PF0 is bit 0x00000001, shifted left two is 0x00000004.
So this means to set the led:


SetLed:
ldr r0,=0x40025004 ;@ GPIO_F_BASE+GPIO_DATA+(PIN_0<<2)>
mov r1,#0x00000001 ;@ pin GPIO_0
str r1,[r0]


And to turn off the led


ResetLed:
ldr r0,=0x40025004 ;@ GPIO_F_BASE+GPIO_DATA+(PIN_0<<2)>
mov r1,#0x00000000
str r1,[r0]



So to put this all together, I am going to assume that the chip is running at 8MHz, and each instruction is one clock cycle. With that if we burn around 8 million clock cycles between toggling the led we should definitely see it change. 0x3E0000 is over 4 million and two instructions in a wait loop gives us over 8 million instructions, hopefully that blinks at a not too fast and not too slow rate.

Here is the blinker1 code:


/* blinker1.s */
.cpu cortex-m3
.thumb

.thumb_func
PUT32:
str r1,[r0]
bx lr

.thumb_func
GET32:
ldr r0,[r0]
bx lr

.thumb_func
PUTGETRESET:
ldr r2,[r0]
bic r2,r1
str r2,[r0]
bx lr

.thumb_func
PUTGETSET:
ldr r2,[r0]
orr r2,r1
str r2,[r0]
bx lr

.thumb_func
dowait:
ldr r4,=0x3E0000
wait0:
sub r4, #1 ;@why wont it let me use subs, but then it creates a subs?
bne wait0
bx lr

.thumb_func
.globl _start
_start:
ldr r0,=0x400FE108 ;@ SYS_CONTROL+RCGC2
mov r1,#0x00000020 ;@ GPIO_F
bl PUTGETSET
mov r1,#0x000000001 ;@ pin zero
ldr r0,=0x40025420 ;@ GPIO_F_BASE+AFSEL
bl PUTGETRESET
ldr r0,=0x40025400 ;@ GPIO_F_BASE+DIR
bl PUTGETSET
ldr r0,=0x4002550C ;@ GPIO_F_BASE+ODR
bl PUTGETRESET
ldr r0,=0x4002551C ;@ GPIO_F_BASE+DEN
bl PUTGETSET

SetLed:
ldr r0,=0x40025004 ;@ GPIO_F_BASE+GPIO_DATA+(PIN_0<<2)
mov r1,#0x00000001 ;@ pin GPIO_0
str r1,[r0]

bl dowait

ResetLed:
ldr r0,=0x40025004 ;@ GPIO_F_BASE+GPIO_DATA+(PIN_0<<2)
mov r1,#0x00000000
str r1,[r0]

bl dowait
b SetLed

.end


The one thing that is missing is how to get from reset to calling _start.
I intentionally separated this from the main program because I have a bootloader I use as described in a previous blog.

To use the bootloader you want the first instruction in your binary to simply branch to _start:


/* novectors.s */
.cpu cortex-m3
.thumb
b _start
.end


For the rest of the world you need a proper interrupt vector table at the beginning of your binary. This was actually a bit difficult to find, there wasnt a big neon sign that said look here dummy. The ARMv7M Architectural Reference Manual, which is included in the materials with the eval board. Part B System Level Architecture, Section B1.5.2 and B1.5.3 describe the exception vector table. This is NOT what you are used to from the ARMv4 (ARM7TDMI) days. I think its superior but what do I know. Address 0x00000000 is what they call SP_main, for this example what we care about is that it is setting up the stack pointer, we are not using the stack in this example but for a chip like this you probably want to just set it at the top of ram anyway. The words that follow have a one to one relationship with the various exceptions. So a proper vector table would look like this:


/* vectors.s */
.thumb

.word 0x20010000 /* SP_Main */
.word _start /* 1 Reset */
.word hang /* 2 NMI */
.word hang /* 3 HardFault */
.word hang /* 4 MemManage */
.word hang /* 5 BusFault */
.word hang /* 6 UsageFault */
.word hang /* 7 RESERVED */
.word hang /* 8 RESERVED */
.word hang /* 9 RESERVED*/
.word hang /* 10 RESERVED */
.word hang /* 11 SVCall */
.word hang /* 12 Debug Monitor */
.word hang /* 13 RESERVED */
.word hang /* 14 PendSV */
.word hang /* 15 SysTick */
.word hang /* 16 External Interrupt(0) */
.word hang /* 17 External Interrupt(1) */
.word hang /* 18 External Interrupt(2) */
.word hang /* 19 ... */

hang: b .

.end



Unlike the ARMv4 cores the word is not a branch instruction (nor a ldr pc), it is simply the address of the handler. The one exception we care about here is Reset which we send to _start.

Back to the 6965 datasheet. The vector table lives at address 0x00000000 which is in the On-chip flash on the 6965 (Section 3 Memory Map), which is good thats where we want it. Sram goes from 0x20000000 to 0x2000FFFF. The push instruction (see the ARMv7M ARM) decrements the SP first then writes to memory so you can initialize your stack pointer one byte past the end as we have here 0x20010000.

There are MANY ways to tell the gnu linker the memory layout for your system, each one more insanely complicated that the one before it. I prefer the KISS approach, and currently this is the linker script I use:


/* blmemmap */
MEMORY
{
rom(RX) : ORIGIN = 0x00002000, LENGTH = 0x3E000
ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
.text : { *(.text*) } > rom
}


That is for use with my bootloader which currently wants you to start at address 0x2000. For the rest of the world:


/* memmap */
MEMORY
{
rom(RX) : ORIGIN = 0x00000000, LENGTH = 0x40000
ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
.text : { *(.text*) } > rom
}


In a nutshell anything that is Rom or eXecutable goes in the first memory location, the flash, everything else (W, A, I, L) is targeted for SRAM.

Using the gnu binutils compiled using the steps described in a prior post, here is my Makefile for building the binaries.


all : blinker1.bl.bin blinker1.norm.bin

blinker1.bl.bin : blinker1.s novectors.s blmemmap

arm-thumb-elf-as novectors.s -o novectors.o
arm-thumb-elf-as blinker1.s -o blinker1.o
arm-thumb-elf-ld -X -o blinker1.bl.elf novectors.o blinker1.o -T blmemmap
arm-thumb-elf-objdump -D blinker1.bl.elf > blinker1.bl.list

arm-thumb-elf-objcopy blinker1.bl.elf blinker1.bl.bin -O binary

blinker1.norm.bin : blinker1.s vectors.s memmap
arm-thumb-elf-as vectors.s -o vectors.o
arm-thumb-elf-as blinker1.s -o blinker1.o
arm-thumb-elf-ld -X -o blinker1.norm.elf vectors.o blinker1.o -T memmap
arm-thumb-elf-objdump -D blinker1.norm.elf > blinker1.norm.list
arm-thumb-elf-objcopy blinker1.norm.elf blinker1.norm.bin -O binary

clean:
rm *.bin
rm *.o
rm *.elf
rm *.list


Using this memmap it is important that the (no)vectors.o object is first in the list of objects for the linker command.

The source for this example is located here: blinker1.tar.gz

Thursday, December 27, 2007

bootloader

Yes, building gcc is fun. Yes the painful self abuse of linux is fun. Trying to get some big debugger thing working that I will never use because I only care to load the flash, not fun.

I gave up trying to get the ftdi jtag working on Linux and quickly wrote a very simple bootloader. I currently use this on the Luminary Micro Stellaris LM3S6965 and LM3S1968 evaluation boards.

The bulk of what you need to know is in the bootload.txt file in the tarball.

bootload-20080106a.tar.gz
bootload.tar.gz

I used the serial port exposed by the on board ftdi chip. On ubuntu and perhaps elsewhere you might need to:

sudo rmmod ftdi_sio
sudo modprobe ftdi_sio vendor=0x0403 product=0xbcd9

(each time you plug in the board)

And that should provide you with /dev/ttyUSB0 and /dev/ttyUSB1 where /dev/ttyUSB1 is the port we are interested in. If you already have usb serial ports then the numbers will shift. On windows who knows how they are chosen you get COMsomething, unplug and replug and watch which port goes away and returns and there you go.

The bootloader has simple commands like

e 0x2000

To erase the page of flash starting at address 0x2000

And

w 0x2000 0x12345678

To write the value 0x12345678 to (an erased) address 0x2000

If you hold the up button on the 1968 or 6965 board (probably just the select button on the 811 if/when I get it going) during power up or a reset it goes into the bootloader waiting for commands. If you do not have that button pressed it jumps to address 0x2000. I have no support for interrupts at this time.

By the way its 115,200 8N1

There is an assembler and C LED blinker example (for now you have to go into the code and change probably one line to pick the board type before compiling/assembling) in the blinker directory. And on linux a simple loader program in the bload directory.

The linker script (memmap) for using my bootloader is

MEMORY
{
rom(RX) : ORIGIN = 0x00002000, LENGTH = 0x3E000
ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 64K
}
SECTIONS
{
.text : { *(.text*) } > rom
}

Which puts you at address 0x2000 in the flash, not 0x0000

If you want to use pretty much any of my examples without the bootloader use this linker script instead:


MEMORY
{
rom(RX) : ORIGIN = 0x00000000, LENGTH = 0x40000
ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 64K
}

SECTIONS
{
.text : { *(.text*) } > rom
}


Also since the bootloader handles the "vector table" which this chip and/or core uses (not the same as what you are used to from the ARM7) I simply branch to main when using the bootloader:


.cpu cortex-m3
.thumb
b main
.end

If you dont want to use the bootloader, use this instead:

.cpu cortex-m3
.thumb

.word 0x20010000 /* stack top address */
.word main /* reset routine location */
.word hang /* NMI ISR location */
.word hang /* Hard Fault ISR location */

hang: b .


20080106a adds support for the LM3S811 evaluation board.

Roll your own GCC

Although the optimizations are pretty weak I prefer to use gcc, probably because I prefer to develop on Linux over Windows. With gcc, esp a homebrew, I can switch back and forth without too much trouble. Yes, the same is true if I use Codesourcery G++ Lite.

The ARM Cortex-M3 core is a new (relative to the ARM7 we all know and love) core. To make it nice and confusing the Cortex cores can support one or more of ARM mode, (traditional) Thumb mode, the new Thumb2 additions to thumb mode, and some DSP extensions to the ARM instructions. The Cortex-M3 on these chips only supports Thumb and Thumb2 instructions. Basically, ARM filled in the weaknesses in the Thumb instruction set with some ARM Like instructions, in theory, with a good compiler, you can come much closer to ARM ISA performance while staying close to Thumb ISA binary size. Thats the theory.

The code I share here is not necessarily one size fits all. There will be some hardcoding from time to time per platform. And in no way, shape, or form do I think that embedded programming means making api/operating system calls. I dont use operating systems, and for the time being I dont even have a C library. And if you really know gcc you know to just forget about floating point.

Cygwin is good, has its place, does its job, but for a gcc cross compiler I prefer statically compiled MinGW binaries, no dll hell and a performance boost for those big jobs. Yes, talking about Windows. The build instructions are almost identical for Linux and Windows, MinGW and/or windows provides some extra problems so they are identified as extra steps. Currently using Ubuntu Gutsy and Windows XP. These build instructions, which are quite simple, have evolved from gcc 2.95 to the present from Windows NT4 and Slackware whatever to the present.

As of 4.2.2 gcc does not yet support the Thumb2 instructions. The next release should have it. Codesourcery has added/included Thumb2 support in their 4.2.2 gcc.

Lets begin:



#IF WINDOWS
Go to http://www.mingw.org, find and download

MinGW=5.1.3.exe
MSYS-1.0.11-2004.04.30-1.exe

Install both
Run the Msys shell/prompt to continue as if on a native Linux system.
#ENDIF

#IF LINUX
Make sure you have texinfo, bison, and flex installed, as well as gcc, make and other typical programs
On ubuntu this would be

sudo apt-get install build-essential bison flex texinfo

Note the makeinfo error I am trying to avoid here is part of the texinfo package
#ENDIF

Most of this section is simply a list of the commands to type.

#IF LINUX
You probably dont run as root all the time so you may need to use a directory other than /arm or
as root create /arm and chown to your username so that you can finish the build as the user.
It wants to run from the path used to create it so think about where you want it and who you want
to use it. If you do all of this as root and keep it in /arm (and make it readable by everyone) everyone
can use it.
#ENDIF

Download http://ftp.gnu.org/gnu/gcc/gcc-4.2.2/gcc-core-4.2.2.tar.bz2
Download http://ftp.gnu.org/gnu/binutils/binutils-2.18.tar.gz

ZZZ='--target=arm-thumb-elf --prefix=/arm'
mkdir /arm
cd /arm
tar xzvf /path/to/binutils-2.18.tar.gz
cd binutils-2.18
mkdir build
cd build
../configure $ZZZ

#IF WINDOWS
edit the Makefile in this directory
Find and change the line from

MAKEINFO = /arm/binutils-2.18/missing makeinfo

to

MAKEINFO = /bin/makeinfo

#ENDIF

make all install

(this will take a while)

/arm/bin/arm-thumb-elf-as --version

GNU assembler (GNU Binutils) 2.18
...

Now binutils, the assembler, linker and other utilties is complete. If you only want to use assembly language this is all you need

To clean up the binutils build files

cd /arm
rm -rf binutils-2.18

On to gcc

cd /arm
tar xjvf /path/to/gcc-core-4.2.2.tar.bz2
cd gcc-4.2.2
mkdir build
cd build
../configure $ZZZ --disable-libssp
make all install

#IF WINDOWS
This will fail at some point with errors that look like

In file included from ../../gcc/libgcc2.c:35:
./tm.h:6:28: error: config/dbxelf.h: No such file or directory
...

It doesnt like the -I by itself in the xgcc command line
The culprit is in gcc/Makefile, a line that starts with

INCLUDES = -I. -I$(@D) -I$(srcdir) ...

Just get rid of that second include

INCLUDES = -I. -I$(srcdir) ...

Then start the build again

make all install

and it will complete this time
#ENDIF

/arm/arm-thumb-elf-gcc --version

arm-thumb-elf-gcc.exe (GCC) 4.2.2

To clean up the gcc build files

cd /arm
rm -rf gcc-4.2.2

#IF WINDOWS
You can exit msys and then copy the arm directory tree from
z:\msys\1.0\arm to z:\arm
(so that your binaries are in z:\arm\bin\)
z:\ is of course whatever directory you happen to be working with C:\ for example
#ENDIF

Welcome to lmistuff

Welcome, I thought I would keep track of my Luminary Micro Stellaris microcontroller evaluation board experiments.

Currently I have an LM3S6965, an LM3S1968, and a dead LM3S811. Well, not really dead I was too eager when it came in and programmed one or more of the jtag I/O pins the wrong way...I have another one on its way.