You will need some compiler tools. You can look at my other blogs and build your own or
just get codesourcery lite.
http://www.codesourcery.com/sgpp/lite/arm/download.html
the GNU/Linux one is fine, dont use the C library or any system calls so we just need the cross compiler itself (and binutils).
Get schematic from sparkfun or olimex
From schematic see that the part is an AT91SAM7S64, and the led is on PA8
go to atmel's website and get the datasheet/document for the AT91SAM7S series (the big book that is not the arm TRM (although if you dont have an ARM TRM or the ARM ARM you will eventually want that too)). From atmels site this is currently doc6175.pdf
chapter 8 memories, looking at the memory map sram starts at 0x00200000 and ends at 0x00300000
chapter 27 parallel I/O (PIO).
figure 27-3 I/O control line logic. work backward from the pad. We want to just write to registers to blink the led on and off instead of using a peripheral like a uart or something
so we will need to use the PIO_PSR(PER/PDR) registers to switch the mux to register control.
So search in the doc for PIO_PSR. 27.4.2 says that a 0 indicates the pin is tied to the peripheral and 1 is tied to the PIO controller, so we will want a one for that I/O pin.
chapter 27.6 table 27-2 register mapping.
0x0000 PIO_PER write only
0x0004 PIO_PDR write only
0x0008 PIO_PSR read only
So what we really want is the PIO_PER or PIO_PDR to enable or disable, PSR is the status register indicating the current state.
Searching for those registers we find 27.6.1 PIO_PER, enables PIO control of the I/O pin disabling peripheral control. That is what we want.
This chip is the kind that has the write a one to a bit in one register to enable something and write the same 1 bit to a different register to disable something. The other kind of chip is
read a register, modify the one bit of interest zero or one, then write the register back. Pros and cons either way, the chips with both methods are ideal because you can make it a win win.
So for this board with PA8 being the I/O pin in question we are going to want to write to PIO_PER with bit 8 set to give PIO control over that pin.
back to 27.4 figure 27-3. To enable output on that pin we need to look for
PIO_OER/OSR/ODR.
0x0010 PIO_OER output enable
same deal we are going to want PA8 to be an output so we need to write a 1 to bit 8 of PIO_OER.
back to figure 27-3 in 27.4 got the feel for this doc now...
PIO_SODR/PIO_CODR is no doubt how you set and clear the bit
0x0030 PIO_SODR
0x0034 PIO_CODR
So SODR sets the output (to a 1 or VCC) and CODR clears the output or puts zero volts
out on the pin.
That may be all we need for what registers to use, but what is the base address
for PIOA and does it need to be enabled?
Figure 8-1 memory map shows PIOA starting at 0xFFFFF400 I would prefer to see this in
a table as well as a picture but dont see it anywhere else.
While looking for the PIOA address saw the PIO_PUDR register, we dont need a pull up
and pull ups are enabled post reset so we can write a 1 here to disable the pull up. Shouldnt hurt if we leave it on, just draw some more current.
So on to the program.
This example is going to run from ram, if/when we figure out how to run from rom the first rom program may just copy this program to ram and run from ram. Then eventually maybe execute instructions from rom and use data/variables in ram.
Even though we are not initially booting from the rom, I still use a rom style interrupt vector table approach to the beginning of the binary/program. The first exception vector in an ARM(7) is a reset, so it will just branch to our startup anyway. This model means that I only have to do it one way and not think about it.
So the startup code, the bytes at the beginning of our binary in memory will be from vectors.s
.globl _start
_start:
b reset
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
reset:
mov sp,#0x00300000
bl notmain
hang: b hang
.globl PUT32
PUT32:
str r1,[r0]
mov pc,lr
.globl ASMDELAY
ASMDELAY:
subs r0,r0,#1
bne ASMDELAY
mov pc,lr
From the sam7s document we saw that there is an sram, that doesnt move around, that goes from 0x00200000 to 0x00300000. So just set the stack pointer to the top of that memory so it can work its way down. Calling C functions so we might need a stack.
I call notmain() just to mentally know this is not a operating system based program that normally calls main(). Some compilers add extra junk when you have a function named main() that they wont do if you call it something else.
I have had too many compilers, gcc included, that no matter how hard you try, optimize out or modify a memory/register load or store, for that reason and others, I burn a function call for accessing memory/registers. In this case PUT32, for writing 32 bit registers is implemented in assembler.
To save you possible frustration, an assembler based count to N routine ASMDELAY() was also added. Normally a C counter loop can be used but as compilers get better they try harder to optimize that code out to nothing. Since this simple example is going to turn the led on, count to N then turn it off count to N, turn it on count to N. We need the count to N to actually burn some time and not get optimized out. So ASMDELAY is here for that reason.
void PUT32 ( unsigned int, unsigned int );
void ASMDELAY ( unsigned int );
#define PIOA_BASE 0xFFFFF400
#define PIO_PER 0x0000
#define PIO_OER 0x0010
#define PIO_SODR 0x0030
#define PIO_CODR 0x0034
#define PIO_PUDR 0x0060
#define PIOA_PER (PIOA_BASE+PIO_PER)
#define PIOA_OER (PIOA_BASE+PIO_OER)
#define PIOA_SODR (PIOA_BASE+PIO_SODR)
#define PIOA_CODR (PIOA_BASE+PIO_CODR)
#define PIOA_PUDR (PIOA_BASE+PIO_PUDR)
#define LED_BIT (1<<8)
int notmain ( void )
{
//volatile unsigned int ra;
PUT32(PIOA_PER,LED_BIT); //enable/connect PIO control to led output
PUT32(PIOA_PUDR,LED_BIT); //disable pull up resistor
PUT32(PIOA_OER,LED_BIT); //enable/set as an output pin
PUT32(PIOA_CODR,LED_BIT); //turn on led. other side of led is 3.3v so to turn it on drive/sink 0 volts.
while(1)
{
PUT32(PIOA_CODR,LED_BIT);
//for(ra=0;ra<0x10000;ra++) continue;
ASMDELAY(0x400000); //this way the compiler wont optimize out the for loop
PUT32(PIOA_SODR,LED_BIT);
ASMDELAY(0x400000);
}
return(0);
}
There is nothing to this now, we looked up the registers for enabling PIO control over the pin, making it an output, and turning it on and off. So this program simply does that and then goes into an infinite loop that turns on the led, counts to N, turns off the led, counts to N.
The next step is building it. Here is the makefile.
ARMGNU = arm-none-linux-gnueabi
AOPS = --warn --fatal-warnings
COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding
all : blinker1.bin
blinker1.bin : blinker1.elf
$(ARMGNU)-objcopy blinker1.elf -O binary blinker1.bin
blinker1.elf : blinker1.o vectors.o memmap
$(ARMGNU)-ld memmap vectors.o blinker1.o -o blinker1.elf
$(ARMGNU)-objdump -D blinker1.elf > blinker1.list
blinker1.o : blinker1.c
$(ARMGNU)-gcc $(COPS) -c blinker1.c -o blinker1.o
vectors.o : vectors.s
$(ARMGNU)-as $(AOPS) vectors.s -o vectors.o
clean :
rm -f *.o
rm -f *.elf
rm -f *.list
You can see that I do not use standard libs, so we dont care that this is a linux based gcc compiler for arm. Dont want to use the compilers startup code, we have provided our own with vectors.s. if you roll your own gcc or use emdebian or winarm or whatever, just change the ARMGNU to match your prefix. The linker script memmap may not work for gcc 3.x or I guess that would be the binutils of the era not necessarily gcc.
Here is my linker script, memmap
/* memmap */
MEMORY
{
rom(RXWAIL) : ORIGIN = 0x00200000, LENGTH = 0x10000
}
SECTIONS
{
.text : { *(.text*) } > rom
}
vectors.o being the first object in the linker script puts it first in the file which is where we want it.
So that leaves us with either a blinker1.elf file or a blinker1.bin file depending on the tools. I have a funny feeling that whatever comes with this board uses some of that sram so you may want to change memmap to some other address like 0x00202000 or something to move it out of the way. using a jtag wiggler, and kicking the thing twice I can get it to run from 0x200000.
I do not have a sam-ba solution as I dont run windows. the .bin or .elf above should work for you, you might have to adjust the address. Eventually I may make a linux loader, at one point I had a windows loader for this board using libusb, so a linux one should be possible as well.
I am currently using an Amontec Jtag-tiny wiggler, the olimex usb one or parallel port one or any arm based one supported by openocd will work using the following instructions. You will need to get and install openocd which may be a simple apt-get install. Here is the configuration file that I used with the jtag-tiny
telnet_port 4444
gdb_port 0
tcl_port 0
interface ft2232
ft2232_device_desc "Amontec JTAGkey"
ft2232_layout jtagkey
ft2232_vid_pid 0x0403 0xcff8
#ft2232_serial T1RWU3KU
reset_config trst_and_srst srst_pulls_trst
jtag newtap sam7s cpu -irlen 4 -ircapture 0x1 -irmask 0x0f -expected-id 0x3f0f0f0f
set _TARGETNAME sam7s.cpu
target create $_TARGETNAME arm7tdmi -endian little -chain-position $_TARGETNAME -variant arm7tdmi
It is easy to build this file from the config files provided with openocd. We just need enough for openocd to gain access to the arm debug tap controller, from there we use openocd commands.
If you are using something other than the jtag-tiny then find the config file for your wiggler and replace the first section of this config file. Note that I have commented out the serial number, I can/have more than one jtag-tiny in a system at a time and using serial numbers like this you can have more than one openocd instance running, also change the telnet port number if you want to do something like that.
In addition to openocd you may need some ftdi drivers for your wiggler. here again for ubuntu this is an apt-get install thing. libftdi-dev or something like that. maybe has a 2232 in the name.
Power the card and in one terminal window run
> openocd -f sam7s.cfg
Hopefully it says that it has found the sam7s cpu/tap device
In another terminal window run
> telnet localhost 4444
You should get a Open On-Chip Debugger prompt
The first thing is type halt
> halt
Now be really careful, if you are not in the telnet session to openocd but at some other prompt you will halt your computer and that is really annoying.
The second command is to load the .elf file, you can do the .bin and provide an address, but since we have the .elf lets use it.
> load_image /path/to/blinker1.elf
If you ran openocd from the same directory as the elf then you dont need the path. The terminal window should say so many bytes were loaded to 0x200000.
Now start the arm running at that address
> resume 0x200000
I had to do this twice the first time after power on to get this to work, I want to remember the startup code in the chip uses some of that sram.
> halt
> resume 0x200000
And you should have a blinking green led on your board!
Now chips like this one that are advertised at some dozens of mhz do not necessarily power up at that speed. They may start at 8mhz or 12 or whatever the crystal is and there is a procedure for programming some clock registers to speed the thing up. So the rate that the led blinks depends on what code you had running from rom before running this program. Likewise if you add code to adjust that clock the blink rate can change and the led may look like it is just on instead of blinking (blinking faster than your eye can tell). That is because this is a simple program that uses a counter as a delay, it counts X number of arm instructions, and if we adjust the execution speed of the arm that X instructions can run fast or slow. Eventually you would want to take control over what speed you run the arm, what speed you run timer reference clocks and use a timer to count to some X number of reference clocks and the arm speed wont matter, also the arm can go do other stuff and come back to check the timer instead of having to burn cycles on blinking.
Excellent article! I wish I had read this a few days ago before I spent hours setting up my toolchain and JTAG environment. Your section on memory layout is helpful, as I am still learning the details of my at91sam7s256. Thanks.
ReplyDeleteExcelent article! I wish it would be for LPC3141 which uses ARM926EJ-s core. Oh what a pitty :)
ReplyDelete