Copyright 2010-2017,
Thomas Schmid,
Matt Smith,
Ye-Sheng Kuo,
Lohit Yerva,
Prabal Dutta,
and Robert Dick.
Creative Commons License
Compile and install a simple LED blinking application using the ARM Cortex-M3.
In Lab 1 you learned how to program the FPGA side of the Actel SmartFusion. For Lab 2, we will use the integrated ARM Cortex-M3. Because of the extreme flexibility and interconnect potential of the SmartFusion, we will first have to program the FPGA with some wiring. This will allow the Cortex-M3 to access the external GPIO lines connected to the LEDs. For now, download the following FPGA image: Lab 2 FPGA Wiring.
You can program your board by doing the following:First, close any open window. We won't need Actel Libero anymore for the rest of this tutorial, as the Cortex-M3 is programmed using a different toolchain.
Next, we need to create an empty directory that will hold our
programming
code. Open the command line by clicking on START
and type
cmd.exe
into the search field. A short cut to the command
window is Windows key and r key. Click on the application
that gets
found (in Windows 7 it is a black symbol with a C:\_
inside of
it). The command line should be open now and by default, you should be
in your
home directory. Create a new directory by typing (or copy/pasting)
mkdir lab2and hit the
Enter
key. To change into the directory,
execute
cd lab2Next, we have to setup the environment and let it know where the compiler, linker, and debugger can be found. We accomplish this by adding the Code Sourcery to the PATH environment variable. Type the following into your console
set PATH=C:\Program Files (x86)\Microsemi\SoftConsole v3.3\Sourcery-G++\bin\;%PATH%Verify that you typed in the right directory and that by executing the make application:
makeThe output should look something like this:
make: *** No targets specified and no makefile found. Stop.If instead you see a message informing you that
make
is
not a
recognized internal or external command, then verify that the path we
typed in
above has the right spelling, and that it really contains the Code
Sourcery
tools.
Now we create a simple assembler file with one global label "main" and a local label for a branch. Create a file "main.S" with the following content (Use start->All Programs->Notepad++):
.equ STACK_TOP, 0x20000800Use an upper-case “.S” or you may run into portability problems if you move from a case-indifferent to case-sensitive filesystem in the future. However, it doesn't matter on Windows machines. Eclipse, on which the IDE is based, only auto-generates build rules for .S assembly files, not .s assembly files.
.section .int_vector,"a",%progbits @ First linker code section
.global _start @ Linker entry point
_start:
.word STACK_TOP @ Stack pointer
.word main @ program counter start address
@ End of int_vector section
.text
.syntax unified
.type main, %function
main:
b main
.end
We now have to compile the assembly file into an object file. We accomplish this by executing the GNU GCC compiler. The created object file will be called main.o, using the input file main.S to create it. Type the following command into the command line:
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -O0 -Wall -c -nodefaultlibs -nostartfiles -o main.o main.SThis will execute a cross compiler. A cross compiler is a compiler capable of creating executable code for a platform other than the one on which the compiler is running. This is standard practice when working with embedded systems where the destination device is very simple and has limited resources. The arguments mean the following:
-mthumb
:
assemble thumb code-mcpu=cortex-m3
:
specifies the target cpu to be a cortex-m3-O0
:
No optimization-Wall
:
Turn on a lot of warnings-c
: do
not link the object file-nodefaultlibs
:
do not use the normal standard system libraries-nostartfiles
:
do not use the normal standard startup files-o main.o
:
name the output file main.omain.S
:
the input file to be compiledarm-none-eabi-gcc
--help
for a general help, or
arm-none-eabi-gcc
--target-help
for target specific command line
options. A complete list of GCC Command options can be found on the GCC
Command Options website. There is also more documentation in the
Code
Sourcery directory under share/doc/arm-none-eabi/
.
If you want to see what the code looks like that gcc
generated,
you can
disassemble it by using objdump
as
follows:
arm-none-eabi-objdump -S -D main.oThe output of this program is the disassembled object. Ignore the
.ARM.attributes
section.
What is the second address and value under _start
?
It is now time to link our object into the final binary. We currently have only one object, and thus the linking is trivial. However, in more sophisticated applications, the linker will pull together code from several different objects and libraries to produce one binary output file.
The linker needs an input file to let it know about the memory
structure.
Create the following file named link.ld
(you can
type start
notepad link.ld
):
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
MEMORY
{
/* SmartFusion internal eSRAM */
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64k
}
SECTIONS
{
.text : /* Within the .text section: */
{
*(.int_vector) /* Interrupt Vector Table is always first */
*(.text*) /* All other text sections follow */
} >ram
}
end = .;
Next, we link the object file:
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -specs=bare.specs -nodefaultlibs -nostartfiles -Tlink.ld -Wl,-Map=lab2.map -o lab2 main.oThis will invoke the linker (note we omitted the -c option to gcc). The new arguments mean the following:
-specs=bare.specs
:
use the bare.specs file to load additional parameters. This file is in
the Code Sourcery directory under arm-none-eabi/lib
.-Tlink.ld
:
the name of the linker file to use-Wl
,-Map=lab2.map
:
output the memory map in lab2.map. This is later used in the actel-map
application.-o lab2
:
the output should be stored in the lab2
file-o main.o
:
this is a list of object files that should get linked together.
Currently, we only have one object. Later, we will have several.
You now have a binary object file, lab2
. Disassemble
it using:
arm-none-eabi-objdump -S -D lab2
How is it different from the output of the main.o file? (value 0x20000009
should
exist at address 0x20000004
)
Why is <_start>
before <main>
?
We need to change the format of our binary file in order to program the Cortex-M3 with our application. Execute the following commands, one after another:
actel-map -o "memory-map.xml" lab2 arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -specs=bare.specs -nodefaultlibs -nostartfiles -Tlink.ld -Wl,-Map=.map
arm-none-eabi-objcopy -O ihex lab2 "lab2.hex"
arm-none-eabi-objcopy -O srec lab2 "lab2.srec"
arm-none-eabi-objdump -h -S lab2 > "lab2.lst"
Find out what these specific objcopy
commands
do by using an Internet search
and the command line arm-none-eabi-objcopy
--help
command.
We use the GNU Debugger (GDB) to upload our
binary to the Cortex-M3. This
particular flash application needs a helper application to lock the
programmer. Therefore, we have to run an actel keep-alive mutex.
Execute the
command:
start actel-keepalive actel-keepaliveThis will open a new command window running the actel-keepalive application. Don't close this window, as long as you need GDB. You can, however, minimize it to the taskbar.
Switch back to your other command window and start GDB by typing
arm-none-eabi-gdbThen, execute the following commands. The lines proceeding with a "#" are comments and explaining what the command does:
# Load the symbol tableAfter the last line, the output of GDB should look like this:
file lab2
# Invoke debug sprite in Cortex-M3 mode
target remote | "arm-none-eabi-sprite" flashpro:?cpu=Cortex-M3 "./"
# Don't restrict memory access to just regions defined in linker script
set mem inaccessible-by-default off
# Disable the watchdog
set *0x40006010 = 0x4C6E55FA
# Specify user application vector table
set *0xE000ED08 = 0x20000000
# Load the program
load
# Run the application
cont
Congratulations! You just started your first application on the Cortex-M3. The yellow LED D14 should be blinking. Nevertheless, this application is not doing much as it is just busy looping forever.
It's now time to hit ctrl-c
to stop
the execution (or quit
to exit GDB). You will again
be presented with the GDB prompt. This prompt allows you to examine the
state
of your embedded platform and later on debug your code by looking at
variable
values, memory, and registers. Go ahead, type help
and explore
some of the capabilities of GDB. For example, type info
registers
. However, note that we compiled our code without
debugging
information. Thus, some of the commands won't show you any useful
information.
Can you tell where in your application you stopped your application if
you
type info registers
?
Explain how and why.
Now add the following two instructions between the lines: main:
and b main
.
mov r1, #1Program the SmartFusion and examine the registers for their values. Did it work?
add r1, r1, #1
Now, write a for loop in assembly by using one of the registers as incrementing variable. Count up to 100 before you go into the busy loop. Use the following debugging setup to test and debug your code.
Please READ the following helpful hints
CB{N}Z
; CMP
= Compare,
branch if zero; CBNZ
= Compare, branch if not zero; <label>
has
to point to an address after the current CB{N}Z
instructionEQ
(==0) or NE
(!=0) to B
(branch) to get BEQ
or BNE
.
They use the status register condition flags to branch. The following
is from the ARMv7 Architecture Reference Manual. SUB
r1, r2
is same as SUB r1, r1, r2
.
That is <operand2>
== r2
.
Similarly, <operand2>
== r2
, in CMP r1, r2
.
SUB
r1, r2, r3
is a 32bit ARM
instruction, not the 16bit
thumb instruction used for SUB r1, r2
.CMP r1, r2
performs r1-r2
and sets the Z flag in the status register accordingly. CMPS
is not
an instruction.{S}
extension can
also update the status register.LSL{S}
Rd, Rm, <Rs|sh>
), <Rs|sh>
can
be a register or a constant.You already know how to launch GDB. However, its usefulness in order
to find
information about the running application were fairly limited so far.
The
compiler has to annotate your binary with special information in order
to let
GDB know more about the application. We accomplish this by passing the -g3
option to
GCC.
Recompile your application but add -g3
to every
non-linking call of GCC,
i.e., execute the following commands
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -O0 -g3 -Wall -c -nodefaultlibs -nostartfiles -o main.o main.SDisassemble the
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -specs=bare.specs -nodefaultlibs -nostartfiles -Tlink.ld -Wl,-Map=lab2.map -o lab2 main.o
lab2
binary now. Do you notice the difference to before?
Now, let's start GDB but this time, use the following script to run your application
# Load the symbol tableNote that this time, we set a temporary breakpoint. Once you hit enter after
file lab2
# Invoke debug sprite in Cortex-M3 mode
target remote | "arm-none-eabi-sprite" flashpro:?cpu=Cortex-M3 "./"
# Don't restrict memory access to just regions defined in linker script
set mem inaccessible-by-default off
# Disable the watchdog
set *0x40006010 = 0x4C6E55FA
# Specify user application vector table
set *0xE000ED08 = 0x20000000
# Load the program
load
# set a temporary breakpoint at main
tb main
# Run the application
cont
cont
GDB
will stop once it reaches the main function. Your GDB
shell should look something like this:
You are now ready to debug your application. The following list are some useful GDB commands. But they are by far not all of them. Use the GDB help for many more commands to inspect memory, backtrace code, or for different types of stepping or continuing executing the code.
cont |
continue execution until the next breakpoint |
step |
advance to the next line of code |
step n |
advance n code lines |
next |
step forward proceeding over subroutine calls |
b
main.S:10 |
set a breakpoint on line 10 in the main.S file |
list |
show the source code around the current position |
disp varname |
display the content of variable varname every time we stop |
bt |
backtrace the function call history |
Try out to set a breakpoint in your loop. Then, check whether the
register
increments correctly each loop.
Also, try to find out what the difference between b
and bt
command is.
There are many many more commands in GDB. I encourage you to learn how
to use
GDB, as it is THE tool for debugging your embedded system. For example,
you
can put a watch on a variable, and interrupt the program code
if the
variable, or memory location for that matter, changes. Also note that
hitting
enter
on an empty line in GDB repeats the last command.
This is
extremely useful if you just want to step through code and advance one
line at
a time.
In-Lab Questions
There are a few In-Lab questions from this point on. Answer these questions and submit them with your post lab.
Q1: Explain the difference between the GDB commands step
and
stepi
. Use the GDB help
command to find the
difference. Is there any difference with assembly source code? C code?
We now change our application to toggle the I/O lines connected to the LEDs. We accomplish this by using one of the peripherals of the Cortex-M3, the "General Purpose I/O Block (GPIO)". You can find more about this particular peripheral in the "Actel SmartFusion MSS User's Guide".
You will also learn more about how we really communicate with peripherals in later lectures and labs. For now, just read the following brief overview.
Remember the EECS 370 five stage pipeline? The only thing it could do was read and write from memory. In EECS 270, you built a D-Flip-Flop from gates and understood how to read and write from it. A DFF is also called register, which is physically the same at the registers in the processor. In the EECS 270 lab, you connected an LED to the output of a DFF or register. How does a processor control an LED? By writing values to the LED register. We use a method called Memory Mapped I/O to allow the processor to access the LED register. Basically, some region of the memory address space is allocated for the external I/O devices. The value that the processor thinks is sending to some location in standard memory is physically redirected to a new processor.
In the PostLab, you will be asked to write assembly functions that initialize and set the GPIO (use the completed main.S, which is provided after gpio.S, as a guide):
initGPIO task - In the case of the GPIO peripheral, we have two specific memory locations of interest. The first one is the configuration register. Note that these are not like the Cortex-M3 registers r0-r15. We just use the name register to indicate a specific location in memory. The microcontroller subsystem has 32 I/O lines, and each line has its own configuration register. The memory location starts at 0x40013000 for I/O line 0 (GPIO0). Each register is 32 bit long, and since the memory is byte indexed, we increment this memory location by 4 to find the configuration register for the other I/O lines, i.e., the configuration register for GPIO i is at (0x40013000 + i*4).
The bits in the configuration register have a specific meaning. The following table is a summary of the different configuration possibilities. To see further description, look at the "Actel SmartFusion MSS User's Guide, Revision 1" on page 317.
As we want our I/O lines to be output, we will have to write 0x1 into the configuration registers.
setGPIO task - Once we configured all the I/O lines we need, we can set their status with the output register GPIO_OUT located at 0x40013088. Every bit inside this register represents one I/O line. Thus, clearing bit i to 0, will pull GPIO i low, while setting bit i to 1, will pull GPIO i high.
For now, we are going to provide you with assembly function stubs. That is they are fully callable, but do nothing. In the following section we will use these functions stubs to illustrate a powerful software development tool. The assembly function stubs follow. For now just create gpio.S file with the following code in a new folder, lab2b.
.equ GPIO_OUT_BASE, 0x40013000
.equ GPIO_OUT, 0x40013088
.text
.syntax unified
.thumb
@ Configure the GPIOx to output
@ Inputs: GPIO number is provided in r0
@ Output:
.global initGPIO
.type initGPIO, %function
initGPIO:
@ Load GPIO_OUT_BASE address
@ Calculate the GPIOx register address
@ left shift by 2 => i*4
@ GPIO_OUT_BASE + i*4
@ Write 1 to config register to set GPIO as output
bx lr @ Return
@ Set the value of all 32 GPIO output bits based on the input bits
@ Inputs: 32bit value is provided in r0
@ Output:
.global setGPIO
.type setGPIO, %function
setGPIO:
@ Load GPIO_OUT register address
@ Write 32bit value to GPIO output register
bx lr @ Return
Create the binary object by compiling the gpio.S file using
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -O0 -Wall -c -nodefaultlibs -nostartfiles -o gpio.o gpio.S
Note the calls to initGPIO and setGPIO in the following
code.
Create a new file called main.S
with the following
content:
.equ STACK_TOP, 0x20000800Note that we also have to reset the GPIO peripheral before we can use it. This is performed by writing 0, then 1 into a particular bit of the SYSREG_SOFT_RST_CR registers. Use
.equ SYSREG_SOFT_RST_CR, 0xE0042030
.section .int_vector,"a",%progbits @ First linker code section
.global _start @ Linker entry point
_start:
.word STACK_TOP, main
@ End of int_vector section
@ Standard text section
.text
.syntax unified
.thumb
.type main, %function
main:
@ Load SYSREG_SOFT_RST_CR address
movw r0, #:lower16:SYSREG_SOFT_RST_CR
movt r0, #:upper16:SYSREG_SOFT_RST_CR
@ Reset GPIO hardware
ldr r1, [r0, #0]
orr r1, #0x4000
str r1, [r0, #0]
@ Take GPIO hardware out of reset
ldr r1, [r0, #0]
mvn r2, #0x4000 @ move bitwise negation of 0x4000 into r2
and r1, r2
str r1, [r0, #0]
mov r0, #24 @bits 24 - 31 are mapped to LEDS 0-7 respectively or D1-D8 on the kit
bl initGPIO @ Call initGPIO in gpio.S to initalize GPIO 24
mov r0, #0
bl setGPIO @ Call setGPIO in gpio.S to write 0 to GPIO output register
loop:
mov r1, #1
lsl r1, #24
eor r0, r1 @ Exclusive-OR (XOR)
bl setGPIO
b loop
.end
main.S
as an
example for modifying gpio.S
.
Now, create the binary object by compiling the main.S file using
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -O0 -Wall -c -nodefaultlibs -nostartfiles -o main.o main.S
Q2: Disassemble the main.o file. Do you see the function call into your gpio.o file? Explain.
bl 0 <initGPIO>
, it means that 0
is the current address of the label initGPIO. We now have two object files and are ready to use the linker to create one large binary file. Copy over the link.ld from lab2. Execute the linker using the following command
arm-none-eabi-gcc -mthumb -mcpu=cortex-m3 -specs=bare.specs -nodefaultlibs -nostartfiles -Tlink.ld -Wl,-Map=lab2b.map -o lab2b main.o gpio.oNote that this time, we have two object files after the
-o lab2b
parameter. You can again disassemble this file and look at the binary
code created. Do
you see how the linker resolved all the jumps to the right memory
locations in
your main application?
Follow the instructions in the next 2 sections to build and debug your code in the user friendly SoftConsole IDE.
You can close all the command line windows, and start SoftConsole through the Start Menu. Until now, you have done everything by hand, and embedded system programming might seem tedious to you. However, there exist very good Integrated Development Environments (IDE) that wrap all the tools you just used into a nice interface.
Microsemi/Actel's development IDE is called SoftConsole
and is based on Eclipse.
Underneath, it uses the CodeSourcery
G++ for the exact same steps you just
executed. They just hide them using a nice interface. Additionally,
they use
make
, a tool which controls the generation of executables
and
other non-source files. However, it is beyond the scope of this class
to show
you everything that GNU make can do, especially as SoftConsole
completely
hides its usage. If you are interested, read the GNU Make Website
or ask a TA or lab staff for the location of the Makefile. We are happy
to
show you where it is.
You can close all the open windows, and start SoftConsole through the Start Menu. After starting, SoftConsole will ask you for the workspace. Accept the default, mark the "Use this as the default and do not ask again" checkmark, and hit OK.
Use SoftConsole v3.3Next you will see the "C/C++ Prespective". This is the layout for Assembly or C development.
We first have to create an empty project. Click on File > New > C Project. Name the project Lab2 and make sure the Actel Cortex-M3 Tools Is selected in the Toolchains choice. Then hit finish.
Now we are ready to create an Assembly source file in SoftConsole. Click on File > New > Source File. Name the file main.S and make sure that it says Lab2 Source Folder. Change the Template to <None>. When done, hit Finish.
Now, copy the content from your lab2b main.S
file into
this one.
To import the gpio.S
and link.ld
files,
right click
on the Lab2 project name on the left hand side and select
Import.... In the lower selection list, select General >
File System
and click Next. Click on Browse... and select the lab2b
folder under your user name. After hitting Ok you should now
see the
content of this folder in the lower selection window. Select the
gpio.S
and link.ld
files and click Finish.
A copy of gpio.S and link.ld now exists in the Lab2 folder.
Next, we have to configure our build target. Building is the process of compiling and linking all the assembly source code into the a single final binary file. This particular linker script builds an executable that runs from the SmartFusion internal SRAM. Therefore, after a reset, the program will be gone. We will see later how we can program our application into the non-volatile memory of the SmartFusion.
We are now ready to build our project. Click on Project > Clean... and make sure that the Lab2 project is selected. Check Clean projects selected below and Build only the selected projects. This will remove any old binaries and create the final binary for only the selected project.
If everything went well, then you should have an empty Problems tab on the bottom of your perspective. If there are problems, scroll through the Console tab to find the command that caused the problem.
From now on you can use the Hammer icon
to build. It is faster because it doesn't clean before compiling, and
it only
recompiles the files that have changed. Basic build works in most
cases, however,
if you run into problems perform a Clean build.
Q3: In the left window pane, Project Explorer, open Lab2->Debug->Lab2.lst file. Does it look familiar? How? Explain the contents of the file.
Note that by default, all the binaries build with SoftConsole contain debug information. You will also note that you got two new folders in your project named Debug and Binaries. As their names suggest, they hold debug information and the final binary respectively.
Expand the Debug folder. You will see many familiar files
inside of it
that you manually created last time. The key behind everything is the
makefile
inside this folder. It tells make
what
targets it should compile, and which commands to run.
Now, let's program our microcontroller and start the debugging. The first time, we will have to configure our debug target. For this, right click your project Lab2 and select Debug As > Debug Configurations .... A new window will open. Right click Actel Cortex-M3 RAM Target and select New.
Next, click on Apply then Debug. The window will close and SoftConsole asks you if it should switch over to the Debug Perspective. Click on Remember my decision. Then click on Yes.
SoftConsole automatically launched GDB in the background. You can see its output in the bottom Console tab. Additionally, SoftConsole's Debug Perspective also shows you your source code, an outline of your binary, variables, breakpoints, registers, and a call trace. At the top, it gives you shortcuts to typical GDB commands you used earlier like cont (called resume, the button with the green arrow), step (called step into), next (called step over), and many more.
Try to add a breakpoint by double clicking on the space just left of the line number in your main.c file. Notice how the line gets marked with a blue-green dot, and how the breakpoint appears in the Breakpoint tab. Click the Resume button (or hit F8). Notice how your code stopped on your breakpoint. Additionally, the Variables tab updated to the current context, showing you the content of all the visible variables.
If you ever feel the need that C stepping doesn't work, and that you need to look at the disassembled code, click the Instruction Stepping Mode button on the top (the letter "i" with a right pointing arrow). A new tab will appear showing you the disassembled code at your location. Additionally, the step button now becomes a stepi instruction. You can also add breakpoints to the disassembly tab, similar as you were able to do in the C file.
Explore the capabilities of the debugger. I highly encourage you to learn the keyboard shortcuts. They will make your life much easier later on during the projects. Additionally, it speeds up your debugging a lot.
gpio.S
that were provided for you
in section 5.
Comments were provided in the function stubs suggesting content. The
content in section 5 should provide you with enough information to
initialize the and set the GPIO registers. For the truly ambitious, create a "Knight Rider" effect with all the LEDs in pure Assembly. The LEDs should fade, not just toggle, between each other.
Demonstrate and verify to the lab staff..