at91sam7s quick start guide for...
TRANSCRIPT
AT91SAM7S Quick Start Guide for Beginners 6 March 2013
Version 2.2 – 06/03/2013
Version 2 – 16/01/2013
Version 1 – 03/01/2013
Adam Goodwin
Table of Contents
Introduction ............................................................................................................................................ 4
Overview ................................................................................................................................................. 5
A Brief Introduction to the SAM7S MCUs ............................................................................................... 6
The SAM7S Variants ............................................................................................................................ 6
Memory Remapping and Exception Vectors ...................................................................................... 6
Reset Configuration ............................................................................................................................ 7
SAM7S Processor ................................................................................................................................ 7
Installing the Required Software and Hardware .................................................................................... 8
What You’ll Need ................................................................................................................................ 8
1. The Eclipse IDE (Integrated Development Environment) ....................................................... 8
2. YAGARTO (Yet Another GNU ARM Toolchain) ........................................................................ 8
3. OpenOCD (Open On-Chip Debugger) ...................................................................................... 8
4. An ECE USB-JTAG adapter ....................................................................................................... 9
5. A board with a SAM7S chip, wired to allow a JTAG connection ............................................. 9
The Overall Setup ................................................................................................................................ 9
Installing the Components ................................................................................................................ 12
Eclipse ........................................................................................................................................... 12
YAGARTO ....................................................................................................................................... 12
OpenOCD ...................................................................................................................................... 12
Hardware ...................................................................................................................................... 12
Creating the LED-Flashing Test Project ................................................................................................. 15
Starting a New Eclipse Project .......................................................................................................... 17
Copying Existing Files to the Project ................................................................................................. 19
Writing the Source Files .................................................................................................................... 20
Creating the “sam7s_startup.s” Assembly Source File ................................................................. 20
Creating the “board.h” Header File .............................................................................................. 26
Creating the “sam7s256_init.c” C Source File............................................................................... 27
Creating the “main.c” C Source File .............................................................................................. 37
Creating the Delay Module ........................................................................................................... 39
Scripts and Configuration Files ......................................................................................................... 42
Creating the “led_test_sam7s256.ld” Linker Script ...................................................................... 42
Creating the “run_openocd.bat” Batch File .................................................................................. 50
Creating the “uc-sam7-usbjtag.cfg” OpenOCD Configuration File ............................................... 51
Creating the Makefile ................................................................................................................... 55
Testing and Debugging .......................................................................................................................... 67
Checking the Hardware and OpenOCD ............................................................................................. 68
Programming the Board .................................................................................................................... 70
Debugging the Program .................................................................................................................... 72
Debugging the Startup Code ............................................................................................................. 73
Debugging Using OpenOCD .............................................................................................................. 75
Conclusion and Contact Information .................................................................................................... 79
Web Links .............................................................................................................................................. 80
Version 2 Changes ................................................................................................................................. 82
References ............................................................................................................................................ 83
Introduction
The intention of this guide is to allow a quick and easy setup of everything you need to begin
programming one of Atmel’s AT91SAM7S microcontrollers.
The idea is that by simply following the guide you will be ready to write software for the
microcontroller, and won’t have to spend time stumbling around trying to get things working on
your own. Starting from scratch on your own would be particularly difficult if you are unfamiliar with
the open source software required.
This guide is aimed at beginners who have very little embedded systems experience outside of an
introductory university course, but who are to work with a SAM7S microcontroller and need a crash
course. The guide assumes you are working under Windows, with the guide itself being put together
and tested under Windows 7.
I have made this guide after getting a SAM7S up and running for the first time, so I am by no means
an expert. I made extensive use of two tutorials to get to this stage: Using Open Source Tools for
AT91SAM7S Cross Development (Lynch, 2007) and Building Bare-Metal ARM Systems with GNU
(Samek, 2007).
These two tutorials were extremely helpful, and I recommend reading them if you have time. If you
try to follow the two tutorials exactly you may have some trouble with the tutorials being out of
date, due to significant changes having been made to the open-source tools since the time of the
tutorials’ writing. However, by reading them you will learn a lot.
This guide is based heavily on the two tutorials (I cannot emphasise enough how helpful these
tutorials were) but has been brought up to date to 2013. This guide also contains instructions
specific to the University of Canterbury, due to the use of the Electrical and Computer Engineering
(ECE) Department’s USB-JTAG adapter, but it shouldn’t be too difficult to use another interface in its
place if needed (the guide will try to help you with this once you reach the relevant section).
Upon completing this guide you should have an LED-flashing application which can be successfully
loaded to the SAM7S chip and debugged from your PC. Above all else, what you should gain from
this guide is an elementary understanding of all aspects of programming a SAM7S, and a saving of
many hours you might have otherwise spent learning how to get up and running on your own. The
guide is all-inclusive, so hopefully you will not be left confused about any aspect of the process.
Note: As stated previously, this is a guide for beginners. Those with more experience will probably
manage on their own, or will only need to make use of the more advanced tutorials from James
Lynch and Miro Samek. Still, this brief guide may be worth reading as a primer, and any suggestions
or corrections are welcome.
You can contact me via email at [email protected].
Overview
This guide takes you step-by-step through installing the required software and hardware, then
moves onto an example project which simply flashes any LEDs connected to the microcontroller
(MCU). When you test the program after reaching the end of the guide a very brief introduction to
debugging (using the command line) is also included.
When walking you through the required software and hardware the guide explains briefly what each
component is, where to get it, and how everything fits together.
The workings of the example project are explained as each of the source files is introduced. The
guide is based around a SAM7S256, but explains with examples how to adapt the project to different
models of SAM7S MCU where appropriate. The project can be put together and loaded onto your
chip as a starting point, and from there you can then extend the example to suit the needs of your
own project.
The example project contains low level code, and some script, which is sourced from Miro Samek’s
tutorial (Samek, 2007). However, there are some minor modifications. Note also that there are no
advanced features, such as interrupts, utilised in this example project. This is because the example
project is intended to be a very simple test to make sure that everything has been set up correctly
for development, and that everything is working as expected. If you get this simple project working,
you can then go on to adjust it to include all of the more complicated features of Miro Samek’s
tutorial – such as nested interrupts or C++ code. James Lynch’s tutorial also covers advanced
features such as interrupts.
The idea with using such a simple example project is that if you reach the end of the guide, but don’t
have the expected result, you’ll have fewer places to look for the cause of your problems. If you’re
anything like me you’ll also prefer to rely on as little of somebody else’s code as possible when
learning to use a new MCU, with the code that you do reuse being simple enough to understand.
The main point is, if something isn’t working, it’s extremely difficult to work out the source of the
problem if your project is trying to use all sorts of advanced features – at least that’s what I find.
To summarise what the guide contains before you dive in, here are the main segments that will be
covered:
- Introduction to the microcontroller
- Installation of the software and hardware
- Creating the example project
- Testing that everything has worked, and debugging
A Brief Introduction to the SAM7S MCUs
As you already know, this guide is specifically for Atmel’s range of SAM7S MCUs. Depending on how
much you know, there are a few things discussed here which might help you to become more
familiar with the SAM7S family of MCUs.
The SAM7S Variants
First of all, there are eight devices in the family. They are the (AT91SAM7S) 16, 161, 32, 321, 64, 128,
256 and 512. Aside from the 16 and 32 which have 48 pins, they are all 64 pin MCUs with very
similar peripherals. The main difference between the devices is the amount of flash memory and
SRAM that they have.
The best place to go for information on the MCUs is the SAM7S datasheet, available from Atmel here
(it is named SAM7S Series Complete).
Memory Remapping and Exception Vectors
One important thing to know about the SAM7S MCUs is the memory remapping feature. Although
this is explained in the datasheet it is worth mentioning explicitly here. A SAM7S, being a 32-bit
MCU, can address up to 232 bytes or 4GB of memory. To us, the most relevant of these addresses are
those referring to the first 3MB of memory:
The 1MB address space spanning from the address 0x0010 0000 through to 0x001F FFFF (inclusive)
is mapped to the internal flash memory of the MCU. The actual size of the flash memory varies from
16KB to 512KB depending on the particular SAM7S variant being used, but it always starts at 0x0010
0000.
The 1MB of addresses spanning from 0x0020 0000 through to 0x002F FFFF (inclusive) refers to the
internal SRAM of the MCU (again with the actual memory size varying from 4KB to 64KB depending
on the MCU).
Finally, the 1MB region from 0x0000 0000 to 0x000F FFFF (inclusive) is known as the remap region.
Ordinarily this points to the 1MB flash memory area, but this can be toggled via a register in order to
point the remap region to the 1MB area for SRAM.
This is important, because the design of the processor means that the bytes of memory from 0x0000
0000 up to (but not including) 0x0000 0020 are exception vectors. These “exception vectors” are 32-
bit addresses which the processor should execute instructions from, should an exception occur.
To explain further, consider the reset exception vector at address 0x0000 0000. When the MCU is
powered on or reset, the first instruction to be executed is fetched from the reset exception vector.
So the instruction at address 0x0000 0000 is the first instruction of your program.
The consequence of the exception vectors and the remapping feature is that while your program is
running, it can change where the exception vectors are located (in flash or in RAM) by using the
remap command. In turn, the consequence of this is that once the remap region is mapped to RAM,
you can modify the exception vectors by writing into the first 32 bytes of address space (which can’t
be done when this area of memory points to flash – as flash is for all intents and purposes read-only
memory).
Reset Configuration
To reset a SAM7S MCU, the reset pin needs to be pulled to ground. However, a trick that might catch
you out is that the reset pin needs to first be enabled in software – and it is disabled by default. How
to enable it is explained later in the guide, with the rest of the initialisation code, but it is again a
notable feature of the MCUs which should be brought to a beginner’s attention.
SAM7S Processor
The SAM7S family of MCUs uses the ARM7TDMI CPU, a 32-bit RISC-based CPU, the architecture of
which is the ARMv4T architecture. For more information on the architecture, refer to the ARM
Architecture Reference Manual available from ARM’s documentation site (the ARMv5 Reference
Manual covers the ARMv4T architecture). Note that you will need to register a free ARM account to
view the manual. The manual will be of particular use if you plan on looking at this guide’s assembly
code in any amount of detail.
An interesting feature of the architecture is that the CPU can execute both 32-bit ARM instructions
and 16-bit Thumb instructions. It’s up to you which source files you want to compile to ARM code
and which files you want to compile to Thumb code.
Installing the Required Software and Hardware
In this section the software and hardware needed for completing this quick start guide are
described. Everything you need, where to get it, and how to set it up, is explained here. We will start
by looking at each of the tools you need followed by an explanation of what they do and how they
interact.
What You’ll Need
What follows is a list of the software and hardware needed for this guide. All of the software is free,
and the list includes links to where you can obtain it. Don’t worry about installing everything in the
list now; the installation is covered later in the guide. At this stage you can just download everything
to a temporary folder and make sure you have the hardware on hand.
We will start at the higher level software needed to program the chip and then work down towards
the MCU. Here is what you’ll need:
1. The Eclipse IDE (Integrated Development Environment)
First of all you will need somewhere to write your code. You can use anything you want for this
really, even just a text editor if you don’t want to download and install a full IDE. Eclipse has a lot
of useful features though, and YAGARTO (the next tool you’ll need) is designed to be compatible
with Eclipse, so it makes sense to use it for this guide.
(Note: I haven’t actually explained integrating YAGARTO and Eclipse in this guide, and only use
the command line for building and debugging the example project, but once again you can get
information about connecting YAGARTO and Eclipse from James Lynch’s tutorial – if you’d prefer
to debug from within the IDE.)
You can get Eclipse from the Downloads page of the Eclipse website. Just make sure you choose
the Eclipse IDE for C/C++ Developers – the download should be a zipped folder. At the time of
writing the current release is Eclipse Juno 4.2.
2. YAGARTO (Yet Another GNU ARM Toolchain)
YAGARTO is a GNU ARM toolchain from Michael Fischer. It consists of two packages, which can
be found on the YAGARTO website. These are the “YAGARTO Tools” and the “YAGARTO GNU
ARM Toolchain” packages. YAGARTO contains everything you need to build ARM compatible
programs from your C code (the SAM7S MCUs have an ARM processor). Download these two
executables now. At the time of writing the most recent tools package is dated 18/10/2012 and
the most recent toolchain package is dated 22/12/2012.
3. OpenOCD (Open On-Chip Debugger)
OpenOCD is software created by Dominic Rath which allows you to program and debug the MCU
from your PC. OpenOCD’s SourceForge page can be found here. Under the “Getting OpenOCD”
page you will find that there’s a link to Freddie Chopin’s website, where you can download a
copy of OpenOCD already compiled for Windows. Download the latest version now. At the time
of writing the current version is OpenOCD 0.6.1.
4. An ECE USB-JTAG adapter
You’ll need this to connect your SAM7S MCU to your PC. Although it is assumed you’ll be using
one of the ECE Department’s USB-JTAG adapters, it shouldn’t be too difficult to use another
adapter if you have one. Still, you can borrow an ECE adapter at no cost, so I recommend using
one at least until you finish this guide and have gotten everything to work. At that point you can
try a different adapter if you like, and you’ll have narrowed the problem down if things stop
working.
5. A board with a SAM7S chip, wired to allow a JTAG connection
This guide was written while using a development board from Olimex (the SAM7-H256 board)
with a SAM7S256 MCU. The chances are that if you’re at the University of Canterbury, this is
what you are using as well. However, as long as you have an AT91SAM7S MCU that can accept a
JTAG connection, this guide will apply.
The Overall Setup
The below image shows the overall configuration of the five items from the previous section. What
follows on the next page is a description of the setup, so that you can understand what each
component is for and how they all interact.
1
2
3
4
5
Source code
Executable code
USB connection
JTAG connection
The overall setup with the Eclipse IDE (1), YAGARTO (2), OpenOCD (3), the USB to JTAG
interface (4), and the target board (5). The links show the relationship between
components.
Again I’ll start with the high level software and work down towards the lower levels. If you want
more information about any stage, James Lynch’s tutorial covers the same sort of material but is
more in-depth (Lynch, 2007).
For the purposes of this guide, as I have mentioned above, the Eclipse IDE is really only used as a text
editor. Nonetheless, to complete the LED-flashing project, you’ll need to write the source code
somewhere.
Once you have your source code, this is when the YAGARTO toolchain takes over (not the YAGARTO
Tools package, that comes in later). YAGARTO’s GNU compiler is used to compile your C code to
object code for the ARM7TDMI processor of a SAM7S chip. Similarly, YAGARTO’s GNU assembler is
used to assemble your assembly code into object code.
At this stage you’ll have several object files corresponding to each of the original source files.
YAGARTO’s GNU linker is then invoked, via the GNU compiler, to combine all of these together
(along with any standard libraries used) into a single ELF executable. For the linker to create this
executable program, we have to provide it with a linker script. The linker script tells the linker how
to lay out the program in memory, so that it can be run correctly from the flash memory and RAM of
the SAM7S MCU.
All of this YAGARTO use can be done from the command line. That is, you can go from the source
code to the ELF executable by entering (a lot of) commands into the Windows Command Prompt.
However, as you probably know, a makefile simplifies this greatly – you can just type “make” into
Command Prompt and everything is done automatically. This is where YAGARTO Tools comes in. The
YAGARTO Tools package contains Make for Windows as well as some other helpful tools.
Anyway, once you have an ELF executable you still need to get the program off of your computer
and into the MCU’s flash memory. OpenOCD runs as a server on your PC and handles
communication with the MCU, including the programming. OpenOCD connects to the ECE USB-JTAG
adapter via USB, which in turn connects to the MCU via JTAG. When starting OpenOCD we give it a
configuration file to tell it about the JTAG adapter and MCU we’re using.
What is meant by “OpenOCD runs as a server” is that OpenOCD runs in the background on your PC,
waiting for requests (from clients) for it to communicate with the connected hardware. You can
connect to the OpenOCD server over TCP using Telnet and give it commands manually (for example
you could send it commands to write your program to the MCU’s flash memory). Alternatively, you
can run GDB (the GNU debugger, included with YAGARTO) and connect GDB to the OpenOCD server.
From there, you can let GDB debug the program running on the MCU just as it would with any other
program (provided you have told GDB what the ELF executable in use on the MCU is). Not only can
GDB debug through OpenOCD, but you can also manually send commands to the OpenOCD server
from within GDB.
So by using GDB you have full control. You can run GDB and debug your program, you can load an
ELF executable to the MCU, and you can also make use of OpenOCD commands from within GDB.
Hopefully you are now starting to understand the relationship shown in the image on the previous
page, and how you can go from writing source code to running your program on the MCU using the
tools in this guide.
Installing the Components
Eclipse
First install the Eclipse IDE. All that you should need to do is unzip the zipped folder you downloaded
from the Eclipse website. The only thing is that you might want to put it in an appropriate place. I
unzipped the compressed folder to “C:\Program Files\Eclipse” and put a shortcut to eclipse.exe on
the desktop.
If you try to run Eclipse, and get an error about the Java Runtime Environment (JRE), you probably
don’t have it installed. You can install it from the Java website, at the time of writing the most recent
version is Version 7 Update 10.
YAGARTO
Now install the YAGARTO Toolchain by double clicking the executable you downloaded from the
YAGARTO website. It should be the executable with a long name, containing the version numbers of
the main components of the toolchain. There isn’t much you need to do, just make sure all of the
installation components are selected and ensure you install to a directory with no spaces – such as
“C:\yagarto-20121222”. (The YAGARTO website explains that the path without spaces is required for
both of the packages to work.)
Now install the YAGARTO Tools package. Double click this installer and do the same as with the
toolchain; make sure all of the components are selected and that you choose a path with no spaces.
OpenOCD
Finally install OpenOCD. Again, this is just a matter of extracting the compressed folder to an
appropriate directory – I chose “C:\Program Files\openocd-0.6.1”.
Hardware
If you are not using the ECE USB-JTAG adapter you should follow the installation instructions
included with your adapter. The rest of this section can be skipped.
Otherwise, if you are using the ECE USB-JTAG adapter, you should connect the adapter to a USB port
on your PC for the first time. The adapter uses an FT2232D chip, and the appropriate FTDI drivers
should be installed automatically by Windows. If Windows gives a message that the drivers were not
successfully installed, you might have to click on this notification and specify that Windows should
search the internet for the appropriate drivers.
To verify that the drivers have installed, open Control Panel. On Windows 7, if you are viewing by
category, open Hardware and Sound then open Device Manager. Otherwise just open Device
Manager directly. Once Device Manager has opened, expand the “Universal Serial Bus controllers”
section. Here you should see the “USB Serial Converter A” and “USB Serial Converter B” devices
associated with the USB-JTAG adapter.
Right-click on “USB Serial Converter A”. From here you can choose “Update Driver Software...” if you
haven’t already had the successful install. You can also right-click and choose properties to verify the
installation. In the Driver tab of the properties window you should see that the FTDI drivers are
installed; it should look something like the following:
There is still another step to completing the installation of the adapter; the libusb-win32 filter driver
needs to be installed. This can be downloaded from the SourceForge page here. Scroll down and
follow the link to the download site, then browse the libusb-win32-releases folder. Then browse to
the folder of the most recent release – at the time of writing the most recent release is 1.2.6.0.
The file you are looking for will be named “libusb-win32-devel-filter-1.2.6.0.exe”, except with the
appropriate version number that you selected. Download and run this installer. It shouldn’t matter
where you install to – I used the default setting of “C:\Program Files\LibUSB-Win32”.
Once the installation has finished, navigate to the install directory and find in the “bin” folder the
“install-filter-win.exe” program. Run this program, and when it opens choose to “Install a device
filter”.
In the next set of options, you need to select the “USB Serial Converter A” device as the device to be
filtered.
Click “Install”, and you should get a notification that the filter was successfully installed. You can now
cancel out of the installer. If you want to check everything is as it should be, go back to Device
Manager. If the “USB Serial Converter A” device is still listed, and doesn’t have a little yellow error
icon over it, then everything should be fine.
Now you have everything you need to start working on a SAM7S project. Creating the LED-flashing
project is next up, starting on the following page.
Creating the LED-Flashing Test Project
This section will walk you through using Eclipse to write all of the source files, scripts, and other files
needed to complete the LED-flashing example project. The idea is that you’ll rewrite each of the files
into your Eclipse workspace – making sure you have an understanding of what’s happening in each
one.
If you’re in a real hurry you can just copy and paste each of the files’ contents from this guide, but I
recommend you take the time to try and get as full an understanding as possible.
The reason this is a “quick start” guide is not because it’s short, but because a person unfamiliar with
many of these kinds of files and scripts should be able to get a good idea of how they all work
relatively quickly by reading it (much more quickly than if you tried to learn by reading the
documentation only). At least, that is the intention.
The files you will need to create for this project are:
- sam7s_startup.s*
- board.h
- sam7s256_init.c*
- main.c
- delay.h
- delay.c
- led_test_sam7s256.ld*
- run_openocd.bat
- uc-sam7-usbjtag.cfg
- makefile*
To be clear, you really do need to have an understanding of these sorts of files to program a SAM7S
MCU with any confidence. Particularly, you should make an effort to understand the assembly file,
initialisation C file, linker script file, OpenOCD configuration file, and makefile.
The files are explained in more detail as they are introduced to the project throughout the guide.
This includes the changes you’ll need to make if you’re not using a SAM7S256 MCU or an ECE USB-
JTAG adapter.
There is also one additional file you will need, which I haven’t included in this guide:
- AT91SAM7S256.h
This is a header file from Atmel’s official libraries. You can get this file from the AT91 library available
from this download page on Atmel’s website. At the time of writing, the latest version of the library
is AT91LIB version 1.9. You can download the zipped folder and find within it just the header file
needed for your MCU, but you might as well hold onto the entire library as it contains some other
header and source files you might find useful in the future.
*These files are based heavily on files from Miro Samek’s tutorial (Samek, 2007). Although my versions
differ, particularly with respect to the makefile, the majo rity of the code is his and credit (and my thanks)
goes to him for it. The link to the Building Bare-Metal ARM Systems with GNU article is in the references
section of this guide.
Note: The AT91LIB library appears to have AT91SAM7S header files for every SAM7S chip, except for
the SAM7S16. The header file for the SAM7S16 is there, but has been incorrectly named as
AT91SAM7S161.h (open the file and look at the comments and definitions, and you’ll soon see that
this is the case). In actual fact it is the header file for the SAM7S161 that is missing.
If you happen to be using a SAM7S161, what you should be able to do is copy a header file from one
of the other 64-pin MCUs (e.g. AT91SAM7S256.h) and replace its memory mapping definitions (at
the bottom of the file) with those from the AT91SAM7S161.h file (which is supposed to be named
AT91SAM7S16.h). I don’t guarantee this to be completely correct though.
Starting a New Eclipse Project
When you start Eclipse it will ask you to choose a workspace folder. Each Eclipse project you make is
contained in its own folder, and all of your project folders will be stored in your workspace folder.
Put your workspace folder wherever is most convenient.
After Eclipse has loaded and you close the welcome page, it should look something like this:
Because this guide won’t go into using Eclipse for building and debugging, it’s enough to know that
the Project Explorer (the left pane) will show the directory layout of your project, and that the blank
pane in the middle of the screen will contain the text editor for writing the source files.
In the File menu select “New”, then “C Project”.
In the window that pops up, for the project name
choose something like “led_test_sam7s256” or
“led_test_sam7s16” depending on your MCU. For the
project type, expand the “Makefile project” folder and
select “Empty Project”.
Leave the toolchain as “Other” then click finish to
create the project.
At this stage we could start adding new source files to
the project, but there are a few other things to change
in the project settings in order to make better use of
the Eclipse IDE.
From the Project menu choose “Properties”. In the new window, expand “C/C++ General” and select
“Paths and Symbols” in the left pane.
Now you should see something similar to that shown below:
In the “Includes” tab, with “GNU C” selected as the language, you need to add the include directory
“C:\yagarto-20121222\lib\gcc\arm-none-eabi\4.7.2\include”. Change this appropriately if you have
a different YAGARTO version or if you installed YAGARTO to a different location.
This is required, because when you include a header file in your code such as “stdint.h”, Eclipse will
try to find it so that it can parse your code correctly and warn you of any errors. For example, if you
don’t tell Eclipse where “stdint.h” is, imagine you try to declare a variable of type “uint32_t” after
including “stdint.h” in a source file.
Eclipse will tell you that the use of uint32_t is an error in your code, which it isn’t, because if you
compiled your code using the YAGARTO toolchain, YAGARTO’s GCC would know where to look for
YAGARTO’s stdint.h file and there would be no problem.
In the same properties window, now switch to the “Symbols” tab. For similar reasons to before, add
the symbols shown in the following image:
Make sure the symbols have leading and trailing double-underscores, with single-underscores
between words. The purpose of defining these symbols for Eclipse is that the stdint.h header file,
used in this example project, contains “#if”, “#ifdef” and “#ifndef” C preprocessor directives reliant
on these symbols. Without defining them within Eclipse, Eclipse would parse the header files
incorrectly and wrongly notify you of errors in your code (as explained on the previous page).
Click “OK” to apply the settings and return to the main Eclipse window.
Copying Existing Files to the Project
There is one source file which has already been created, which is the AT91SAM7S256.h header file.
Minimize Eclipse, and simply copy the file from the Atmel library you downloaded and paste it into
the example project’s folder in Windows Explorer.
Maximize Eclipse again and expand the project in the Project Explorer. You might need to right-click
on the project and click “Refresh”, but what you should see is that Eclipse has now acknowledged
the header file as being part of the project. Note also that there is now an “Includes” item in the
Project Explorer, which is the result of the previous step.
Writing the Source Files
Creating the “sam7s_startup.s” Assembly Source File
From the File menu select “New”, then “Source File”. Type in “sam7s_startup.s” as the source file’s
name then click finish. In the editor that has opened, you will notice that the source file already
contains a comment based on a template. You can now fill in the rest of the file with the actual
assembly code (for this guide, put all of the files you create into the example project’s folder).
What follows is the completed source file*. I have broken it apart in order to discuss particular
sections, but the entire file is still included here (this format will be used for the rest of the files in
this guide as well). The comments should hopefully make clear most of the functionality.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/* File: sam7s_startup.s * Created on: 7/12/2012 * Author: Adam Goodwin * * Description: This is startup code which can be used for any of the SAM7S * chips. The startup code sets up the initial exception vectors, calls the * initialisation code specific to the SAM7S model being used, initialises RAM * by copying from ROM or setting to zero, and sets up the stack pointers for * each of the seven processor modes of the ARM7TDMI CPU. The code also embeds * a string to be stored near the beginning of ROM, indicating the software * version. * * To reiterate: this code is suitable for all SAM7S model MCUs, however the * initialisation code called from this file will need to be changed depending * on the SAM7 model in use (e.g. SAM7S16, SAM7S256 etc). The initialisation * code called in this particular file is found in sam7s256_init.c. * * Note: This code is based on the startup code used in the series of articles * "Building Bare-Metal ARM Systems with GNU" by Miro Samek. You can find the * articles at http://www.state-machine.com/resources/articles.php#ARM */ /* Program Status Register bit definitions: * When I_BIT is set, IRQ interrupts are disabled. When F_BIT is set, FIQ * interrupts are disabled. */ .equ I_BIT, 0x80 .equ F_BIT, 0x40 .equ USR_MODE, 0x10 .equ FIQ_MODE, 0x11 .equ IRQ_MODE, 0x12 .equ SVC_MODE, 0x13 .equ ABT_MODE, 0x17 .equ UND_MODE, 0x1B .equ SYS_MODE, 0x1F /* Constant for pre-filling the stack. When debugging, if none of this constant * is visible, it probably means a stack overflow has occurred. */ .equ STACK_FILL, 0xAAAAAAAA
* The files are included verbatim, so there are a lot of comments. Hopefully this will give a better
understanding of what each file is doing.
As you can see from the comments, this startup file can be used as-is for any of the SAM7S MCUs.
The file contains the first code that will be run every time the MCU is reset; it sets up the CPU and
RAM as required for using the C language, and calls MCU initialisation code located in another file.
***
43 44 45 46 47 48 49
/* Append to the .text section, and specify that 32-bit ARM instructions are * being used. */ .text .arm
The specification of the text section is to do with the linker. This will be explained further, when the
linker script file is discussed, but for now it is enough to say that line 46 is telling the assembler to,
during assembly, append the rest of the file to the .text “section”. The linker will take input sections,
such as .text, from the compiled or assembled object files, and then put them into the output
sections specified by the linker script. These output sections will then be loaded into specific
memory locations.
If you aren’t used to the syntax of assembly code, and want to look up specific aspects of it, the GNU
Assembler documentation is the place to go. The index page is especially useful if you want to look
up a particular item, like the text directive mentioned just previously.
***
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
/* The following code, being the exception vectors, needs to be linked at the * start of ROM. This isn't necessarily address zero, due to the remapping * operation possible with the SAM7S chips. */ .global _vectors .func _vectors _vectors: /* Vector table * This is used only until RAM is remapped to address zero instead of flash. */ LDR pc, =_reset /* Reset (Branches absolutely to _reset in flash) */ B . /* Undefined Instruction */ B . /* Software Interrupt */ B . /* Prefetch Abort */ B . /* Data Abort */ B . /* Reserved */ B . /* IRQ */ B . /* FIQ */ .size _vectors, . - _vectors .endfunc /* Embed in ROM a short string identifying the program. */ .string "Test LED Flashing Program" /* After including a string, the following ensures the location counter has * its two least significant bits equal to zero. In other words, the * assembly that follows will begin on a 4-byte boundary of ROM * corresponding to the 32-bit word length of the ARM CPU.
81 82 83 84
*/ .align 2
Lines 62 through to 69 are the first actual pieces of assembly code in the file; the body of the
_vectors function. You’ll notice that these are the ARM exception vectors. Provided the linker script
specifies that the code in this startup file is the first piece of code to put in flash memory, this would
put the exception vectors at address 0x0010 0000 (the beginning of flash). This is exactly what we
want, because when the MCU is powered on or reset, the flash memory is mapped to address
0x0000 0000 as explained in the Memory Remapping and Exception Vectors section of this guide.
Thus, the exception vectors are where the CPU assumes them to be (starting at address 0x0000
0000), and the correct result will occur in the event of an exception.
Note also that the reset exception vector is the only one which branches somewhere useful. The
other exceptions will enter infinite loops if they occur, as their vectors simply contain branches to
themselves. The reset exception vector’s instruction results in loading the absolute address (in flash)
of the _reset function to the program counter. From this a branch to the _reset function’s flash
address (something like 0x0010 003C) will occur. The idea of this is to stop running code from the
remap region as soon as possible, and instead start running code from the actual flash addresses.
This reduces the risk of strange bugs occurring due to address mappings moving about.
One final note in this section of code is about the .align directive. Generally, the align directive aligns
the output code to the specified byte boundary. For example, “.align 4” would align the code to the
next nearest 4-byte boundary.
The necessity of this is due to the string embedded in the code just prior to the use of the .align
directive. Being a 32-bit machine, each instruction for the SAM7S takes up 4 bytes (2 bytes in Thumb
mode). Therefore, for instructions to be fetched correctly, they must start on an address that is a
multiple of 4 (or 2 in Thumb mode). Otherwise, the CPU would fetch the end of one instruction and
the start of the next.
If the string doesn’t take up just the right amount of bytes, then the next instruction would be out of
the required alignment. This is why .align is used.
It is important to note however, that for ARM targets the .align directive does not work in the way
described in the “.align 4” example above. For ARM, “.align n” is equivalent to “.align 2n”. You can
think of the value n as being the number of least-significant-bits in an instruction address that need
to be zero (in order for an address to be pointing at the beginning of an instruction, and not part way
through one).
So where “.align 2” is used in the code above, 22 evaluates to 4, meaning that the code is aligned to a
4-byte boundary – which is what you would want for the 4-byte ARM instructions being used.
***
85 86 87
/* The reset function calls the appropriate initialisation code for the SAM7S * model being used. In this case, the initialisation function sam7s256_init is * called from the file sam7s256_init.c. When the MCU resets, the instruction
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
* at address 0x0 performs a branch to this location allowing the reset function * to run before anything else (see the vector table above). */ .func _reset _reset: /* NOTE: This startup file is suitable for any SAM7S chip, as long as the * following branch instruction calls an initialisation function appropriate * for the chip being used, in this case the SAM7S256. */ LDR r0, =_reset /* Pass the reset address as the 1st arg */ LDR r1, =_cstartup /* Pass the return address as the 2nd arg */ MOV lr, r1 /* Store return address in the link register */ LDR sp, =__stack_end__ /* Set the temporary stack pointer */ B sam7s256_init /* Branch to the initialisation function */
This is the beginning of the _reset function. In this first part of the function, the initialisation code
from a C file (discussed soon) is called before returning to the part of the _reset function labelled
“_cstartup”. Because C code is being called, the stack pointer needs to be set so that registers can be
pushed onto the stack. Here the stack pointer is set temporarily to the address “__stack_end__”
which is a symbol defined in the linker script.
Just make sure that the function being called, in this case “sam7s256_init”, is named appropriately
for the chip you’re using. This is the only adjustment you need to make to this assembly file.
***
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
_cstartup: /* NOTE: The following executes upon return from the chip-specific * initialisation code. At this time, the memory remap will have occurred, * and so address 0x00000000 will point to RAM. */ /* Copy the .fastcode section from ROM to RAM. */ LDR r0, =__fastcode_load LDR r1, =__fastcode_start LDR r2, =__fastcode_end 1: CMP r1, r2 LDMLTIA r0!, {r3} STMLTIA r1!, {r3} BLT 1b /* Copy the .data section from ROM to RAM (initialised variables). */ LDR r0, =__data_load LDR r1, =__data_start LDR r2, =_edata 1: CMP r1, r2 LDMLTIA r0!, {r3} STMLTIA r1!, {r3} BLT 1b /* Clear the .bss section to zero (clearing uninitialised variables). */ LDR r1, =__bss_start__ LDR r2, =__bss_end__ MOV r3, #0 1: CMP r1, r2
137 138 139 140 141 142 143 144 145 146 147 148
STMLTIA r1!, {r3} BLT 1b /* Fill in the .stack section. */ LDR r1, =__stack_start__ LDR r2, =__stack_end__ LDR r3, =STACK_FILL 1: CMP r1, r2 STMLTIA r1!, {r3} BLT 1b
In the above code, sections of flash are copied into RAM. The fastcode section which is copied from
ROM to RAM is simply user-specified pieces of code which are to be run from RAM in order to gain a
performance boost (as loading data from RAM during execution does not require wait states – which
are explained later).
The .data and .bss sections being copied to RAM are to do with variables. Obviously the point of
variables is that they can be written to, as well as read, so they therefore need to be located in RAM.
Initialised variables need to have their initial values copied to the correct location in RAM, and
uninitialized variables need to have their values located in RAM set to zero.
The stack section being filled in RAM is simply for debugging purposes. It doesn’t matter what the
values in the stack are initially, because they will only be read after they have been previously
written. However, if the stack is filled with the same value, then you can quickly tell when a stack
overflow has occurred during debugging. If none of the stack fill value is visible in RAM, it means that
the entire stack has been completely used, and so it’s quite probable that a stack overflow has
occurred.
All of the above symbols with leading and trailing double-underscores are addresses defined in the
linker script. So don’t worry about them for now.
***
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
/* Initialise the stack pointers for each of the exception modes. The * ARM7TDMI cpu has seven processor modes, each having their own individual * stack pointer. The only exception is that the system and user modes share * a stack pointer, so overall there are six stack pointers to initialise. * Each stack pointer is initialised by switching to a mode then setting * the stack pointer. * * For more information, see the ARM Architecture Reference Manual. */ MSR CPSR_c, #(IRQ_MODE | I_BIT | F_BIT) /* Switch to IRQ mode */ LDR sp, =__irq_stack_top__ /* Set the IRQ stack pointer */ MSR CPSR_c, #(FIQ_MODE | I_BIT | F_BIT) /* Switch to FIQ mode */ LDR sp, =__fiq_stack_top__ /* Set the FIQ stack pointer */ MSR CPSR_c, #(SVC_MODE | I_BIT | F_BIT) /* Switch to SVC mode */ LDR sp, =__svc_stack_top__ /* Set the SVC stack pointer */ MSR CPSR_c, #(ABT_MODE | I_BIT | F_BIT) /* Switch to ABT mode */ LDR sp, =__abt_stack_top__ /* Set the ABT stack pointer */
170 171 172 173 174 175 176 177 178 179
MSR CPSR_c, #(UND_MODE | I_BIT | F_BIT) /* Switch to UND mode */ LDR sp, =__und_stack_top__ /* Set the UND stack pointer */ /* This is for setting the system and user mode stack pointer. This is the * normal mode of operation. */ MSR CPSR_c, #(SYS_MODE | I_BIT | F_BIT) /* Switch to SYS mode */ LDR sp, =__c_stack_top__ /* Set the C stack pointer */
The ARM7TDMI CPU has seven processor modes, usually switched between when an exception
occurs. Two modes share a stack pointer, whereas for the other modes the stack pointer is
preserved separately for each one. Therefore to initially set up the stack pointers, each mode must
be forcibly switched to in order to assign the appropriate stack pointer address for that mode. The
last mode switched to is the system mode, as it is the normal mode of operation, so it will be
remained in for the rest of the code.
***
180 181 182 183 184 185 186 187 188 189 190 191 192
/* Enter the C code. */ LDR r12, =main MOV lr, pc /* Set the return address */ BX r12 /* The target code can be ARM or THUMB */ /* Cause an exception if main() ever returns. */ SWI 0xFFFFFF .size _reset, . - _reset .endfunc .end
Finally the _reset function ends, with a branch to the location of the main function. After this an
exception is caused, because if the program executes the SWI instruction it means that it must have
returned from the main function – which is not supposed to happen.
Creating the “board.h” Header File
After that last file, this one is nice and simple. The board.h header file contains only a couple of
definitions. These provide the rest of the code with the important details about the board you are
using. You would add to this file any additional information needed for your specific setup.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// File: dev_board.h // Created on: 10/12/2012 // Author: Adam Goodwin // // Description: This header file contains definitions specific to the board in // use. In this case, the board is the SAM7-H256 from Olimex. #ifndef DEV_BOARD_H #define DEV_BOARD_H // External oscillator frequency which provides the Main Clock, MAINCK, in Hz. #define MAINCK 18432000U // Master Clock frequency (PLL Clock divided by 2) in Hz. #define MCK 47923200U #endif // DEV_BOARD_H
First, notice that in my case I named the filename “dev_board.h” instead of just “board.h”. This is
because I was using a development board from Olimex. If you’re going to be using more than one
board then I suggest you also have multiple board.h files, each with an appropriate name. Create
them in the same way you created the startup assembly file.
Only two constants are defined in board.h. The first, MAINCK, is set to the frequency (in hertz) of the
external crystal connected to the MCU. So for example, with the SAM7-H256 development board I
used, an 18.432MHz crystal oscillator is connected to the MCU to provide the Main Clock signal. (See
the SAM7S datasheet for details on the clocks – I have included a brief rundown in the next section
though.)
The second constant defined in the file is MCK, which is the Master Clock frequency in hertz (the
Master Clock is the CPU clock rate, and also provides the clock signal to peripherals). Unlike the Main
Clock, the Master Clock (to an extent) isn’t defined by external components; it is instead configured
in software. If you intend to choose a different MCK frequency here, you will have to put an
approximate value for now and remember to come back later to change it to the exact value.
This is because you can’t select any frequency you want; you can only select frequencies which are
possible to reach using the available configuration options. The next section explains this further.
Here the nominal Master Clock frequency is about 48MHz; it is up to the code in the next section to
ensure this frequency is achieved. (The external crystal of 18.432MHz with a Master Clock of around
48MHz is a common configuration for SAM7S chips; the 48MHz is a requirement for the use of USB).
So for this example project these two definitions are the only ones required. Make sure you have set
them correctly for the board you’re using.
Creating the “sam7s256_init.c” C Source F i le
This file ties in with the last two in order to finish the configuration stage. Recall from the startup
assembly code that a chip-specific initialisation function was called. This file contains that
initialisation function, which configures things like the CPU clock, and sets up the exception vectors
in RAM. Make sure you name the function as you did in the assembly code, and also name the file
itself correctly for the chip you’re using.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// File: sam7s256_init.c // Created on: 10/12/2012 // Author: Adam Goodwin // // Description: This initialisation code is specifically for the SAM7S256 model // of MCU. There is one initialisation function which should be called during // the more general (valid for all SAM7S chips) startup assembly code. // // Some aspects of this initialisation code are dependent on the board which the // SAM7S256 chip is mounted on. These dependencies are handled by the board // header file included in this code. In this case, dev_board.h. // // The main purpose of this initialisation code is to set up the SAM7S256 // hardware, such as the processor clocks, as well as to remap the RAM to be // visible at address 0x0 (instead of ROM). // // The startup assembly code which utilises this file is found in // sam7s_startup.s. // // Note: This code is based on the initialisation code used in the series of // articles "Building Bare-Metal ARM Systems with GNU" by Miro Samek. You can // find the articles at http://www.state-machine.com/resources/articles.php#ARM #include <stdint.h> #include "AT91SAM7S256.h" #include "dev_board.h"
Make sure you include the appropriate Atmel header file, and use the correct name for the board
header file.
***
28 29 30 31 32 33 34 35 36 37 38 39 40
// This is the address where RAM starts, as defined by the linker script, i.e. // 0x00200000. extern uint32_t __ram_start; // The first of the following values is an LDR instruction, with the address // offset left blank. The bitwise or of this value with a 12-bit value results // in an instruction that branches to an address relative to the program // counter. The relative location, or offset, from the program counter is given // by the 12-bit value. Note that the value of the program counter, as seen by // an instruction, is the address two instructions ahead of that instruction. static uint32_t const LDR_PC_PC_0x18 = 0xE59FF000U | 0x18; static uint32_t const MAGIC = 0xDEADBEEFU;
At the end of this file, the SAM7S memory remapping feature will be used. This is in order to have
the exception vector addresses point to RAM instead of flash. Once exceptions start occurring after
the remap has been completed, the vectors in RAM will need to contain executable instructions.
Otherwise, when an exception occurs, the CPU will try to execute whatever random value happens
to be stored in RAM at the exception vector location. For this reason, a constant representing an
executable instruction is created:
The constant LDR_PC_PC_0x18 contains a “load register” ARM instruction, where a data word is
loaded from memory into a register. The destination register in this case is the program counter (PC)
– which therefore causes the program to branch. The CPU will treat the value loaded into the
program counter as an address, and so will branch to the address represented by that value.
The program counter is also used as the base register in this use of the instruction. The base
register’s value, plus some offset, is used as the address to load the data word from.
If you look at the Instruction Set Encoding section of the ARM Architecture Reference Manual, you’ll
see that 0xE59F F000 corresponds to the “load register” instruction described above, with the
program counter serving as both the destination and base registers.
Notice that the lower twelve bits (3 hexadecimal digits) of 0xE59F F000 are zero. This means that the
offset from the base register, which is specified in these lower bits, is also zero. This is why there is a
bitwise-or of 0xE59F F000 and 0x18 when defining LDR_PC_PC_0x18. This makes the offset 0x18,
completing the instruction.
So the instruction represented by LDR_PC_PC_0x18 causes a branch to an absolute address. The
absolute address is stored in memory, at a location relative to the current value of the program
counter. The address to branch to is stored in memory at the current value of the program counter
plus 0x18. The reason for this will become clear towards the end of the file.
***
41 42 43 44 45 46 47 48 49 50 51 52 53 54
// Function: sam7s256_init // // Description: This function is called by the startup assembly code after // initialising the C (user and system mode) stack, but before initialising the // segments in RAM. The function cannot rely on initialisation of any static // variables, because these have not yet been initialised in RAM. // // Parameters: // reset_addr: The address of the _reset function from the startup assembly // code. // return_addr: The address within the _reset function that this function // returns to. void sam7s256_init(void (*reset_addr)(), void (*return_addr)()) {
Here the initialisation function actually begins. Recall that in the assembly code the address of the
reset function and the return address within the reset function were passed as arguments. The
initialisation function configures high-importance peripherals of the MCU.
***
55 56 57 58 59 60 61 62 63
// Enable the User Reset. // // By default, a low level on the NRST pin will not result in the MCU being // reset. The reset pin needs to be enabled for any attached reset button // to work, as well as to allow resets via JTAG during programming and // debugging. The password of 0xA5 is required to confirm the setting is // intentional. AT91C_BASE_RSTC->RSTC_RMR |= AT91C_RSTC_URSTEN | (0xA5 << 24);
Here the reset pin is enabled. Without this, grounding the NRST pin would not reset the
microcontroller – i.e. reset buttons etc would not work. The register definitions and constants used
here, and throughout the rest of this file, come from the Atmel library header file. For information
about each peripheral and their associated registers consult the SAM7S datasheet.
***
64 65 66 67 68 69 70 71 72 73
// Set up the Embedded Flash Controller. // // Set the Flash Microsecond Cycle Number (FMCN) and the Flash Wait State // (FWS) fields of the Flash Mode Register (FMR). The FMCN bits of the FMR // are set to the number of Master Clock cycles that occur in one // microsecond (500000 is added to ensure rounding up). FWS is set to one, // as required for operation over 30MHz. AT91C_BASE_MC->MC_FMR = ((AT91C_MC_FMCN) & ((MCK + 500000)/1000000 << 16)) | AT91C_MC_FWS_1FWS;
The flash memory in the MCU can only be read from so quickly. Here the flash controller is
configured to have wait states, to stop attempts at accessing the flash from happening faster than
the flash can handle. You’ll only ever need one wait state at most, and don’t need any if you are
going to operate the MCU at below 30MHz.
Here you also need to specify the number of Master Clock cycles that occur in one microsecond, so
that the flash will operate properly.
***
74 75 76 77 78 79 80 81 82
// Disable the Watchdog Timer. // // The Watchdog Timer is enabled by default, with a default period of 16 // seconds. Note that the Watchdog mode register can only be written once // per processor reset. Therefore, if you want to set up the Watchdog Timer, // you must do this here (or elsewhere only if this write to the register is // removed). AT91C_BASE_WDTC->WDTC_WDMR = AT91C_WDTC_WDDIS;
The Watchdog Timer allows the microcontroller to automatically reset via software, if the timer ever
reaches zero. This means that if the program gets stuck in a while loop, for example, after a set
amount of time in the loop the MCU will reset (not if you coded the loop to restart the Watchdog
Timer though). Here we disable the Watchdog Timer.
Now is a good time to summarise the clocks on the SAM7S MCUs:
The MCU’s Clock Generator can output three different clock signals for use by the rest of the MCU.
The first is the Slow Clock (SLCK), which is built-in and runs at 32.768kHz. The second is the Main
Clock (MAINCK), which is determined by your choice of external crystal (here it is 18.432MHz). The
third clock signal is the PLL Clock (PLLCK), which can be configured to a frequency equal to MAINCK
divided and multiplied by some user-defined values.
One of these three sources is chosen to be prescaled, and this prescaled clock signal then provides
the Master Clock (MCK). From here the Master Clock directly provides the Processor Clock (PCK) and
the clock signal for the peripherals. Upon reset, the Slow Clock is the default signal used.
***
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
// Enable the Main Oscillator. // // For an external crystal of 18.432MHz, the datasheet specifies (in the // table of Main Oscillator Characteristics) that the startup time for the // the Main Oscillator will be somewhere between 1ms and 1.4ms. The OSCOUNT // field of the MOR register therefore needs to be set appropriately for // this startup time. OSCOUNT contains the number of times that the Slow // Clock (32768Hz) should go through 8 cycles in order to allow the startup // time to have elapsed. // // For the startup time of 1.4ms, this equates to 45.8752 cycles of the Slow // Clock (0.0014s * 32768Hz). The Main Oscillator will be stable after this // many Slow Clock cycles. Therefore OSCOUNT is set to 6 (45.8752 / 8 // rounded up). The enable bit is also set at this time. AT91C_BASE_PMC->PMC_MOR = ((6 << 8) & AT91C_CKGR_OSCOUNT) | AT91C_CKGR_MOSCEN; // Wait for the startup time to elapse and the Main Oscillator to stabilise. while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MOSCS));
To configure the Main Oscillator, the startup time needs to be defined in terms of the Slow Clock,
and the oscillator needs to be enabled. After this the code then busy-waits until the startup time has
elapsed and the oscillator has stabilised. You will need to ensure that the startup time is correct if
you have used a different external crystal.
***
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
// Configure the PLL. // // Set the divider field to 5 to get 3.6864MHz (18432000Hz / 5). Set the // multiplier field to 25 to get 95.8464MHz (3.6864MHz * (25 + 1)). // Similarly to OSCOUNT above, PLLCOUNT must also be calculated. This // depends on the low-pass filter connected to the PLLRC pin. Atmel has a // tool available for calculating startup times, it is available on their // website at http://www.atmel.com/tools/ATMELPLLLFTFILTERCALCULATOR.aspx // With the values R = 1K, C1 = 10nF and C2 = 1nF, Atmel's tool gives a // worst-case startup time of 0.865ms. Therefore, PLLCOUNT should be set to // 29, as 29 Slow Clock ticks will take just over this amount of time // (0.000865s / (1 / 32768Hz)). AT91C_BASE_PMC->PMC_PLLR = (AT91C_CKGR_DIV & 0x05) | (AT91C_CKGR_PLLCOUNT & (29 << 8)) | (AT91C_CKGR_MUL & (25 << 16)); // Wait for the startup time to elapse. while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_LOCK));
119 120 121
// Wait for the Master Clock to become ready. while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY));
In the above code the PLL is configured. This consists of setting the divider, and multiplier, to get the
desired output PLL frequency. Note that there is a trick; the multiplier value stored in the register is
actually increased by one before being used to multiply the input signal frequency.
As with the Main Oscillator, a startup time in terms of Slow Clock cycles needs to be defined. To
calculate the startup time, you can use the Excel-based tool provided by Atmel found here. You need
to know the Low-Pass Filter configuration connected to the PLLRC pin in order to use this tool (as
well as the clock signal frequencies you desire).
If you have an 18.432MHz crystal, or if you are even using the same board as the one used in this
guide, you don’t need to worry about changing these settings (provided you are happy with the CPU
running at 48MHz).
***
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
// Configure the Master Clock and CPU clock. // // The CPU clock is simply the Master Clock, with the addition of being able // to be disabled (set to idle mode). The maximum clock speed is 55MHz, so // the Master Clock shall be set to the PLL Clock with a prescaler setting // of 2 to give 47.9232MHz (95.8464MHz / 2). AT91C_BASE_PMC->PMC_MCKR = AT91C_PMC_PRES_CLK_2; // Wait for the Master Clock to become ready. while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY)); // Set the Master Clock to the PLL Clock now that the prescaler has been set // to 2, meaning that the clock speed won't go over the 55MHz limit. AT91C_BASE_PMC->PMC_MCKR |= AT91C_PMC_CSS_PLL_CLK; // Wait for the Master Clock to become ready. while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY));
As the final clock settings, the Master Clock (and thus the CPU clock) is configured. You’ll recall from
the dev_board.h header file that the nominal MCK frequency was around 48MHz, but was actually
specified as 47.9232MHz exactly. If you choose to set a different MCK frequency, you’ll have to
figure out what the closest you can get to it is through the PLL configuration and MCK configuration
settings (and by using the Atmel tool linked above). Remember to go back and change the board
header file once you have worked out the exact MCK frequency.
So here, when the Atmel tool is used and its restrictions on PLL multiplying/dividing values are
obeyed, 47.9232MHz is an attainable frequency close enough to 48MHz. It is achieved by PLLCK
being 95.8464MHz, as per above, then setting the prescaler to two for the MCK.
Note that the prescaler has to be set to two before switching the Master Clock source over to the
PLL Clock. Otherwise you’d be operating the CPU at 96MHz for a short amount of time, and the
maximum allowable clock speed specified in the datasheet is 55MHz.
***
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
// Setup the exception vectors in RAM. // // This is done before the memory remap to ensure that there are valid // exception vectors at address 0x0 as soon as the remap is completed. // // First, below, is the primary vector table, located at the very beginning // of RAM. Stored in these vectors are ARM instructions which branch to an // address found 32 bytes forward relative to each instruction. In other // words, the vectors in the primary vector table branch to the address // specified in the corresponding entry of the secondary vector table. Note // that 0x18 is used to specify the relative location of the entries in the // secondary table, not 0x20, as the program counter of the ARM7TDMI is 8 // bytes ahead of the executing instruction when in ARM mode // (0x18 + 0x08 = 0x20 = 32). See the ARM Architecture Reference Manual's // Registers section for more detail. // // You might find it strange that the address of __ram_start is used, and // not its value. This is because of how the linker, ld, creates symbols. // When the linker script defines __ram_start as the starting address of // RAM, it does not actually set the value of __ram_start to the address. // Instead, the value of __ram_start is undefined, and __ram_start's address // is set to the desired value. Therefore, to obtain the starting address of // RAM in C code, you access the __ram_start variable's address, not its // value. (Using the & operator). *(uint32_t volatile *)(&__ram_start + 0x00) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x01) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x02) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x03) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x04) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x05) = MAGIC; *(uint32_t volatile *)(&__ram_start + 0x06) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x07) = LDR_PC_PC_0x18; // Secondary vector table. *(uint32_t volatile *)(&__ram_start + 0x08) = (uint32_t)reset_addr; *(uint32_t volatile *)(&__ram_start + 0x09) = 0x04U; *(uint32_t volatile *)(&__ram_start + 0x0A) = 0x08U; *(uint32_t volatile *)(&__ram_start + 0x0B) = 0x0CU; *(uint32_t volatile *)(&__ram_start + 0x0C) = 0x10U; *(uint32_t volatile *)(&__ram_start + 0x0D) = 0x14U; *(uint32_t volatile *)(&__ram_start + 0x0E) = 0x18U; *(uint32_t volatile *)(&__ram_start + 0x0F) = 0x1CU;
This is near the end of the initialisation code. Line 161 through to line 176 is the setup of the
exception vectors in RAM.
To start with, look at the primary vector table. Here the ARM instruction discussed earlier, defined as
LDR_PC_PC_0x18, is being assigned to the first RAM addresses (with the exception of 0x14, but we’ll
get to that later). So we are filling in the exception vectors in RAM with legitimate ARM instructions,
as originally intended.
Now it’s time to understand why the instruction is the one that it is. Recall that LDR_PC_PC_0x18
causes a branch to an absolute address. The absolute address to branch to is stored in memory, at a
location relative to the current value of the program counter. The relative location is the program
counter plus 0x18.
First of all, why is the address stored at the program counter plus 0x18? Well, first you need to
understand something about the program counter and the ARM7TDMI pipeline:
Each instruction actually completes in three stages. First the instruction is fetched, then decoded,
then executed. When one instruction is being executed, the next is being decoded, and the next
again is being fetched. So by the time an instruction is being executed, the program counter is
actually pointing two instructions ahead – at the one being fetched. Each instruction (in ARM mode)
is 4 bytes long, and so at any given time the program counter is actually at the address of the current
instruction plus 8.
With this in mind, recall once again that the LDR_PC_PC_0x18 instruction requests a data word from
memory – at the address of PC + 0x18, As we’ve just learned, PC + 0x18 will equate to the address of
the current instruction plus 0x20 (because 0x18 + 0x08 = 0x20).
Now, how big is the vector table? The vector table contains eight 32-bit vectors/addresses. This
equates to 32 bytes which in hexadecimal is 0x20.
So picture the secondary vector table positioned in memory right after the first. To reach the first
entry in the secondary vector table, if you’re at the first entry in the primary table, you would take
the address of the current instruction and add the size of the vector table – i.e. 32 or 0x20.
What this all means is that the LDR_PC_PC_0x18 instruction, which is located in each of the RAM
exception vectors, branches to an address stored in the corresponding entry of the secondary vector
table.
In other words, through software you can specify an exception handler by storing its address in the
secondary vector table – located in RAM. Then, when the exception occurs and the CPU branches to
the exception vector, it will load this address from the secondary vector table into the program
counter. The CPU will then branch again to your exception handler.
If you look at the secondary vector table, you can see that all of the addresses are just the original
exception vectors – except for the reset exception. This is to cause infinite loops to result if any of
the unhandled exceptions occur.
The reset exception differs because you do not want a reset to result in an infinite loop. Instead the
CPU should branch to the address of the actual reset function in flash. The infinite loop wouldn’t
occur with a hardware reset via the NRST pin; however it is possible for the infinite loop to occur if
you implement it. If a software reset of the CPU is performed, and a software reset of the
peripherals isn’t performed, this would result in a reset which would use the RAM exception vector
with the infinite loop (a peripheral reset or loss of power is what undoes the remapping to RAM
operation, so not performing one is what allows the RAM reset exception vector to be used).
***
180 181 182 183 184 185 186 187 188 189
// Remap the memory. // // This is operated by a toggle only, so remapping needs to be performed // only if it has not been done already. You might not think this a problem, // as upon a reset the remap region at address 0x0 points to flash. However, // if a soft reset is performed, e.g. during debugging, there is no // guarantee that this assignment of flash to the remap region has occurred. // // To check the state of the remap region, we see if address 0x14 contains // MAGIC. If it does, then the remap region is already pointing to RAM. If
190 191 192 193 194 195 196 197
// it doesn't, then the remap region is pointing to ROM, and a remap toggle // needs to occur. if (MAGIC != (*(uint32_t volatile *)0x14)) { AT91C_BASE_MC->MC_RCR = 1; } }
On the subject of remapping, the last action performed by the initialisation function is to remap the
memory, so that address 0x0000 0000 refers to RAM and not flash; thus enabling the newly
programmed exception vectors. The remap operation is a toggle only, so the code needs to
somehow check whether the remap region of memory is currently mapped to flash or RAM.
This is why the unused exception vector, 0x14, was set to the value MAGIC (which is equal to
0xDEADBEEF). If the remap region is set to RAM, writing 0x14 will be successful. If the remap region
is set to flash, the write to 0x14 will have no effect (flash is ROM). To decide whether or not to
perform a remap, address 0x14 is read. If its value is 0xDEADBEEF, then the remap doesn’t need to
occur as RAM is already mapped to address 0x0000 0000.
***
There is something that should be mentioned regarding this file. For a line such as the following:
162 *(uint32_t volatile *)(&__ram_start + 0x01) = LDR_PC_PC_0x18;
It might not be entirely clear, to those unfamiliar, how exactly the four-byte value of
LDR_PC_PC_0x18 is being written to address 0x0020 0004. This is what must be happening, as the
line is supposed to be writing the second exception vector in RAM (which would be the second four
bytes of RAM).
The right hand side of the assignment is straightforward, but what about the left? Well, the first
segment, (uint32_t volatile *), casts the expression (&__ram_start + 0x01) to a pointer to a volatile
uint32_t. The first asterisk on the line (on the far left) is then dereferencing this pointer, so that the
assignment is storing the value of LDR_PC_PC_0x18 to the address pointed to by the pointer
(uint32_t volatile *)(&__ram_start + 0x01).
This still leaves some confusion about how the (&__ram_start + 0x01) portion evaluates to 0x0020
0004 (this expression is what gets used as a pointer to the second exception vector in RAM, so it
must necessarily evaluate to 0x0020 0004).
Obviously &__ram_start must be 0x0020 0000 based on its name alone, but how is this so? On top
of that, &__ram_start being 0x0020 0000 would imply that the constant of “0x01” is somehow equal
to 0x04, in order to bring the result of the expression to 0x0020 0004. How can that be!?
To understand all of this we need to go back to line 30:
28 29 30
// This is the address where RAM starts, as defined by the linker script, i.e. // 0x00200000. extern uint32_t __ram_start;
The variable __ram_start is declared as extern, meaning that the actual variable definition is
somewhere else. The “somewhere else” is in the linker script, but this is where things get a bit
strange.
When the linker script defines the symbol __ram_start as the address of the start of RAM, it doesn’t
make a variable whose value is 0x0020 0000. Instead, __ram_start is just a symbol at address 0x0020
0000. This is just how linker symbols work.
So in your C code, when you want to get the 0x0020 0000 value out of __ram_start, what you
actually need to do is get its address, not its value, hence the leading ampersand.
***
So that clears up half of our problem. We now know how &__ram_start equals 0x0020 0000.
But it is still extremely strange that, in the vector tables, each exception vector is accessed by adding
a value, from 0 to 15, to the base &__ram_start address. After all, each exception vector is four
bytes long, so shouldn’t each exception vector after the first be accessed by adding multiples of four
to the &__ram_start address?
Here’s a few of the lines again:
161 162 163
*(uint32_t volatile *)(&__ram_start + 0x00) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x01) = LDR_PC_PC_0x18; *(uint32_t volatile *)(&__ram_start + 0x02) = LDR_PC_PC_0x18;
For example, take a look at line 162. When 0x01 is added to &__ram_start, the idea is to get an
address four bytes further through RAM (to get to the next exception vector). But how does that
work when one is being added to &__ram_start, and not four?
The answer is that the addition of 0x01 to a pointer does not mean “add one to the address”. What
it actually means is “add one of the pointer’s value’s type’s size in bytes to the address”.
To be clearer: When you add to a pointer to change the address, it’s the type that the pointer points
to which determines how much the address changes by.
So when __ram_start is a uint32_t, adding 0x01 to &__ram_start increases the address by one times
the size (in bytes) of a uint32_t. In other words, because the size of a uint32_t is four bytes, the
result of &__ram_start + 0x01 is as intended – the address is that of &__ram_start increased by
four.
The reason why the operation works like this is in order to make addressing elements of an array
safer in C. If you have the base address of an array, and add an integer value to it, you move the
address forward by that many array elements. It stops you from accidentally moving part way
through an array element.
So you (hopefully) now have a full understanding of how the code for those vector tables actually
works.
***
I expect these first code files may have left you confused, but it’s to be expected with an MCU so
much more complicated than you might be used to – especially as you can often get around dealing
with this low level configuration with simpler MCUs. With a SAM7S there’s not really any avoiding it.
The rest of the code files are much simpler though, and should be much more in line with what you
are used to.
Creating the “main.c” C Source Fi le
We are now at the stage of actually writing the LED-flashing program for the MCU. The program is
very simple, here it is:
1 2 3 4 5 6 7 8 9 10 11 12 13
// File: main.c // Created on: 7/12/2012 // Author: Adam Goodwin // // Description: This is a basic test program which is to flash LEDs connected // to a SAM7S256. The program simply drives the specified output pins // alternately high and low, making it easy to change the pins to suit your // board configuration. #include <stdint.h> #include "AT91SAM7S256.h" #include "delay.h"
The delay.h header file is coming up next; just know that it allows you to call a delay function for a
specified number of milliseconds. Remember to include the right Atmel header file.
***
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
int main(void) { uint8_t i = 0; // The pins connected to the LEDs to be toggled. uint32_t pins = AT91C_PIO_PA5 | AT91C_PIO_PA6 | AT91C_PIO_PA7 | AT91C_PIO_PA8 | AT91C_PIO_PA9 | AT91C_PIO_PA10 | AT91C_PIO_PA11 | AT91C_PIO_PA12; // Enable the peripheral clock. AT91C_BASE_PMC->PMC_PCER |= 0x1 << AT91C_ID_PIOA; // Set the pins as GPIO and not peripherals. AT91C_BASE_PIOA->PIO_PER = pins; // Now set the pins as output. AT91C_BASE_PIOA->PIO_OER = pins; // Initialise the delay module. delay_init();
The configuration for the LEDs here should really be in another module, but for such a simple
program this is ok. The pins variable is used to define which input/output (IO) pins have LEDs
attached so change this to suit your board.
Enabling the IO is quite simple. First the peripheral clock is enabled for the Parallel IO controller.
Then the Parallel IO controller is configured to specify that the LED pins are to be user-controlled IO,
and not peripheral-controlled IO. Finally, the pins are set as outputs and not inputs.
You’ll also notice that the delay module is initialised here. The contents of this initialisation function
will be revealed in the next section.
***
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
// Flash the LEDs quickly three times. for (i = 0; i < 3; i++) { // Turn the LEDs off. AT91C_BASE_PIOA->PIO_CODR = pins; // Wait 200ms. delay(200); // Turn the LEDs on. AT91C_BASE_PIOA->PIO_SODR = pins; // Wait 200ms. delay(200); } // Now flash the LEDs slowly indefinitely. while (1) { // Turn the LEDs off. AT91C_BASE_PIOA->PIO_CODR = pins; // Wait 1000ms. delay(1000); // Turn the LEDs on. AT91C_BASE_PIOA->PIO_SODR = pins; // Wait 1000ms. delay(1000); } return 0; }
Next the program does a quick three flashes of the LEDs using a for loop and the i variable declared
at the start of the main function.
Finally the program just loops indefinitely: Turning off the LEDs, waiting for one second, turning the
LEDs back on, and then waiting again before repeating.
To learn more about what the register settings used throughout this file are, look up the peripherals
in the SAM7S datasheet and search for the register field names (e.g. CODR or SODR). You can also
put the cursor on a constant in Eclipse’s text editor (for example PIO_CODR), then right click on it
and choose “Open Declaration” to have Eclipse go to the source of the constant or variable.
Creating the Delay Module
The delay module is what is used by main.c to control the timing of the LEDs’ flashing. The module
makes use of one of the SAM7S MCU’s Timer Counters and is comprised of a header file (delay.h)
and a C source file (delay.c). We will first look at the header file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// File: delay.h // Created on: 19/12/2012 // Author: Adam Goodwin // // Description: This delay module provides a means to perform basic busy-wait // loops using Timer Counter 0. #ifndef DELAY_H #define DELAY_H #include <stdint.h> #define MAX_DELAY_MS 1000U #define TC0_MCK_DIV 1024U // Function: delay_init // // Description: This function configures the timer counter to allow the timing // of delays to be accurately measured. void delay_init(void); // Function: delay // // Description: This function implements a delay by busy-waiting for the // specified number of milliseconds. The delay is capped to MAX_DELAY_MS. // // Parameters: // milliseconds: The duration of the delay in milliseconds. void delay(uint16_t milliseconds); #endif // DELAY_H
The header file defines two constants and declares two functions. The constants are the maximum
delay that will be allowed, and the division of MCK that will be used for timing.
The functions are for initialisation, and for causing a delay to occur.
Now we will look at the implementation of the module in delay.c:
1 2 3 4 5 6 7 8 9 10 11 12
// File: delay.c // Created on: 19/12/2012 // Author: Adam Goodwin // // Description: The implementation of the module declared in delay.h. #include <stdint.h> #include "delay.h" #include "AT91SAM7S256.h" #include "dev_board.h"
Here we have the usual includes. You know the drill – just make sure they’re named correctly for
your setup.
***
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
void delay_init(void) { // Enable the Timer Counter 0 peripheral clock. AT91C_BASE_PMC->PMC_PCER |= 0x1 << AT91C_ID_TC0; // Set the input clock source of the timer counter as the Master Clock // divided by 1024. Set the timer counter to stop and disable once the // compare value is reached. Select the waveform to count up without // resetting at the compare value. Also enable waveform mode. AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV5_CLOCK | AT91C_TC_CPCSTOP | AT91C_TC_CPCDIS | AT91C_TC_WAVESEL_UP | AT91C_TC_WAVE; }
The delay_init function needs to be called before you try to use a delay. This initialises the Timer
Counter for use by the delay function.
First, as with the Parallel IO Controller in the main.c file, the Timer Counter peripheral clock is
enabled. Being a timer, Timer Counter Zero (TC0) needs access to the Master Clock signal if it is to
work.
Line 22 onwards is simply setting the operation of TC0 to a configuration appropriate for our
purposes. If you want to know more, use the SAM7S datasheet to look at the Timer Counter
documentation (in the Peripherals section). Just look at what each of the five Timer Counter Channel
Mode Register (TC_CMR) fields (set in the above code) do for the operation of the Timer Counter.
***
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
void delay(uint16_t milliseconds) { // Cap the delay. if (milliseconds > MAX_DELAY_MS) { milliseconds = MAX_DELAY_MS; } // Set the Timer Counter 0 compare value. This depends on the Master Clock // rate, the divider setting from the delay initialisation function, and the // specified delay in milliseconds. AT91C_BASE_TC0->TC_RC = ((MCK / TC0_MCK_DIV) * milliseconds) / 1000U; // Enable the Timer Counter 0 clock, and trigger it (reset the counter and // start the clock). AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; // Wait until an RC Compare has occurred. This means the timer has reached // its target and so the delay is finished. while (!(AT91C_BASE_TC0->TC_SR & AT91C_TC_CPCS)); }
The delay function itself begins by capping the user-specified delay to the maximum duration
defined in the header file.
Line 40 sets the TC0 Compare Value, which is the value that the counter will have to reach before
the delay is ended. The specified delay in milliseconds has to be converted into ticks of the timer, so
this therefore is dependent on the Master Clock speed and division used.
If you’ve used a different MCK speed than that assumed in this guide, you will have to rewrite line 40
to calculate the delay duration correctly. Note that if this means changing the division of the Master
Clock used, you will also have to change the constant defined in delay.h and the constant used in
delay_init (which is currently AT91C_TC_CLKS_TIMER_DIV5_CLOCK).
Another thing to keep in mind is how you use parentheses to ensure that the calculation never
results in an overflow, which may also require a different MAX_DELAY_MS value. With all of the
multiplying and dividing you can get some quite large numbers – or lose some accuracy during
divisions. Careful use of ‘U’ characters, appended to numerical constants, is also advised to ensure
that they are interpreted as unsigned values.
The remaining two lines of code (44 and 48) simply start the timer, then busy-wait until the compare
value is reached (as indicated by TC0’s Channel Status Register).
***
This concludes the discussion of the source files of the example project. What remains now is to
discuss the files needed for building the project and getting the code running on the MCU.
Scripts and Configuration Files
In this section the remainder of the project files are created. These files are what get the source
code running on the MCU. They are:
- led_test_sam7s256.ld*
- run_openocd.bat
- uc-sam7-usbjtag.cfg
- makefile*
Creating the “led_test_sam7s256.ld” Linker Script
When you create the new file in Eclipse, make sure you name the linker script appropriately for the
SAM7S chip you’re using.
The purpose of the linker script is to define how to create the LED-flashing program’s executable file.
When you compile your source code, you are left with object files – one for every source code file.
These need to be combined together, along with any required library object code, to give the final
executable as one individual file. This is what the GNU Linker does.
There is another important aspect to the linker as well. Object code files, due to being separate files
with no idea of how they are to be connected by the linker, need to leave some addresses
unspecified. The object code can’t know where all instructions are going to be located in memory; it
depends on where the linker puts each file in the final executable.
So it is the linker’s job to fill in these remaining addresses, and this linker script tells it how.
When looking through the linker script, to find out more about any particular aspect of the script,
you should go to the GNU Linker documentation.
*These files are based on files from Miro Samek’s tutorial (Samek, 2007).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/* File: led_test_sam7s256.ld * Created on: 12/12/2012 * Author: Adam Goodwin * * Description: This linker script is specifically for the SAM7S256 MCU, where * the program to be loaded is the led_test_sam7s256 program from this project. * * The linker script takes input sections (such as .text, .rodata etc) from * compiled and assembled code, and organises them in output sections (such as * .reset, .ramvect etc). The organisation of the output sections, and the * resulting linker output file, determines the layout of the data in the MCU's * memories (RAM and ROM/flash). * * Note: This script is based on the linker script used in the series of * articles "Building Bare-Metal ARM Systems with GNU" by Miro Samek. You can * find the articles at http://www.state-machine.com/resources/articles.php#ARM */ OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_vectors)
These first few lines set some information for the linker: The endianness of the executable, and the
architecture of the CPU. The entry point of the program is also specified (it is the _vectors function
from the startup assembly code).
***
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/* Memory map of the AT91SAM7S256. Simply change this to adapt the linker script * for loading the same program to a different SAM7S chip. */ MEMORY { ROM (rx) : ORIGIN = 0x00100000, LENGTH = 256k RAM (rwx) : ORIGIN = 0x00200000, LENGTH = 64k } /* The sizes of the stacks used by the application. */ C_STACK_SIZE = 512; IRQ_STACK_SIZE = 0; FIQ_STACK_SIZE = 0; SVC_STACK_SIZE = 0; ABT_STACK_SIZE = 0; UND_STACK_SIZE = 0;
Here the MEMORY command is telling the linker what areas of memory are available for use. It
specifies attributes (such as readable, writable etc) describing the memory regions, it specifies
where the memory regions are located, and it specifies what sizes the memory regions are.
ROM (which refers to the internal flash) and RAM (which refers to the internal SRAM) are the
memory regions that we are telling the linker about. You could also specify the remap region,
although it’s not needed for this guide.
Potentially, this is the only part of the linker script that you’ll need to change. If you’re not using a
SAM7S256 then you’ll need to specify the correct lengths of the ROM and RAM for your chip.
This area of the script also contains the stack size definitions. The C stack size, (for the stack shared
between the user and system processor modes,) is arbitrarily set to 512 bytes. The remaining stack
lengths are set to zero, as the example code doesn’t do anything in these processor modes other
than enter infinite loops – so a stack isn’t needed.
***
40 41
SECTIONS {
Now to explain a little bit about sections: When source code is compiled or assembled to object
code, the object code is organised into sections. For example, executable code is placed in the .text
section, and initialised global variables are placed in the .data section.
The linker takes these input sections from object code and places them into output sections. The
output sections are then organised into memory. This all happens in the SECTIONS command’s block,
which is enclosed by curly braces.
***
42 43 44 45 46 47
.reset : { *startup.o (.text) /* Startup code from assembly startup file */ . = ALIGN(0x4); } >ROM
The first output section is the .reset section. Take a look at line 44: Here all .text input sections are
placed in the .reset output section, provided they come from an object file with “startup” at the end
of the filename. Hence the startup code is placed into memory before anything else – as long as the
startup file is named correctly. This is what we want because the startup code begins with the
exception vectors – so it needs to be the first thing in flash for the program to run. The use of
“>ROM” after the .reset section is what places it in the location of flash.
On line 45, the dot or “location counter” is aligned to a 4-byte word boundary. Recall from the
assembly code that a similar function was performed there. This is the same, except you might also
recall that the assembler required “.align 2” for ARM targets, which is not the case here.
The dot symbol in the linker script refers to the byte offset from the start of the current containing
object. So here the containing object is the .reset output section, which is in turn contained within
the SECTIONS command. Whenever you place some data in a containing object, the location counter
for that object is advanced by the number of bytes of data you placed.
So let’s say that the .text section of sam7s_startup.o is 101 bytes long. After placement in the .reset
output section, the .reset section’s location counter will be advanced by 101 (setting it to 101,
because it was at zero to begin with).
After the alignment in line 45, the location counter within the .reset output section will be set to
104. Then, because the .reset section has been placed in the SECTIONS command, the location
counter for the SECTIONS command will also advance by 104 (0x68). However, because .reset was
placed in ROM (with starting address 0x0010 0000), the location counter for the SECTIONS command
would actually advance to 0x0010 0068.
You can also assign values to the location counter to set it directly, in order to skip forward or
backward a number of bytes (this is how the ALIGN is performed). You can’t skip the location
counter backwards within an output section though, or backwards outside of an output section
either – in this case only if it will cause an overlap of data (see the Location Counter page of the
linker documentation for more details).
This ability to skip the location counter backwards (when outside of an output section) is how you
are able to place something in RAM with “>RAM”, then still go back to place something in ROM with
“>ROM”.
***
48 49 50 51 52 53
.ramvect : { __ram_start = .; /* Used for vectors remapped to RAM */ . = 0x40; } >RAM
The next output section is .ramvect, which you’ll notice is placed in RAM (with “>RAM”). No data of
any use is included here. Instead, the location counter is simply skipped to 0x40 to artificially make
the .ramvect section 0x40 bytes long – allowing space for the exception vectors when they are
written to RAM in the initialisation C code.
You will notice that the __ram_start symbol, used in the initialisation code, is defined here on line
50. It is set to the location counter at the beginning of the .ramvect section, which would be 0x0 at
this time. This means that __ram_start refers to the address offset 0x0 bytes forward from the
address of the .ramvect section.
In other words, __ram_start is a symbol at the same address as the start of the .ramvect section.
Because .ramvect is the first section placed in RAM, its start address is the same as the start address
of RAM. Therefore, __ram_start is at the same address as the start of RAM, which is 0x0020 0000.
This is exactly in agreement with how __ram_start was used in the initialisation C code.
***
54 55 56 57 58 59 60 61 62 63 64 65 66
.fastcode : { __fastcode_load = LOADADDR(.fastcode); __fastcode_start = .; * (.glue_7) * (.glue_7t) * (.text.fastcode) /* Add other code modules to be executed from RAM here. There are two * options. Using the following example C function, the two methods are * below: *
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
* int example(int a) * { * return a; * } * * Method 1. You can set the GCC compiler option -ffunction-sections, * which allows you to specify individual functions here in the linker * script. So to put the example function in RAM, you would put this * line in the linker script within this .fastcode output section: * * * (.text.example) * * Method 2. You can use GCC function attributes. These can be placed * after a function declaration (on the same line before the semicolon), * or before a function definition on the above line. Like so: * * int example(int a) __attribute__ ((section(".text.fastcode"))); * * Or: * * __attribute__ ((section (".text.fastcode"))) * int example(int a) * { * return a; * } * * Note that for the second method the appropriate line is already in * the .fastcode section of this linker script. So the choice depends on * whether you want to edit the linker script or the source code file. */ . = ALIGN(0x4); __fastcode_end = .; } >RAM AT>ROM
With the .fastcode section comes a few new concepts. Notice that following the section is “>RAM
AT>ROM”.
It’s time to introduce the idea of the Virtual Memory Address (VMA) and the Load Memory Address
(LMA). VMAs are what we’ve been dealing with previously; when a section is followed by something
like “>RAM” or “>ROM” only.
The VMAs are the addresses given to sections. So when the code is run, the CPU is looking to the
VMAs for the location of instructions. But the addresses that are given to the sections don’t
necessarily have to be where the data is actually loaded.
The LMAs are the address to which the data is loaded, and these are specified with the AT> keyword.
Like this: “AT>ROM”.
So with the .fastcode section: If you’ve told the CPU that the code is in RAM using “>RAM”, but
you’ve actually loaded it to ROM by appending “AT>ROM”, then you need to copy the code from
ROM to where you said it would be (in RAM) before the CPU tries to run it. If you recall, that’s
exactly what was done in the startup assembly code; any data that needed to be in RAM was copied
over from ROM.
The only question that remains is how the assembly code knows where to copy the data from, and
where to put the copied data. This is accomplished in the .fastcode section with the
__fastcode_load, __fastcode_start and __fastcode_end symbols.
The latter two are just like the __ram_start symbol. These mark the start and end of the RAM
addresses where the “fastcode” should go. It’s the __fastcode_load symbol that’s different. Rather
than using the location counter on its own, which would give a relative VMA, __fastcode_load is
assigned to be “LOADADDR(.fastcode)”. As the function name “LOADADDR” suggests, this returns
the LMA of the specified section.
So these symbols give to the assembly code the area in RAM that the data needs to be copied to, as
well as the location in ROM of the first piece of data to be copied. This is enough information to get
the job done.
The only remaining thing that needs to be said about the .fastcode section is regarding how to
actually specify the code to be placed in the section – in order for faster execution from RAM (don’t
worry about what the .glue_7 and .glue_7t input sections mean for now). The comments in the
linker script should do a good enough job of explaining the two methods of placing code in the
.fastcode section.
***
102 103 104 105 106 107 108 109 110 111 112 113 114 115
.text : { . = ALIGN(0x4); * (.text) /* .text sections (code) */ * (.text*) * (.rodata) /* .rodata sections (constants, strings, etc) */ * (.rodata*) * (.glue_7) /* "Glue" ARM to Thumb (already in .fastcode) */ * (.glue_7t) /* "Glue" Thumb to ARM (already in .fastcode) */ . = ALIGN(0x4); _etext = .; /* Global symbol at end of code */ } >ROM
The text section is fairly straightforward. The .text, .rodata and .glue_7 sections from any input file
(as specified by the asterisk wildcard character) are placed in the .text output section. As mentioned
before, .text input sections are used for code. The remaining input sections (.rodata and .glue_7) are
for read-only data and interfacing ARM/Thumb code respectively.
You might notice that there are some double-ups: We’ve already placed the startup file’s .text
section in the .reset output section – now it will be getting placed again here. The .glue_7 input
sections were also already placed in the .fastcode output section...
What happens is that the first placement is the one that takes priority. Any repeated input sections
are ignored and are not included more than once.
You might also be unsure about how to know if you’ve included all of the input sections. How do you
know that there isn’t an input section that’s been missed? These are called “Orphan Sections”. You
don’t need to worry too much about these, because the linker will decide how to place them
automatically.
***
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
.data : { __data_load = LOADADDR(.data); __data_start = .; * (.data) /* .data sections (initialised variables) */ * (.data*) . = ALIGN(0x4); _edata = .; } >RAM AT>ROM .bss : { __bss_start__ = .; * (.bss) /* .bss sections (uninitialised variables) */ * (.bss*) * (COMMON) . = ALIGN(0x4); _ebss = .; __bss_end__ = .; } >RAM PROVIDE(end = _ebss); PROVIDE(_end = _ebss); PROVIDE(__end__ = _ebss);
The above part of the script is just more of the same, anything you don’t recognise at this point (e.g.
COMMON or PROVIDE) can be looked up in the linker documentation’s index if you’re interested.
***
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
.stack : { __stack_start__ = .; . += IRQ_STACK_SIZE; . = ALIGN(0x4); __irq_stack_top__ = .; . += FIQ_STACK_SIZE; . = ALIGN(0x4); __fiq_stack_top__ = .; . += SVC_STACK_SIZE; . = ALIGN(0x4); __svc_stack_top__ = .; . += ABT_STACK_SIZE; . = ALIGN(0x4); __abt_stack_top__ = .; . += UND_STACK_SIZE; . = ALIGN(0x4); __und_stack_top__ = .;
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
. += C_STACK_SIZE; . = ALIGN(0x4); __c_stack_top__ = .; __stack_end__ = .; } >RAM /* Remove information from the standard libraries. */ /DISCARD/ : { libc.a (*) libm.a (*) libgcc.a (*) } }
To conclude the linker script, symbols are defined so that the startup assembly code knows where
the stacks are. Finally, input sections from the standard libraries are discarded to save on space.
Remember, if there are any repeats then the first placement takes priority – so the discarding won’t
remove any sections of the standard libraries that are actually being used.
Creating the “run_openocd.bat” Batch Fi le
This batch file is nothing too complicated once you get past the syntax. It is only a part of the project
to make running OpenOCD more convenient. If you run the batch file it will open an instance of
OpenOCD in a new Command Prompt window, if there isn’t already an instance running.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
@echo off REM File: run_openocd.bat REM Created on: 14/12/12 REM Author: Adam Goodwin REM REM Description: This batch file runs OpenOCD if it isn't already running. REM REM You must specify the OpenOCD executable, the path to the executable, and the REM configuration file to be used. REM Set the OpenOCD executable name here: set OCD=openocd-x64-0.6.1.exe REM Set the absolute path to the OpenOCD executable here: set OCDP=C:\Program Files\openocd-0.6.1\bin-x64\ REM Set the configuration file here: set OCD_CFG=uc-sam7-usbjtag.cfg REM Check if OpenOCD is running, storing the count of how many open instances of REM OpenOCD there are. FOR /F %%A IN ('tasklist /FI "IMAGENAME eq %OCD%" /NH ^| find /C "%OCD%"') DO ( set COUNT=%%A ) REM Run OpenOCD if necessary: if %COUNT% EQU 0 ( echo OpenOCD is not running. Starting OpenOCD... start "OpenOCD" /D . /MIN "%OCDP%%OCD%" "-f" "%OCD_CFG%" ) ELSE ( echo There is already %COUNT% instance^(s^) of OpenOCD running. )
The only changes you might need to make are to lines 12 and 15, where the OpenOCD executable
name and installation path are specified. Line 18 contains the configuration file OpenOCD is to use,
which you’ll only need to change if you’re not using an ECE USB-JTAG adapter (this configuration file
is discussed in the next section).
The remainder of the batch file first uses the “tasklist” and “find” programs to calculate the number
of running OpenOCD instances. Finally, the batch file uses the “start” command to run OpenOCD
(minimized) in a new window. The configuration file from earlier is specified as an argument to
OpenOCD.
If you have a Command Prompt open in your project directory, rather than typing something like
start “OpenOCD” /D . “C:\Program Files\openocd-0.6.1\bin-x64\openocd-x64-
0.6.1.exe” –f uc-sam7-usbjtag.cfg to run OpenOCD, you can instead just type
run_openocd.
Creating the “uc-sam7-usbjtag.cfg” OpenOCD Configuration Fi le
Here we write the OpenOCD configuration file, which was specified in the run_openocd.bat batch
file just previously. OpenOCD uses the configuration file to correctly communicate with the JTAG
adapter being used, as well as to ensure correct debugging of the target CPU.
The OpenOCD User’s Guide is the place to go for help on OpenOCD specifics. If you’ve downloaded
OpenOCD as per the instructions at the beginning of this guide you should find that there’s a copy
included in the zipped folder you downloaded.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
# File: uc-sam7-usbjtag.cfg # Created on: 28/11/2012 # Author: Adam Goodwin # # Description: OpenOCD configuration for the University of Canterbury ECE # USB-JTAG interface with a SAM7S target. # # Note that the adapter speed is set to 3kHz, which is quite slow. This is # done because the configuration file is making no assumptions about the clock # speed of the MCU. Upon reset, the MCU's clock is set to the Slow Clock, which # is 32.768kHz. According to the OpenOCD documentation, the maximum adapter # clock speed is one sixth of the MCU's clock speed for a SAM7S. Therefore 3kHz # has been chosen as it is safely under this limit. If the MCU's clock speed is # increased during its initialisation code, or via OpenOCD, then after this time # the adapter speed can also be increased to match the new limit. # # For a better understanding, imagine that the MCU has been reset and halted # before its initialisation code has had the chance to bring the Master Clock up # to a high speed. Or perhaps the program on the chip simply doesn't change the # source of the Master Clock to something other than the Slow Clock. In this # situation, if OpenOCD is configured to an adapter speed greater than one-sixth # of the Slow Clock, control via JTAG won't work. Therefore the worst-case # adapter speed of one-sixth of the Slow Clock is used. Again, it can be # increased once it becomes possible. # # For the same reason, the adapter speed must be set to 3kHz before using the # command "reset init". This command attempts to interact with the MCU while it # is running on the Slow Clock. # # See ecewiki.elec.canterbury.ac.nz/mediawiki/index.php/OpenOCD_configuration # and the other pages of the wiki for more information.
Remember to name the file appropriately for its use (and to match the filename to that expected by
the run_openocd.bat batch file).
The initial comment in the configuration script explains some of the details about clock speeds. The
gist is that the JTAG clock can be at most one-sixth of the CPU clock at any time. Because the MCU
runs on the Slow Clock (32.768kHz) upon reset, the OpenOCD configuration has to assume this
worst-case speed when it connects to the MCU. Once the processor is known to be at a higher
speed, OpenOCD can increase the JTAG clock frequency accordingly.
***
34 35 36 37 38
# This section configures OpenOCD for using the university's USB-JTAG adapter. interface ft2232 ft2232_layout usbjtag ft2232_vid_pid 0x0403 0x6010
Here the configuration specific to the ECE Department’s USB-JTAG adapter is performed. Really, the
only lines you should need to change for a different adapter are lines 35, 36 and 37.
For those using an ECE USB-JTAG adapter, the above lines tell OpenOCD that the interface is based
on an FT2232 chip. Vendor and product ID values are also specified.
Depending on the adapter design, the FT2232’s GPIO pins can be used for different purposes. The
JTAG signals are always the same, but other IO can vary. Line 36 specifies that “usbjtag” is the GPIO
layout of the ECE adapter’s FT2232D chip. As explained by the OpenOCD User’s Guide, the “usbjtag”
layout is that of the “USBJTAG-1” adapter described in the OpenOCD diploma thesis.
In the thesis you can see from a schematic of USBJTAG-1 that the FT2232’s GPIOL0 line is used for
the Test Reset signal, and that the GPIOL2 line is used for the Reset signal (which would connect to
the NRST pin of a SAM7S MCU). In the schematic for the ECE USB-JTAG adapter you can see a similar
setup, except GPIOL1 is used for the Reset signal.
The point of explaining this is that it means you won’t be able to perform resets from OpenOCD
which require the use of the NRST pin of the MCU (for example the OpenOCD “reset run”
command). You can still perform software resets though, and so it is for this reason that this guide is
designed to make use of software resets only. If you want to enable the rest of the reset
functionality you will need to find a layout choice which is more compatible with the ECE adapter.
You can get a list of the layout options from the OpenOCD User’s Guide but, to work out the actual
FT2232 pin configurations that they represent, you will need to delve into the OpenOCD source code
– particularly the ft2232.c file. (Note that the link takes you to a layout which appears to be the same
as “usbjtag” but with GPIOL1 assigned to the Reset signal. This is the “signalyzer” layout, and should
therefore be a suitable choice. I have not thoroughly tested and confirmed this though.)
If you’re not using the ECE USB-JTAG adapter, then the chances are that you are using an off-the-
shelf commercial programmer. Look around your OpenOCD directory: In the “scripts\interface”
folder, you will hopefully find a configuration file that matches your USB-JTAG adapter.
Say for example you have an Olimex “ARM-USB-TINY” adapter. There is a configuration file in the
interfaces folder for this adapter, called olimex-jtag-tiny.cfg. For this adapter, you would remove
lines 35, 36 and 37, and would replace them with the following line:
source [find interface/olimex-jtag-tiny.cfg]
This should be enough to adapt the configuration file to your setup. I can’t guarantee this though,
and so if this doesn’t work there really is no option but to read through the OpenOCD User’s Guide
to get a better understanding of OpenOCD, and then try to figure out the problem from there.
***
39 40 41 42 43 44 45
# The following are signal-related settings. adapter_khz 3 adapter_nsrst_delay 200 jtag_ntrst_delay 200 adapter_nsrst_assert_width 100 jtag_ntrst_assert_width 100
Line 40 sets the JTAG clock to 3kHz. As explained at the beginning of this section, the setting has a
theoretical maximum of one-sixth of the CPU clock. Because the CPU runs on the Slow Clock
(32.768kHz) after a reset, we can only be sure that the CPU is running at least this slowly when we
connect OpenOCD. For this reason, 3kHz is chosen as the adapter speed as it is safely under one-
sixth of 32.768kHz.
The remaining lines of the above configuration concern the timing of reset signals in milliseconds.
***
46 47 48 49 50 51 52
# This section configures OpenOCD for working with a SAM7 chip. source [find target/at91sam7sx.cfg] # OpenOCD recommends these settings to improve performance with FT2232 adapters. arm7_9 dcc_downloads enable arm7_9 fast_memory_access enable
The configuration file for SAM7S MCUs, included with OpenOCD, is run here. Performance improving
settings are also enabled (OpenOCD issues a warning if you do not enable these settings).
If you are using the ECE JTAG adapter and have chosen to use a different FT2232 layout, in order to
enable system resetting through OpenOCD, you might also have to add the following line at some
point after line 46:
reset_config separate
This is to override a reset_config setting used in the at91sam7sx.cfg configuration script. For some
reason that script assumes that the test logic is reset with any system resets (due to the use of
“srst_pulls_trst”). This means that when you instruct OpenOCD to perform a “reset halt”, OpenOCD
thinks it is not possible and so doesn’t actually do it. If you override the setting using the line above,
OpenOCD appears to perform a reset and halt correctly and without complaint.
***
53 54 55 56
# Halt the MCU when GDB connects otherwise the connection fails. # $_TARGETNAME comes from at91sam7sx.cfg. $_TARGETNAME configure -event gdb-attach halt
Lastly for this file an event is configured, which results in OpenOCD halting the SAM7S MCU
whenever the GNU Debugger attempts to connect to OpenOCD. This is required if you find that,
without configuring the event, you are unable to connect GDB to OpenOCD for debugging (how to
use GDB for debugging is introduced at the end of this guide).
When attempting to run OpenOCD, an important thing to remember is to have the USB-JTAG
adapter connecting the MCU to your PC – make sure the MCU is powered as well. Otherwise you’ll
find that OpenOCD instantly closes down because it can’t find the target.
If you follow this guide and OpenOCD is running properly in every other respect, you might still find
that it gives a warning like “use ‘at91sam7s.cpu’ as target identifier, not ‘0’”. As far as I can tell, this
is a result of the at91sam7sx.cfg script being out of date or incorrect. You can get rid of the warning
by replacing the following line from at91sam7sx.cfg:
flash bank $_FLASHNAME at91sam7 0 0 0 0 0 0 0 0 0 0 0 0 18432
With:
flash bank $_FLASHNAME at91sam7 0 0 0 0 $_TARGETNAME 0 0 0 0 0 0 0 18432
This isn’t necessary, it’s just in case you see the warning and think there’s something wrong with
your configuration. If OpenOCD doesn’t give any other warnings or errors, you should be good to go.
Creating the Makefi le
The final file needed to complete the project is the makefile (the filename is just “makefile” with no
extension).
The reason this has been left until last is because it ties everything together – the makefile has been
designed so that it’s the only thing you’ll need to use Command Prompt for. You will be able to not
only compile and build the project with the makefile, but you’ll also be able to run OpenOCD,
program the MCU, and initiate debugging – all with make commands in Command Prompt.
If you are unfamiliar with makefiles and what they do, the GNU Make documentation should bring
you up to speed. Even if you are familiar with Make, you might not have heard of Make’s functions
before – so when you come across these remember that the documentation is where to look for
info.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# File: Makefile # Created on: 12/12/2012 # Author: Adam Goodwin # # Description: This makefile is for compiling, linking and loading the # led_test_sam7s256 program for the SAM7S256 development board. By default, a # debug build is created. To specify a release build (containing less debugging # information) use the command "make BUILD=RELEASE". # # To adapt this makefile for other SAM7S projects, simply specify the # following: # - The tools to be used # - The project directory (the same folder containing the makefile) # - The directories for different files (relative to the project directory) # - The application name # - The individual source and header files of the project # The makefile comments show where to specify each of these items. # # Note: This makefile is based on the one used in the series of articles # "Building Bare-Metal ARM Systems with GNU" by Miro Samek. You can find the # articles at http://www.state-machine.com/resources/articles.php#ARM # The tools to be used (compiler, assembler, etc): CC = arm-none-eabi-gcc AS = arm-none-eabi-as LD = arm-none-eabi-gcc OBJCPY = arm-none-eabi-objcopy RM = rm -rf MKDIR = mkdir
The tools on lines 24 through to 29 are the names of the executables from the YAGARTO Toolchain,
and YAGARTO Tools package, which are going to be called upon later in the makefile.
In order, they are the GNU Compiler Collection, the GNU Assembler, the GNU Linker (which is
actually called via the compiler collection), the GNU “objcopy” utility, the “remove” tool, and the
“make directory” tool.
***
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
# Set the project directory (relative to this makefile) here: PRJ_DIR = . # Set the directories, relative to the project directory, which contain each of # the following types of file: # Output file directory for release build: RLS_DIR = $(PRJ_DIR)/release # Output file directory for debug build: DBG_DIR = $(PRJ_DIR)/debug # Header files location: HDR_DIR = $(PRJ_DIR) # Assembly source files location: ASM_DIR = $(PRJ_DIR) # C source files location (for source to be compiled to ARM instructions): ARM_DIR = $(PRJ_DIR) # C source files location (for source to be compiled to Thumb instructions): TMB_DIR = $(PRJ_DIR)
Here the directories of the project are specified so that Make knows where to find your source files
and scripts. The project directory (PRJ_DIR) is just set to “.”, the current directory, which will always
give the directory where the makefile is located – as long as you run Make from the project
directory.
(So to ensure that running the makefile works: Make sure you put the makefile in the project folder,
along with all of the other files created so far, and only run Make from Command Prompt after you
have navigated to the project folder using the “cd” command.)
The rest of the directories here are located relative to the project directory. The release and debug
folders (RLS_DIR and DBG_DIR) don’t exist yet, but the makefile will create them later for holding
release and debug output respectively. These are the only output directories; the rest of the
directories should already exist and contain the expected files.
The remaining directories (HDR_DIR, ASM_DIR, ARM_DIR, and TMB_DIR) are set equal to the project
directory. You can optionally organise your source files into different folders, based on whether the
source files are header files, assembly files, C files to be compiled to ARM code, or C files to be
compiled to Thumb code. This organisation won’t cause them to be appropriately compiled or
assembled. You will still need to specify each of the source files, and what type they are, later in the
makefile.
***
50 51 52 53 54 55 56
# Specify the program name, this will be used as the base name for output files: APP_NAME = led_test_sam7s256 # Specify the linker script here, it will be assumed to be in the project # directory: LNK_SCRPT = led_test_sam7s256.ld
Here you specify the program’s name. It makes sense for it to share the name of the project, but it
can be anything. The output ELF and MAP files will have this name along with the appropriate
extensions.
Also specify here the linker script. It’s assumed by the rest of the makefile that the linker script is in
the project directory (PRJ_DIR).
***
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
# List here all of the project header files: APP_DEP = \ AT91SAM7S256.h \ dev_board.h \ delay.h # List here all of the project assembly source files: ASM_SRC = \ sam7s_startup.s # List here all of the project C files to be compiled to ARM code: ARM_SRC = \ sam7s256_init.c \ main.c \ delay.c # List here all of the project C files to be compiled to Thumb code: TMB_SRC =
This is where you list all of the project source files. Be sure that they are all where you said they
would be, based on the directories you entered before (lines 41 to 48).
This concludes the part of the makefile which you might have needed to change. Everything else
from this point forward can be left alone.
***
76 77 78 79 80 81 82 83 84 85 86
################################################################################ # This marks the end of the project-specific settings. For SAM7S projects, the # # rest of this makefile can remain untouched. # ################################################################################ # The CPU the software is being written for: ARM_CORE = arm7tdmi # Copy the list of header files but include the path this time. APP_DEP_WPATH = $(addprefix $(HDR_DIR)/, $(APP_DEP))
Line 82 assigns the processor of the SAM7S MCUs to a variable for later use. In line 85, the specified
header files from earlier are stored in a new variable. This time the full path to each file is included,
by using the “addprefix” function.
***
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
# If make is run like so: "make BUILD=RELEASE", then a release build of the # program will be made without debugging information. A debug build is the # default. ifeq (RELEASE, $(BUILD)) # Release configuration BIN_DIR = $(RLS_DIR) CCFLAGS = -c -mcpu=$(ARM_CORE) -mthumb-interwork -Os -ffunction-sections \ -Wall -I $(HDR_DIR) -D NDEBUG -o $@ ASFLAGS = -mcpu=$(ARM_CORE) -mthumb-interwork -o $@ LDFLAGS = -T $(PRJ_DIR)/$(LNK_SCRPT) -o $(BIN_DIR)/$(APP_NAME).elf \ -nostartfiles -Wl,-Map,$(BIN_DIR)/$(APP_NAME).map,--cref,--gc-sections else # Debug configuration BUILD = DEBUG BIN_DIR = $(DBG_DIR) CCFLAGS = -g -c -mcpu=$(ARM_CORE) -mthumb-interwork –O0 -ffunction-sections \ -Wall -I $(HDR_DIR) -o $@ ASFLAGS = -g -mcpu=$(ARM_CORE) -mthumb-interwork -o $@ LDFLAGS = -T $(PRJ_DIR)/$(LNK_SCRPT) -o $(BIN_DIR)/$(APP_NAME).elf \ -nostartfiles -Wl,-Map,$(BIN_DIR)/$(APP_NAME).map,--cref,--gc-sections endif
When you run Make, you can specify the build type – release or debug. The release build results in
less debugging information and a more optimised output executable, and puts all output in a
different folder to the debug build. This is accomplished by setting different compiler, assembler and
linker flags based on the build type.
The debug build is the default. You can tell Make to do a release build instead by running Make as
usual from the Command Prompt, but with the addition of “BUILD=RELEASE” to the command line
arguments.
In the following part of the guide I’ll give a quick rundown of what each of the arguments in the
compiler, assembler, and linker flags do. I’ll mix the debug and release builds in this explanation, but
you can look at the code above to see where and how each argument is used.
Note also that because the linker is being called via the compiler, some of the linker flags are actually
GCC options and not GNU Linker options. For all of the compiler flags, and these particular linker
flags, you can use the GNU Compiler Collection documentation to find out more. Use the GNU
Assembler and GNU Linker documentation from earlier in this guide to find out more about their
respective options.
CCFLAGS (Compiler Options): “ -g”
This option results in the compiler producing debugging information that can be used by GDB.
CCFLAGS (Compiler Options) : “ -c”
This option specifies that no linking should occur. At this stage, we only want to compile our source
files.
CCFLAGS (Compiler Options): “ -mcpu=$(ARM_CORE)”
Here the contents of the ARM_CORE variable are used to tell GCC what processor is being targeted.
This is so that the output object files contain instructions appropriate for an ARM7TDMI.
CCFLAGS (Compiler Options): “ -mthumb- interwork”
This ensures that ARM and Thumb code will work together if their use is mixed in your program.
CCFLAGS (Compiler Options): “ -O”
This sets optimisation levels. See the GCC documentation for the different options available. Greater
optimisation can mean strange results when debugging.
CCFLAGS (Compiler Options): “ -f function-sections”
This puts functions into their own sections in the output file. If you recall, this is so that you can
place functions in the .fastcode section in the linker script.
CCFLAGS (Compiler Options): “ -Wall”
This enables several warnings.
CCFLAGS (Compiler Options): “ - I $(HDR_DIR)”
By using this option the compiler will look in the header file directory for your header files.
CCFLAGS (Compiler Options): “ -D NDEBUG”
This defines NDEBUG. If you include in your C source files the “assert.h” header file, you can perform
assertions for debugging. There may be some more complex setup required to do this for an
embedded project, but at the very least you can enclose any debug code in “ifndef NDEBUG” and
“endif” directives. When you make a release build, NDEBUG will be defined, and so the debug code
won’t be compiled.
CCFLAGS (Compiler Options): “ -o $@”
The “-o” option sets the output file name. Here the Make “Automatic Variable” of “$@” is specified.
In the “recipe” of a “rule”, this will evaluate to the rule target’s file name (more on this soon).
ASFLAGS (Assembler Options)
All of these are similar to some of the compiler options, so refer to those for more on the meaning of
these assembler options.
LDFLAGS (Compiler and Linker Opt ions): “ -T $(PRJ_DIR)/$(LNK_SCRPT)”
The “-T” option sets the linker script to be used by the compiler (which will be passed to the linker).
It is set to the linker script specified earlier in the makefile.
LDFLAGS (Compiler and Linker Opt ions): “ -o $(BIN_DIR)/$(APP_NAME).elf ”
The name and location of the output file is set, the same as with the compiler and assembler. The
ELF extension is given to the output file as this is the format specified in the linker script.
LDFLAGS (Compiler and Linker Opt ions): “ -nostartf i les”
This tells GCC to tell the linker not to use the standard system startup files. This is because we are
making an embedded application with its own startup assembly routine.
LDFLAGS (Compiler and Linker Opt ions): “ -Wl,-Map,$(BIN_DIR)/$(APP_NAME).map, --cref, - -gc-sections”
The first part of this long option is “-Wl”. This is an option for GCC which tells GCC to pass the rest of
the option on to the linker. Where there are commas, the linker receives separate options delimited
with spaces.
So first the linker gets the option “-Map $(BIN_DIR)/$(APP_NAME).map”. Except, by this time, the
Make variables will already have been substituted out for their values. So with this example project,
the linker would actually receive “-Map ./debug/led_test_sam7s256.map”. The “-Map” option
creates a link map file to help show the layout of the executable.
The “--cref" option adds some additional information to the map file.
Finally, the “--gc-sections” option enables garbage collection of unused input sections.
***
107 108 109 110 111 112 113 114 115 116 117
# Automatically list all of the object code files to be created. ASM_OBJS = $(ASM_SRC:.s=.o) ARM_OBJS = $(ARM_SRC:.c=.o) TMB_OBJS = $(TMB_SRC:.c=.o) OBJS = $(ASM_OBJS) $(ARM_OBJS) $(TMB_OBJS) # Take the same lists, but include the path to the files. ASM_OBJS_WPATH = $(addprefix $(BIN_DIR)/, $(ASM_OBJS)) ARM_OBJS_WPATH = $(addprefix $(BIN_DIR)/, $(ARM_OBJS)) TMB_OBJS_WPATH = $(addprefix $(BIN_DIR)/, $(TMB_OBJS)) OBJS_WPATH = $(addprefix $(BIN_DIR)/, $(OBJS))
The source code variables have their extensions swapped using Make’s “patsubst” function (the
shorthand version), then these new filenames are stored in new variables. This is to form a list of the
object files which are to be made (line 111).
Then, similarly to with the header files, the object files are copied to new variables with the entire
path to the files included (using the addprefix function).
***
118 119 120 121 122 123 124 125 126 127 128
################################################################################ # What follows are the make targets and their rules. # ################################################################################ # Target "all". Rebuilds out of date files. Use the command "make all". .PHONY: all all: $(BIN_DIR)/$(APP_NAME).elf $(BIN_DIR)/$(APP_NAME).elf: $(PRJ_DIR)/$(LNK_SCRPT) $(OBJS_WPATH) @echo Linking... $(LD) $(OBJS_WPATH) $(LDFLAGS)
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
@echo Done. @echo Finished. Output is $(BIN_DIR)/$(APP_NAME).elf. $(ASM_OBJS_WPATH): $(BIN_DIR)/%.o: $(ASM_DIR)/%.s @$(MKDIR) -p $(BIN_DIR) @echo Assembling $<... $(AS) $(ASFLAGS) $< @echo Done. $(ARM_OBJS_WPATH): $(BIN_DIR)/%.o: $(ARM_DIR)/%.c $(APP_DEP_WPATH) @echo Compiling to ARM $<... $(CC) -marm $(CCFLAGS) $< @echo Done. $(TMB_OBJS_WPATH): $(BIN_DIR)/%.o: $(TMB_DIR)/%.c $(APP_DEP_WPATH) @echo Compiling to Thumb $<... $(CC) -mthumb $(CCFLAGS) $< @echo Done.
Here are the makefile “rules”. Each block in the above section of the makefile is a rule. Line 124 is a
rule, lines 126 to 130 are a rule, lines 132 to 136 are a rule, etc. Before going any further, we will
briefly look at the structure of a makefile’s rules.
Makefi le Rule Components: “Targets”
For each rule there are one or more targets. These are everything before the first colon in a rule. So
the first rule in this makefile is for the “all” target. The second rule has the target
“$(BIN_DIR)/$(APP_NAME).elf” or “./debug/led_test_sam7s256.elf”.
The first target is used as the default “goal” of Make. The goal is the target that Make is to update,
by using the rule for that target. To reiterate: When you run Make, Make’s purpose is to update the
specified target – or the default target if none is specified. Except for “phony” targets, a target
should be a filename. So the processing of a rule (which has a non-phony target) should result in the
target file being updated.
You can specify different goals for Make by adding arguments to the command line. For example,
you could type “make all” in the command line to explicitly run Make with the “all” target as the goal
(this will give the same result as just typing “make”, because “all” is the first and default target
anyway).
Makefi le Rule Components: “Prerequisites”
Another component of a rule is the prerequisites. What follow the target (after the colon and on the
same line) are the prerequisites. So for the “all” target, the only prerequisite is
“$(BIN_DIR)/$(APP_NAME).elf”, which will become “./debug/led_test_sam7s256.elf”.
Before Make can process a rule, it must first process the prerequisites’ rules (if they have rules). So
because the “all” target’s prerequisite is “./debug/led_test_sam7s256.elf”, to complete the updating
of the “all” target Make must first process the rule for the “./debug/led_test_sam7s256.elf” target.
You can see that the rule for the “./debug/led_test_sam7s256.elf” target begins on line 126.
Makefi le Rule Components: “Recipes”
Recipes are below the first line of a rule. When all of the prerequisites of a target have been dealt
with, all that remains to finish updating the target file (and thus complete the rule) is to follow the
recipe. The recipe only needs to be followed if the target file does not exist, or if one of the
prerequisite files is more recent than the target file. Otherwise you’d just be creating the exact same
target file as the old version.
Each line in a recipe is a command to be sent to the command line, and must start with a tab
character so that Make knows the line is still part of the recipe.
***
So if we now look again at the section of the makefile from earlier, you can see that there are three
rules for creating the different object files needed to produce the “./debug/led_test_sam7s256.elf”
target (which is itself the prerequisite of the “all” target).
The rules for the object code targets are actually a little bit more complicated than the ordinary rules
I’ve described above because they are “static pattern rules”. Essentially this means that they are
generalised rules which handle many similar targets, by adjusting the prerequisites based on a
pattern.
There is a separate static pattern rule for making object files from assembly code, ARM C code, and
Thumb C code. So Make will consider there to be separate rules for each of the targets in the
variables “$(ASM_OBJS_WPATH)”, “$(ARM_OBJS_WPATH)”, and “$(TMB_OBJS_WPATH)”.
The Make manual, linked earlier, covers static pattern rules in much higher detail if you want to
learn more.
***
Notice the use of the tool and flag variables from earlier – e.g. “$(AS)” and “$(ASFLAGS)”. These are
expanded to their full values by Make, before being sent to the command line where they are
treated as commands that can be run.
Also under the assembly rule, in its recipe, you’ll notice that “$(MKDIR)” is used to create the debug
or release output directory – if it doesn’t already exist. This is performed here, as the directory needs
to exist before Make attempts to create any object output files – which are to be located in the
output directory.
It happens that, because of how the “$(OBJS_WPATH)” variable is defined, the assembly object files
are the first to be attempted (“$(OBJS_WPATH)” is a prerequisite of the output ELF file). The “@”
character is used to suppress echoing for the mkdir line, and many other lines, in the makefile’s
recipes. This is to stop any output from mkdir being printed.
If you are wondering what the “$<” automatic variable does, it expands to the name of the first
prerequisite – which will be the source file having the same name as the output object file for these
rules.
***
148 149 150 151 152 153 154 155 156 157 158 159 160 161
# Target "clean". Deletes generated files. Use the command "make clean". .PHONY: clean clean: @echo Deleting files... \(*.o, *.elf, *.map\) -@$(RM) $(BIN_DIR)/*.o \ $(BIN_DIR)/*.elf \ $(BIN_DIR)/*.map @echo Done. # Target "rebuild". Rebuilds files, even if not out of date. Use the command # "make rebuild". .PHONY: rebuild rebuild: clean all
The phony target “clean”, because it doesn’t correspond to a filename, will always have its recipe
run when requested using “make clean”. The “clean” target’s recipe removes any of output files
created by the makefile.
The “rebuild” target is just the combination of the “clean” and “all” targets. The “rebuild” target has
no recipe, it simply performs its purpose by having prerequisites.
***
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
# Target "program". Programs the device. Use the command "make program". Note # that OpenOCD will be started if it is not already running. .PHONY: program program: all openocd @arm-none-eabi-gdb --batch \ -ex "target remote localhost:3333" \ -ex "monitor adapter_khz 3" \ -ex "monitor reset init" \ -ex "monitor adapter_khz 3000" \ -ex "load" \ -ex "monitor adapter_khz 3" \ -ex "monitor reset init" \ -ex "monitor resume" \ $(BIN_DIR)/$(APP_NAME).elf
The “program” target is for programming the MCU by simply typing “make program” from the
command line. The first prerequisite is “all”, to ensure that the ELF executable is up-to-date, and the
second prerequisite is “openocd”, which ensures that OpenOCD will be running to allow
programming (the “openocd” target is explained further through the guide).
The recipe for “program” looks quite large, although it is actually only one line broken up into
several. The recipe runs the GNU Debugger in batch mode, so that GDB will exit upon completion.
The majority of the rest of the arguments to GDB are “-ex” options, which execute individual GDB
commands (these could have been written into a script, but I decided there have been enough
scripts already and so I incorporated them into the makefile).
You can find information on GDB commands in the GDB documentation here.
The GDB commands start on line 167 by connecting to the OpenOCD server through port 3333. Next,
the GDB “monitor” command is used to pass a command to OpenOCD. The adapter speed is set to
the low value of 3kHz, and the “reset init” command is issued to OpenOCD (the implementation of
this command can be seen in the “at91sam7sx.cfg” script of the OpenOCD directory). For our
purposes, the most important feature of this command is that it brings the MCU’s clocks up to speed
(note that it assumes an external oscillator of 18.432MHz).
After this command is issued, the adapter speed is brought up to 3MHz on line 170. Following this,
GDB’s “load” command is issued to load the specified executable to the MCU. The MCU is then
reset, and allowed to run. The final argument to GDB is to specify “./debug/led_test_sam7s256.elf”
or “./release/led_test_sam7s256.elf” as the executable to use for debugging symbols. This is also the
executable that GDB’s “load” command will load.
***
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
# Target "debug". Runs the GDB debugger. Use the command "make debug". This # assumes that you don't want to debug startup code, and stops the program at # the main function. Note that OpenOCD will be started if it is not already # running. .PHONY: debug debug: openocd @arm-none-eabi-gdb \ -ex "target remote localhost:3333" \ -ex "monitor adapter_khz 3" \ -ex "monitor reset init" \ -ex "monitor adapter_khz 3000" \ -ex "break main" \ -ex "continue" \ $(BIN_DIR)/$(APP_NAME).elf
Similar to the “program” target, the “debug” target lets you start debugging just by issuing the
command “make debug”.
This time the only prerequisite is the “openocd” target. It is assumed that you have already
programmed the MCU with the most recently built executable.
Aside from not running GDB in batch mode (to allow you to continue issuing debug commands after
the initialisation is finished), the initialisation commands are much the same as they were for the
“program” target.
The differences begin on line 188, where a breakpoint is added at the main function. Keep in mind
that only 2 hardware breakpoints are available when you are debugging. After the breakpoint is
added, the MCU is allowed to continue until the breakpoint is reached, where it will halt and await
further debugging instructions.
***
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
# Target "debug_startup". Runs the GDB debugger. Use the command # "make debug_startup". This target is similar to "debug", except here the first # breakpoint is at the beginning of the _reset function in sam7s_startup.s. This # allows you to debug startup code. Remember to use "monitor adapter_khz 3000" # from within GDB once the initialisation code has brought the clocks up to # speed. Note that OpenOCD will be started if it is not already running. .PHONY: debug_startup debug_startup: openocd @arm-none-eabi-gdb \ -ex "target remote localhost:3333" \ -ex "monitor adapter_khz 3" \ -ex "monitor soft_reset_halt" \ -ex "monitor mww 0xFFFFFD00 0xA5000004" \ -ex "break _reset" \ -ex "continue" \ $(BIN_DIR)/$(APP_NAME).elf
Again similar to a previous target, the “debug_startup” target is much like the “debug” target. The
changes begin on line 203. Instead of a “monitor reset init” command, which would bring the clocks
up to speed, the “monitor soft_reset_halt” command is used (which the “reset init” command
actually uses itself, before moving onto clock configuration). The “soft_reset_halt” OpenOCD
command resets the CPU, then moves the program counter back to the reset exception vector to
allow you to debug from the beginning of the program.
The purpose of using the “soft_reset_halt” option is that, in conjunction with a peripheral reset, the
MCU can be put into a state which is essentially the same as when it has only just been powered on.
This allows us to check that the startup code is doing the configuration correctly, without the
possibility of anything working being due to configuration performed by the “reset init” command.
Note: To get the MCU to an initial state, you could also use the OpenOCD command “reset halt”.
This (along with the “reset” and “reset run” commands) attempts to use the NRST pin, which is not
always available (either because it is not enabled, or because the USB-JTAG adapter does not
support it). For this reason I have stuck to using software-based resets in this guide.
On line 204 the Reset Control Register, at address 0xFFFF FD00, is being loaded with the value
0xA500 0004. This is the setting of the PERRST bit of the register to one, with the password of 0xA5.
This performs a peripheral reset, thereby ensuring that the remap region is mapped to flash and the
clock settings are at the default. The effects of any startup code that managed to run before the
“soft_reset_halt” command took place will also be undone.
Finally a breakpoint is placed at the _reset function and the program is allowed to continue until this
point. Note that once you have stepped through the startup code that configures the clocks, you will
be able to increase the JTAG clock above 3kHz.
***
209 210 211 212 213 214 215
# Target "openocd". Runs OpenOCD with the specified configuration file. Use the # command "make openocd". Ensure the USB-JTAG interface is connected between the # board and computer. Note: OpenOCD is only started if it is not already # running. .PHONY: openocd openocd: @echo Running OpenOCD if it is not already running...
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
-@cmd /C run_openocd @echo Done. OpenOCD is running. # Target "show". Shows the values of the makefile variables. Use the command # "make show". .PHONY: show show: @echo BUILD = $(BUILD) @echo APP_DEP_WPATH = $(APP_DEP_WPATH) @echo ASM_OBJS = $(ASM_OBJS) @echo ARM_OBJS = $(ARM_OBJS) @echo TMB_OBJS = $(TMB_OBJS) @echo ASM_OBJS_WPATH = $(ASM_OBJS_WPATH) @echo ARM_OBJS_WPATH = $(ARM_OBJS_WPATH) @echo TMB_OBJS_WPATH = $(TMB_OBJS_WPATH) @echo Output file = $(BIN_DIR)/$(APP_NAME).elf
The final two phony targets are “openocd” and “show”. The “openocd” target runs OpenOCD, if it is
not already running, by making use of the batch file we wrote. The “show” target is for displaying
the value of makefile variables.
To summarise the ways you can use the makefile, the commands you can enter in Command Prompt
that utilise the makefile are as follows:
Command Result
make Builds the debug build of the project.
make all Same as above.
make clean Deletes all of the debug build output files.
make rebuild Equivalent to “make clean” followed by “make all”.
make program Runs “all” then “openocd”. Following this the debug program is loaded to the MCU.
make debug Runs “openocd” and starts GDB for a debugging session of the debug executable.
make debug_startup Similar to “make debug”, but initially breaks at _reset instead of main.
make openocd Runs OpenOCD, if it isn’t running already, by using a batch file.
make show Shows the values of the debug build makefile variables.
BUILD=RELEASE By appending this to any of the other commands, you can use the release build instead of the debug build.
Congratulations! You’ve nearly finished the guide. All that’s left now is to see if the program works.
This is done in the next section.
Testing and Debugging
This section will be very (very) brief, and won’t go into much detail. I’ll give you a few instructions on
how to see if the program has worked, and from there you will now hopefully have enough
knowledge to continue on your own.
First, your project directory should look something like this:
All of the files should be the same or similar.
If you shift and right-click on an empty area of the folder, you should get the option to open a
command window. If not, run Command Prompt and use the “cd” command to navigate to your
project directory.
Now, make sure the USB-JTAG adapter is connected to your PC and to the microcontroller.
Checking the Hardware and OpenOCD
First let’s make sure that OpenOCD is working. Into your Command Prompt, type “run_openocd”
and press enter. You should get a message saying that OpenOCD is not running, and that it is now
being started. Check your taskbar for another Command Prompt window that should have opened.
It should look like this:
If you have this, then you should be extremely relieved – OpenOCD is working and you shouldn’t
need to do any more messing around.
If you have something close to this, but there are errors, do your best to try to figure out how to fix
the problem. The OpenOCD User’s Guide should be able to help significantly. All of the
documentation linked throughout this guide is linked again in the Web Links section for easy
reference.
If you don’t have an OpenOCD window at all, try running the command again and keep an eye on the
taskbar. You’ll probably notice that the OpenOCD window is popping up and closing again very
quickly. This means that OpenOCD is closing after a significant error, such as not being able to find
the MCU.
In your Command Prompt window, try running OpenOCD directly so that it doesn’t close down
straight away. This will be a command such as the following, depending on your installation version:
> “C:\Program Files\openocd-0.6.1\bin-x64\openocd-x64-0.6.1.exe” –f uc-
sam7-usbjtag.cfg
Remember to use the quotation marks (to avoid problems with the space in the “Program Files”
directory).
Now you should see something like this:
I only have two suggestions for this issue. First, open Device Manager as you did during installation
and check that there isn’t the little yellow triangle denoting a problem with the driver for “USB Serial
Converter A”.
Second, make sure that the USB-JTAG adapter is connected to both your PC and the MCU, and check
that the connections are correct.
The remainder of the guide assumes you’ve got OpenOCD working, and is concerned with
programming the MCU and debugging.
Programming the Board
Programming the board should be as simple as running the command “make program”. You could
run “make” first, to more easily see if everything builds correctly, as otherwise the output from
“make program” could be quite cluttered.
If you run “make” alone, then a successful build would look as follows:
Then, after running “make program”, (and if you still have OpenOCD running from before) the
output should look something like this:
You should also see the LEDs on your board flashing on and off every two seconds (with three quick
flashes initially). If not, you’ll have to use what information you have to work out what’s going
wrong.
If OpenOCD is connecting to the board correctly, then you can use Telnet to manually give OpenOCD
commands which could help you debug any problems you’re having. In your Command Prompt
window type “telnet localhost 4444” to do this. You might have to enable Telnet, as it is not enabled
by default on Windows anymore.
Under Windows 7, you can enable Telnet by going to Control Panel, then under the Category view
selecting “Programs”. From there select “Turn Windows features on or off”. Tick the box for Telnet
Client then click “OK”.
A couple of OpenOCD commands you might find useful after connecting to the OpenOCD server
through Telnet are the “mww” and “mdw” commands, for writing and displaying words to or from
memory. You might first have to issue the “halt” command to use these commands.
The command “flash info 0” can also be helpful. If some sectors of flash are write protected, this
could be the cause of your program not running as expected. Use the OpenOCD User’s Guide to find
other commands that can help.
In some circumstances running “make program” will fail at the point where the program is supposed
to be loaded (using GDB’s “load” command). I have had this happen when the MCU’s flash memory
is completely erased, for example. If it looks like everything should be working, but “make program”
fails, then try using Telnet to manually get OpenOCD to load the program. For example this
command through Telnet should work (you might have to give the full path to the ELF file):
> flash write_image led_test_sam7s256.elf
Remember, you can also email me with questions. As this is an early version of this guide, I’m sure
there will be things I’ve overlooked, and the guide can be updated with any improvements.
Debugging the Program
All that remains now is to briefly introduce debugging with GDB. Once again, the remainder of the
guide assumes you have completed the previous steps successfully, with the board programmed
correctly and the LED-flashing program running.
If you’ve unplugged the JTAG adapter or the board, reconnect them, and in your command line
enter the command “make debug”. If you’ve closed OpenOCD from before, it will reopen, and
eventually you should get something like the following:
The majority of the output is just the result of the commands for setting up OpenOCD through GDB
(from the makefile recipe). The important part is what should be at the bottom; the program should
have hit the breakpoint at the main function, and GDB should be waiting for further input from you.
From here you can debug the code. You can step through line by line, add and remove breakpoints,
and let the program continue freely. These options are demonstrated below:
Here the “step” command was used three times, to step further into the main function. After this,
“break delay” was used to add a breakpoint at the delay function, and the program was allowed to
continue (until it hit this breakpoint) by the use of the “continue” command. Finally “clear delay”
was used to remove the breakpoint, and the program was continued again indefinitely.
Debugging the Startup Code
To debug the startup code type “make debug_startup” from the command line. The principles are
the same as when the command “make debug” was used.
Here is what the last of the output should look like:
If you type the command “monitor flash info 0” you should see the following (it will take a few
seconds, because the JTAG speed is only 3kHz at the moment):
The reason for doing this is that you can see the Master Clock is currently 32kHz, the same as the
Slow Clock. So the initial commands to GDB, from the makefile, were successful in putting the MCU
into a pseudo-just-powered-up state.
Step through the program until after the clock configuration in the initialisation code, then use the
flash info command again. What you should see is that the clocks are now up to speed.
What this means is that you can now use “monitor adapter_khz 3000” to speed up the JTAG clock
for faster debugging.
You can also use the “info breakpoints” command to see what breakpoints are set; you should see
that there is still a breakpoint at the beginning of the startup code. This is no longer needed, and so
you can use the “delete” command to free up the breakpoint for later use. Finally, if you set a
breakpoint at main with “break main”, then use “continue”, you are now back to where you would
have been had you used the “debug” makefile target:
This is as far as I will go into debugging with GDB. I’ll leave you with one more useful command I
haven’t yet covered though. This is the print command, which you can use to see the value of a
variable. You can also use it to see the value of the program counter, by using “print $pc”, and you
can also use it to see the value of the stack pointer, by using “print $sp”. For other commands you
should look through the GDB documentation.
Debugging Using OpenOCD
This section briefly talks about some ways that you can use OpenOCD for debugging, if ever the need
arises. Perhaps you have to run some code from the remap region, or maybe there is some other
reason you cannot use GDB.
If you have completed the previous section of this guide successfully, you can probably skip this
section and only use GDB for debugging. However, if you need to use the alternative of debugging
with OpenOCD (like I did at some stages of writing this guide) you can read this section to get a head
start. This section of the guide is written in the context of having code that needs to run from the
remap region.
***
Under some circumstances you may have code which is run from the remap region. You will not be
able to use GDB for debugging this code. This is due to there being no debugging symbols defined in
the ELF file for the remap region (addresses 0x0000 0000 to 0x000F FFFF) because of how the linker
script is written. GDB will not be able to tell what is happening if you try to debug this code.
One solution is that you could change the linker script to define the ROM memory region as starting
at 0x0000 0000 instead of 0x0010 0000. Be aware that this would reverse the problem, and would
not allow you to debug any code run from outside of the remap region.
Rather than changing the linker script you can instead use OpenOCD, through Telnet, for a rough
form of debugging. You will have no debugging symbols to help you, and will essentially only be able
to use the program counter to guess where you are in your code, but this should be enough to see if
something seriously wrong is going on.
First you should take note that the program counter value displayed by OpenOCD is two bytes
behind in ARM mode – i.e. it doesn’t show the real-time program counter, but shows the value of
the program counter at the time of fetching the current instruction. In other words, the program
counter value displayed by OpenOCD is the address of the instruction that is ready to be executed.
If you intend to follow along with this section of the guide, you should have the LED flashing program
loaded to the board and running. You should then launch OpenOCD and connect to the server from
another Command Prompt window by using the command “telnet localhost 4444”.
Recall that you can use the “mdw” command to view the contents of memory at an address. This can
be used to see what instruction is about to be executed.
Looking at an example:
Here I used the “reset init” command to get the microcontroller back to an initial state. This
command begins with “soft_reset_halt”, which resets the CPU then halts the program as soon as
possible. It then moves the program counter back to the reset vector.
The rest of the “reset init” command sets up the MCU back to an initial state by resetting the
peripherals, and does a bit more configuration similar to our startup assembly code. The end result
is that you have the MCU halted and in a state almost as though it has only just been powered on,
except the Master Clock is running a lot faster than the Slow Clock.
You can use the command “adapter_khz 3000” at this time to speed up JTAG communication.
After the “reset init” and “adapter_khz 3000” commands I then used the “step” command once to
proceed one instruction beyond the reset vector. The program counter jumped to 0x0000 003C,
implying that the first line of the startup assembly code’s _reset function is located at this address.
That would be this line from sam7s_startup.s:
99 LDR r0, =_reset /* Pass the reset address as the 1st arg */
I then used the “mdw 0x03c” command to read the value of the current instruction, which turned
out to be 0xE59F 00B4.
Now, to be sure that this is the “LDR r0, =_reset” instruction that we expect, we might have to
decode it using the ARM Architecture Reference Manual. This would be tedious, so you can instead
use a tool from the University of Cambridge which will do the decoding for you.
Here is the result for 0xE59F 00B4 from the evaluator (make sure you select the appropriate
architecture and exception mode etc):
So the instruction is similar to what we expect; it’s a load register instruction. But is it loading the
address of the _reset function to the R0 register as we expect from line 99 of sam7s_startup.s?
You might recognise that this is the same sort of instruction which was used in the initialisation
code, which was there defined as LDR_PC_PC_0x18.
In this case the base register is once again the program counter, but this time the destination
register is R0 and the offset is 180 (0xB4).
So where is the instruction loading a value from? The program counter when the instruction was
fetched was 0x0000 003C. So the program counter plus the offset of 180 (plus the additional 8 due
to the pipeline) gives:
Location of load value in memory = 0x0000 003C + 0xB4 + 0x08 = 0x0000 00F8.
So the instruction is loading the value from 0x0000 00F8 into the register R0. Let’s see what the
word being loaded into R0 actually is:
Result
Sure enough it is 0x0010 003C, the address of the reset vector. So we have verified where we are in
the startup code (line 99) and we know that the instruction about to be executed is correct.
This is obviously quite a tedious process, but it has demonstrated how you can use OpenOCD for
some very low-level debugging. From here you could step through the program within OpenOCD and
keep track yourself of where you are in your source code, verifying as you go that everything is as
expected.
Here are a few more pointers about OpenOCD: You can set breakpoints in OpenOCD by using the
“bp” command. Something like “bp 0x00100050 4 hw” will set a hardware breakpoint (because of
the “hw” argument) at address 0x00100050. The “4” argument sets the length of the breakpoint to
four bytes which is one word or instruction.
Using the “bp” command with no arguments lists all of the current breakpoints, and the “rbp”
command removes a breakpoint at the specified address (e.g. “rbp 0x00100050”). Remember that
there are only two hardware breakpoints available.
***
This is as far as the guide will go into debugging. The GDB documentation extensively covers the
debugging commands, and other features of GDB, and so it is the place to go for any further details
on debugging with GDB. Once again the OpenOCD User’s Guide is where you should look for help on
using OpenOCD.
Conclusion and Contact Information
Congratulations on making it through to the end of this guide – I hope you have had successful
results because of it. For programming the SAM7S family of microcontrollers, you have now
hopefully gained enough knowledge of the basics that you can continue to progress further on your
own.
The resources and documentation linked to throughout this guide are included once again on the
next two pages for convenience. These should prove valuable as you continue work on your SAM7S
project.
If you need more help with this tutorial, or have any other feedback, feel free to contact me via
email at [email protected].
At this time I’d like to once again mention my thanks to James Lynch for his Using Open Source Tools
for AT91SAM7S Cross Development tutorial (Lynch, 2007), and Miro Samek for his Building Bare-
Metal ARM Systems with GNU tutorial (Samek, 2007). See the references section of this guide for the
full details on these tutorials.
Web Links
In this section I’ve brought together all of the links to software and documentation used throughout
this guide. After finishing the guide and starting your own SAM7S project, this page should provide
you with a lot of good places to look for help if you get stuck or need resources.
AT91SAM7S Datasheet (SAM7S Series Complete):
http://www.atmel.com/devices/SAM7S256.aspx?tab=documents
ARM Documentation Site (For ARMv5 Architecture Reference Manual, Registration Required):
http://infocenter.arm.com/help/index.jsp
Using Open Source Tools for AT91SAM7S Cross Development:
http://www.at91.com/component/resource/article/Tools/27-Development%20Tools/1057-
using-open-source-tools-for-at91sam7-cross-development.html
Building Bare-Metal ARM Systems with GNU:
http://www.state-machine.com/arm/Building_bare-metal_ARM_with_GNU.pdf
Eclipse IDE:
http://www.eclipse.org/
Java:
http://www.java.com/en/download/index.jsp
YAGARTO:
http://www.yagarto.de/
OpenOCD:
http://openocd.sourceforge.net/
Freddie Chopin’s Website (for OpenOCD Binary Download):
http://www.freddiechopin.info/
libusb-win32:
http://sourceforge.net/apps/trac/libusb-win32/wiki
OpenOCD User’s Guide:
http://openocd.sourceforge.net/documentation/online-docs/
Olimex Development Board (Used for this Guide):
https://www.olimex.com/Products/ARM/Atmel/SAM7-H256/
AT91LIB Version 1.9:
http://www.atmel.com/tools/AT91SAMSOFTWAREPACKAGE.aspx
Atmel PLL Filter Calculator:
http://www.atmel.com/tools/ATMELPLLLFTFILTERCALCULATOR.aspx
GNU Compiler Collection Documentation:
http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/
GNU Assembler Documentation:
http://sourceware.org/binutils/docs/as/
GNU Linker Documentation:
http://sourceware.org/binutils/docs/ld/
GNU Make Documentation:
http://www.gnu.org/software/make/manual/#content
GNU Debugger Documentation:
http://sourceware.org/gdb/current/onlinedocs/gdb/
University of Cambridge Arm Instruction Evaluator:
http://svr-acjf3-armie.cl.cam.ac.uk/main.cgi
Version 2 Changes
The major differences between this and the previous version of the guide are briefly outlined below:
- Changed the “B _reset” instruction in sam7s_startup.s to “LDR pc, =_reset”. This is to ensure an
absolute branch to the location of _reset in flash (rather than a relative branch inside the remap
region), which is the same as how the reset exception vector in RAM works.
The reason for doing this was because building with no optimisation (or more specifically, building
without the -fomit-frame-pointer GCC option) would cause a data abort exception. The exception
would occur shortly before the return from the initialisation C function was supposed to happen,
provided the startup and initialisation code was running from the remap region.
I can only assume that without the optimisation, code relating to the frame pointer (which would
otherwise have been omitted) was trying to relatively access flash immediately before the return
from the initialisation function. But, because the initialisation function remaps memory, the relative
addressing was suddenly accessing RAM instead of the expected flash – causing the abort.
- In sam7s256_init.c I have changed the __ram_start variable to be defined as a uint32_t instead of a
uint8_t. This also means I have changed the vector tables to increment __ram_start by multiples of
one instead of four. The reason for this was because higher optimisation levels enable the option “-
Wstrict-aliasing”. When __ram_start was uint8_t, the vector tables were casting it to a uint32_t then
setting it to a 32-bit value. This was causing a warning about “dereferencing a type-punned pointer”
at higher optimisation levels. I decided not to leave the code in a state where warnings occur.
- Added to the makefile a target for debugging startup code. As a side consequence of the reset vector
change in sam7s_startup.s, debugging startup code is now possible in all cases (previously a boot
from flash would cause the startup code to run from the remap region where there are no debugging
symbols). Have also disabled optimisation completely for debugging.
- The Debugging Using OpenOCD section was originally included because of the problems now
eliminated by the change in sam7s_startup.s. Rather than removing the section I have instead
partially rewritten it, because it still contains some potentially useful information.
- Added an alternative method for loading the program to the MCU using OpenOCD and Telnet.
References
Lynch, J. P. (2007). Using Open Source Tools for AT91SAM7S Cross Development Revision C. Retrieved from AT91SAM Community website: http://www.at91.com/component/resource/article/Tools/27-Development%20Tools/1057-using-open-source-tools-for-at91sam7-cross-development.html
Samek, M. (2007). Building Bare-Metal ARM Systems with GNU. Retrieved from Quantum Leaps website: http://www.state-machine.com/arm/Building_bare-metal_ARM_with_GNU.pdf