Introduction: Part 1 ARM Assembly TI RSLK Robotics Learning Curriculum Lab 7 STM32 Nucleo

About: Have enjoyed a mostly fun ride in an electronics and software career. Also enjoy latin dancing.

The focus of this Instructable is the STM32 Nucleo micro-controller. The motivation for this to be able to create an assembly project from bare bones. This will aid us delve deeper and understand the MSP432 Launchpad project (the TI-RSLK) that has been the topic of several Instructables already.

There isn't very much help online to create an assembly-only project for the MSP432, using the Code Composer Studio. Up to now we have been just copying/pasting from a pre-existing assembly project. This approach has served us well.

However, now, for Lab 7, we've run into a bit of a problem. Or at least a temporary hiccup. The Lab 7 introduces finite-state-machines, and the first thing we encounter is the need to create and use an array of values. Since the TI course mainly uses C programming - this is not a problem. But these Instructables have focused on assembly, not C.

Further, since the array is of read-only values, it would be good to put it in flash memory, not RAM.

There seems to be a lot more help online for assembly projects using the STM32 MCU, thus, we begin with this Instructable, with the goal of using what is learned, to then apply to the MSP432 and the Code Composer Studio.

On the road to that goal, we'll have also gotten experience with yet another, popular micro-controller.

Step 1: Initial Test of Device

Again, why pick the STM32 Nucleo in particular?

Honestly? Because I was searching for good articles on bare-metal assembly projects for ARM controllers, and I came across this series. And also because the STM32 seems to be a popular MCU.

I did do some research (there are a lot of versions to choose from - see image above), but in the end it became what can I actually get, since I was going to use Amazon (in the U.S.).

It comes in a simple but professional package, with some start-up instructions. It was a bit funny to see that the demo burned into the controller was almost exactly what we've done in past Instructables - an LED flashes and changes speed according to the press of a button.

It seems that this development board is very similar to the MSP432 in that there are 2 LEDs, and one user-pushbutton. The MSP432 has 2 user-buttons.

As you can see in the photos, I was a bit taken aback that the board has a mini and not a micro USB. Had to run out to buy a cord.

Another good test is that when you connect it to your computer (I am using a Linux box), it shows up in my file manager, as a file system, called "NODE_F303RE". Opening that reveals two files, one HTML and one text.

That's it, but at least it also says connectivity seems quite easy.

Now we are ready to begin.

I'm going to try to not repeat any of the good information from the IVONOMICON Bare Metal article series, but rather augment it.

Step 2: The Essentials

First thing we need is a compiler.

<pdevchu@chubox:~$ sudo apt-get install gcc-arm-none-eabi
[sudo] password for devchu: 
Sorry, try again.
[sudo] password for devchu: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following additional packages will be installed:
  binutils-arm-none-eabi libnewlib-arm-none-eabi libnewlib-dev
  libstdc++-arm-none-eabi-newlib
Suggested packages:
  libnewlib-doc
The following NEW packages will be installed:
  binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libnewlib-dev
  libstdc++-arm-none-eabi-newlib
0 upgraded, 5 newly installed, 0 to remove and 8 not upgraded.
Need to get 65.1 MB of archives.
After this operation, 610 MB of additional disk space will be used.
Do you want to continue? [Y/n]

And then, we need a debugger:

devchu@chubox:~$ sudo apt-get install gdb-arm-none-eabi<br>Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  gdb-arm-none-eabi
0 upgraded, 1 newly installed, 0 to remove and 8 not upgraded.
Need to get 2,722 kB of archives.
After this operation, 7,738 kB of additional disk space will be used.
Get:1  http://us.archive.ubuntu.com/ubuntu  xenial/universe amd64 gdb-arm-none-eabi amd64 7.10-1ubuntu3+9 [2,722 kB]
Fetched 2,722 kB in 1s (1,988 kB/s)            
Selecting previously unselected package gdb-arm-none-eabi.
(Reading database ... 262428 files and directories currently installed.)
Preparing to unpack .../gdb-arm-none-eabi_7.10-1ubuntu3+9_amd64.deb ...
Unpacking gdb-arm-none-eabi (7.10-1ubuntu3+9) ...
Processing triggers for man-db (2.7.5-1) ...
Setting up gdb-arm-none-eabi (7.10-1ubuntu3+9) ...

Step 3: The Essentials - Windows

The above step assumed we're using Linux. What if we're using Windows?

You can go to the arm Developer site, and there are several download options available. I am using a Windows 8 machine.

During installation, I elected to install it to the root "C:\" drive instead of Program Files just because I am also using cygwin, and it was easier to create a link from my local bin to a root C: folder than all the mess in the path to Program Files (with spaces, etc).

Thus, my cygwin environment and path, etc, looks like so:

C:\cygwin64\home\<your user name here>\bin\arm-none-eabi-gcc, where the arm-none-eabi-gcc is a link to C:\GNUToolsArmEmbedded\7.2018.q2.update\bin\arm-none-eabi-gcc .

I then created a "dev" folder under cygwin home, and that's where I placed the core.S file and ran the compiler command. (see further below for the compiler stuff).

I did the exact same thing for gdb (arm-none-eabi-gdb).

Step 4: What Are the Essentials

So what is "gcc-arm-none-eabi" ?

The gnu compiler (GCC) will compile programming languages (like C) into native code for the machine it's running on. For instance, if you were to compile some C code using GCC on your Windows machine, it would be built to run on the Windows machine. The generated executable will not (typically) run on the ARM micro-controller.

So, in order to build programs to be downloaded and burned into the ARM micro-controller (in our present case that would be the STM32 Nucelo), we need to give GCC something else: the ability to "cross-compile". That is, the ability to generate an executable, not for its native system (and processor), but for the target system (the ARM micro-controller). That's where "gcc-arm-none-eabi" comes into play.

So then what is "gdb-arm-none-eabi" ?

Once we have downloaded and burned(flashed) the newly-generated executable into the micro-controller, we'll probably want to debug it - step through line by line of the code. GDB is the gnu debugger, and it, too, needs a way to do its job, but targeting a different system.

Thus, gdb-arm-none-eabi is to GDB, what gcc-arm-none-eabi is to GCC.

Another suggested package install was the "libnewlib-arm-none-eabi". What is that one?

Newlib is a C library and math library intended for use on embedded systems. It is a conglomeration of several library parts, all under free software licenses that make them easily usable on embedded products.

And finally, the package "libstdc++-arm-none-eabi". That one's pretty obvious; it's C++ library for the cross-compiler; for embedded ARM micro-controllers.

Step 5: The Linker File

Let's create a linker script.

One key part or block in this file would be the MEMORY command.

--- from sourceware.org:

The linker’s default configuration permits allocation of all available memory. You can override this by using the MEMORY command.
The MEMORY command describes the location and size of blocks of memory in the target. You can use it to describe which memory regions may be used by the linker, and which memory regions it must avoid. You can then assign sections to particular memory regions.
The linker will set section addresses based on the memory regions, and will warn about regions that become too full. The linker will not shuffle sections around to fit into the available regions.
A linker script may contain many uses of the MEMORY command, however, all memory blocks defined are treated as if they were specified inside a single MEMORY command.
The syntax for MEMORY is:
MEMORY
    {
        name [(attr)] : ORIGIN = origin, LENGTH = len
        …
    }

The example in the article:

/* Define the end of RAM and limit of stack memory */
/* (4KB SRAM on the STM32F031x6 line, 4096 = 0x1000) */ /* (RAM starts at address 0x20000000) _estack = 0x20001000;

MEMORY { FLASH ( rx ) : ORIGIN = 0x08000000, LENGTH = 32K RAM ( rxw ) : ORIGIN = 0x20000000, LENGTH = 4K }

So we need to figure out how much FLASH (for our program and constants, etc) and how much RAM (for use by the program; heap and stack, etc) for our particular board. This gets a bit interesting.

The nice little card that comes with the Nucleo says that it has flash memory is 512 Kbytes, and SRAM is 80 Kbytes. However, connecting it to USB, it gets mounted as a file system with two files, and both the file manager and GParted indicate it has over 540+ Kbytes of space. (RAM?).

BUT, attempting to delete the two files using the file manager, disconnecting then reconnecting the device, still shows the two files. (and the file manager did recognize something because there's a little "lock" icon on each file.

So let's go with the figures on the card. So now we take the above example and convert it to our specific board.

You may want to use something like this online memory converter, to go from the general KB to specific number of bytes.

Then you may want to use an online decimal to hex converter.

/* Define the end of RAM and limit of stack memory */

/* (4KB SRAM on the STM32F031x6 line, 4096 = 0x1000) *//* the example*/

/* step 1: (80KB SRAM on the STM32F303RE, 81920 = 0x14000) *//* our board */

/* step 2, add the hex size to hex starting address (below). */

/* (RAM starts at address 0x20000000) */

_estack = 0x20001000; /* the example */

_estack = 0x20014000; /* our board */

MEMORY {

FLASH ( rx ) : ORIGIN = 0x08000000, LENGTH = 512K

RAM ( rxw ) : ORIGIN = 0x20000000, LENGTH = 80K

}

Let's call the above file "linker.script.ld".

Step 6: The Vector Table

Now we're going to create a small assembly file (with directives) to do some very basic interrupt-handling. We'll follow the article's example and create file named "core.S".

Again, here is the example file contents, but I made a change for our specific board:

// These instructions define attributes of our chip and

// the assembly language we'll use:
.syntax unified        /* See below after this code area */
/*.cpu cortex-m0 */ /*comment out this line of the example */
.cpu cortex-m4     /* add instead our board's cortex. see above image in this step */

/*.fpu softvfp */ /* comment out this line of the example */
.fpu vfpv4         /* add instead our board's; it does have an FPU */

.thumb

// Global memory locations.
.global vtable
.global reset_handler

/*
 * The actual vector table.
 * Only the size of RAM and 'reset' handler are
 * included, for simplicity.
 */
.type vtable, %object
vtable:
    .word _estack
    .word reset_handler
.size vtable, .-vtable

Hmm.. No '.align' Directive

However, that's not critical. More on that (maybe) later.

.syntax unified

.syntax [unified | divided]

This directive sets the Instruction Set Syntax as described in the ARM-Instruction-Set section

9.4.2.1 Instruction Set Syntax Two slightly different syntaxes are support for ARM and THUMB instructions. The default, divided, uses the old style where ARM and THUMB instructions had their own, separate syntaxes. The new, unified syntax, which can be selected via the .syntax directive.

.fpu vfpv4

The GCC compiler can produce binaries with several options regarding floating point: soft - suitable for running on CPU's with no FPU - calculations are done in software by compiler generated softfp - suitable for running on CPU's with or without FPU - will use an FPU if present. For our specific case (you'll have to do your own research), this particular board's FPU conforms to vfpv4. You may have to play with this. Or even leave it at softfp.

.thumb (vs .arm)

These ARM microcontroller actually have a mix of instruction sets. One is ARM, another is THUMB. One difference is 16-bit instructions vs 32-bit instructions. Thus, this directive tells the compiler to treat subsequent instructions as either THUMB or ARM.

We're going to just take the remainder of the file as-is since these Instructables haven't yet delved into interrupt-driven assembly programming.

Attachments

Step 7: The Assembly Version of a 'Hello World' Program

The following can also go into the previously-created "core.S" file. This, again, is from the example in the article.

/*
* The Reset handler. Called on reset. */ .type reset_handler, %function reset_handler: // Set the stack pointer to the end of the stack. // The '_estack' value is defined in our linker script. LDR r0, =_estack MOV sp, r0

// Set some dummy values. When we see these values // in our debugger, we'll know that our program // is loaded on the chip and working. LDR r7, =0xDEADBEEF MOVS r0, #0 main_loop: // Add 1 to register 'r0'. ADDS r0, r0, #1 // Loop back. B main_loop .size reset_handler, .-reset_handler

So, the thrust of the above program is to load a recognizable pattern into one core MCU register (in this case R7), and an incrementing value starting at zero into another core MCU register (in this case R0). If we step through the executing code, we should see R0's data increment.

If you have been following along with the Instructables regarding the MSP432 and the TI-RSLK course/labs, then pretty much all of the above program should be familiar to you.

The one new thing I can see is the use of "=" when loading "DEADBEEF" in to register R7. We hadn't used that.

The "core.S" file attached here now contains the complete source.

Attachments

Step 8: Compiling the Code

It's time to do some command-line stuff. Something real, finally.

However, we're not quite there. We again have to tweak the command given in the article, and modify it to our own situation.

Here's the example code:

arm-none-eabi-gcc -x assembler-with-cpp -c -O0 -mcpu=cortex-m0 -mthumb -Wall core.S -o core.o

If we go to the gnu.org site for GCC, (in this case version 7.3),

-x

The -x is to specify the language. Otherwise if no -x, then the compiler will try to guess by using the file extension. (in our case, *.S).

The above example from the article specifies assembler-with-cpp, but we could just do assembler.

-c

The -c says "compile but do not link.

-O0

The -O is to set the optimization level. Using -O0 (oh-zero) says "reduce compile time and make debugging produce the expected results. This is the default".

-mcpu=cortex-m0

The -mcpu specifies the name of the target processor. In our case, it would be cortex-m4.

-mthumb

The -mthumb specifies selecting between generating code that executes ARM and THUMB states.

-Wall

The -Wall is of course very common and well-known. It turns on all warning flags.

Finally, at the end of the command we have the input file core.S and the output file core.o.

Here is the resulting new command line to fit our specific case.

arm-none-eabi-gcc -x assembler -c -O0 -mcpu=cortex-m4 -mthumb -Wall core.S -o core.o

And that compiled.

Step 9: Linking the Program

Directly from the example in the article, we have this:

arm-none-eabi-gcc core.o -mcpu=cortex-m0 -mthumb -Wall --specs=nosys.specs -nostdlib -lgcc -T./STM32F031K6T6.ld -o main.elf

Most of the above you've seen. Below is what's new.

--specs=nosys.specs

This one is a bit tricky to explain.

It has to do with "semihosting" and "retargeting", and it has to do with input / output. It also has to do with system calls and libraries.

Typically, embedded systems do not provide standard input/output devices. This would affect system or library calls (example: printf()).

Semihosting means the debugger (see Step 11 image with debugger portion circled in red) has a special channel and uses the semihosting protocol, and you can see the output of printf() on the host machine (via the debugger).

Retargeting, on the other hand, means that those very same system or library calls mean something else. They do something else, that makes sense for the embedded system. In a sense, say for printf(), there's a new implementation, a retargeted implemention of that function.

Having said all that, the --specs=nosys.specs means that we will not be semihosting. That would normally then mean we're retargeting. That brings us to the next flag.

-nostdlib

The linker option -nostdlib is used to link a program intended to run standalone. -nostdlib implies the individual options -nodefaultlibs and -nostartfiles. Below we discuss the two options separately, but the most typical use is just nostdlib for one-stop shopping.
When linking a hosted program, standard system libraries such as libc are linked by default, giving the program access to all standard functions (printf, strlen and friends). The linker option -nodefaultlibs disables linking with those default libraries; the only libraries linked are exactly those that you explicitly name to the linker using the -l flag.

-lgcc

libgcc.a is a standard library that provides internal subroutines to overcome shortcomings of particular machines. For example, the ARM processor does not include a division instruction. The ARM version of libgcc.a includes a division function and the compiler emits calls to that function where needed.

-T<script>

This is just a way to tell the linker to use this file as the linker script. In our case, the file name is linker.script.ld.

-o main.elf

Finally, we tell the linker what will be the name of the final output image file that will be burned/flashed into our device.

Here is our version of the complete command-line, modified for our specific situation:

arm-none-eabi-gcc core.o -mcpu=cortex-m4 -mthumb -Wall --specs=nosys.specs -nostdlib -lgcc -T./linker.script.ld -o main.elf

We make sure that script file, and the core.o file, are both in the same directory, where we'll run the above command-line.

And it links with no problems.

A Check

We then run:

arm-none-eabi-nm main.elf

and we get:

devchu@chubox:~/Development/Atollic/TrueSTUDIO/STM32_workspace_9.1$ arm-none-eabi-nm main.elf
20014000 A _estack 08000010 t main_loop 08000008 T reset_handler 08000000 T vtable

Looks good. The arm-none-eabi-nm command is a way to list symbols within object files.

Step 10: TestingConnection to the STM32 Nucleo-64

Your first mission, should you choose to accept it, is to get your system to see your development board.

Using Windows

For Windows, I decided to install TrueSTUDIO from Atollic (free version). It was a painless install and it automatically installed the driver so I could use st-link to test the connection. Once I installed the TrueSTUDIO and the device manager saw the device, I did download the texan/stlink tools suggested by the Bare Metal article we've been following. I again placed the folder directly under "C:\", and again created some links from my local cygwin home bin to the commands.

ln -s /c/STM32.MCU/stlink-1.3.0-win64/bin/st-info.exe ~/bin/st-info

As an initial test to see if we can really communicate with the device, I ran:

st-info --probe

And got back:

Found 1 stlink programmers

So now we know we can talk/query our development board.

Using Linux

For linux, you don't really need an driver. But for Debian, you'll have to build the st tools from source.

git clone https://github.com/texane/stlink

Make sure you have libusb-1.0-0-dev installed.

apt list | grep -E "*libusb.*dev*"

You should see:

libusb-1.0-0-dev/xenial,now 2:1.0.20-1 amd64 [installed]

or something like that.

To install it:

sudo apt-get install libusb-1.0-0-dev

Note that the above is not the same as:

sudo apt-get install libusb-dev

The correct missing libusb dev can cause cmake to have issues.

CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
Please set them or make sure they are set and tested correctly in the CMake files: LIBUSB_INCLUDE_DIR (ADVANCED)

Change to the root directory of the project (...blah/blah /stlink). Do a "make release".

After that builds, the tools should be under ".. /build/Release".

You can then run "st-info --probe". Here's the output with the Nucleo connected, then not.

devchu@chubox:~/Development/stlink$ ./build/Release/st-info --probe
Found 1 stlink programmers serial: 303636414646353034393535363537 openocd: "\x30\x36\x36\x41\x46\x46\x35\x30\x34\x39\x35\x35\x36\x35\x37" flash: 524288 (pagesize: 2048) sram: 65536 chipid: 0x0446 descr: F303 high density device devchu@chubox:~/Development/stlink$ ./build/Release/st-info --probe Found 0 stlink programmers devchu@chubox:~/Development/stlink$

Step 11: Let's Use GDB With Linux

If you have been trying all this, and you have gotten this far - great! Excellent. Let's have a little fun now.

When you buy these ARM development boards, whether they're the MSP432 Launchpad from Texas Instruments, or this one we're discussing now, the Nucleo-F303 (STM32 Nucleo-64), they usually arrive already flashed with a running program, typically some blinky program that also includes pressing a switch to change the rate at which the LED(s) flash.

Before we are so quick to over-write that, let's see what there is to see and do.

With Linux, open a terminal, change directory the stlink git project that we just built, and find the st-util tool.

devchu@chubox:~/Development/stlink$ find . -name st-util
./build/Release/src/gdbserver/st-util

Run that tool. Since we've already previously tested our connection with st-info --probe, we should get some output like so:

devchu@chubox:~/Development/stlink$ ./build/Release/src/gdbserver/st-util
st-util 1.4.0-50-g7fafee2
2018-10-20T18:33:23 INFO common.c: Loading device parameters....
2018-10-20T18:33:23 INFO common.c: Device connected is: F303 high density device, id 0x10036446
2018-10-20T18:33:23 INFO common.c: SRAM size: 0x10000 bytes (64 KiB), Flash: 0x80000 bytes (512 KiB) in pages of 2048 bytes
2018-10-20T18:33:23 INFO gdb-server.c: Chip ID is 00000446, Core ID is  2ba01477.
2018-10-20T18:33:23 INFO gdb-server.c: Listening at *:4242...

That's the GDB server running now, and it sees our development board, and more importantly, it is listening on port 4242 (the default port).

Now we are ready to fire up the GDB client.

In Linux, open another terminal, enter this:

arm-none-eabi-gdb -tui

That's just the same as running gdb strictly command-line, however it instead produces a text-based terminal (my guess is that it uses curses).

We have the GDB client and GDB server running. However, the client is not connected to the server. At the moment it knows nothing about our Nucleo (or board of your choice). We have to tell it. In the terminal, your prompt should now be the "(gdb)". Enter:

help target

It will give you a list. Notice that the one we want is target extended-remote - Use a remote computer via a serial line.

But we also have to give it the location. So, at the (gdb) prompt, enter:

(gdb)  target extended-remote localhost:4242

You should get back a response something like so:

(gdb) target extended-remote localhost:4242
Remote debugging using localhost:4242
0x080028e4 in ?? ()

Meanwhile, at the terminal running the st-util gdbserver, we got this:

2018-10-20T18:42:30 INFO gdb-server.c: Found 6 hw breakpoint registers
2018-10-20T18:42:30 INFO gdb-server.c: GDB connected.

Step 12: Let's Repeat, With Windows and Flash Our Program

The steps to running the st-util gdbserver, and the arm-none-eabi-gdb client are essentially the same as we did during the previous Step. You open two terminals (cygwin, DOS cmd, or Windows Powershell), find the location of the st-util, run it. In the other terminal, run the arm-none-eabi-gdb client. The only difference is that the -tui (terminal-based text view) mode is most likely not supported.

If the above worked in Windows, then you'll probably have to stop (just the client). At this point, somehow you'll need to run the GDB client where your build file is ("core.out"), or add the entire path to that file as an argument to the GDB client.

I simplified my life by using cygwin and creating links from my local $HOME//bin directory to where both of those tools reside.

Ok, we've compiled and linked just as before, and we have file main.elf ready to be flashed.

We have st-util running in one window. We re-start the GDB client, this time we do:

arm-none-eabi-gdb main.elf

We let it start up, wait for the (gdb) prompt, do our same connection command to the GDB server (st-util), and we are ready to flash the executable. It's very anti-climatic:

(gdb) load

Running with cygwin terminals, there's a known issue with sometime console commands do not output. So in our case, the window running the server was completely silent. The one running the client, where we ran the load, output this:

Loading section .text, size 0x1c lma 0x8000000
Start address 0x8000000, load size 28 Transfer rate: 1 KB/sec, 28 bytes/write.


Step 13: Flashing With Linux - More Rewarding :D

Step 14: Let's Dive a Little Deeper

If you got to here, excellent. Let's move on.

Why not look inside of the main.elf file, the executable? Run the following:

arm-none-eabi-objdump -d main.elf

You should see an output something like this:

main.elf: file format elf32-littlearm

Disassembly of section .text:

08000000 <vtable>: 8000000: 00 40 01 20 09 00 00 08 .@. ....

08000008 <reset_handler>: 8000008: 4802 ldr r0, [pc, #8] ; (8000014 <main_loop+0x4>) 800000a: 4685 mov sp, r0 800000c: 4f02 ldr r7, [pc, #8] ; (8000018 <main_loop+0x8>) 800000e: 2000 movs r0, #0

08000010 <main_loop>: 8000010: 3001 adds r0, #1 8000012: e7fd b.n 8000010 <main_loop> 8000014: 20014000 .word 0x20014000 8000018: deadbeef .word 0xdeadbeef

What little nuggets can we get from the above output?

If you remember back when we discussing and creating the linker.script.ld file, we stated that these ARM devices have RAM starting at 0x20000000, and that FLASH memory starts at 0x08000000.

Thus, we can see that indeed the program is such that it all resides in FLASH memory.

Then, above, but a later Step, when we were disussing the "Hello World" portion, there was a statement where we load an immediate, constant, literal value ("0xDEADBEEF") into a MCU core register ("R7").

The statement was :

LDR R7, =0xDEADBEEF

In our code, that is the only place where we even mention DEADBEEF. No where else. And yet, if you look at the above disassembled/reconstructed instructions, etc, there's more there related to DEADBEEF than we thought we did.

So, the compiler/linker somehow decided to permanently flash the value of DEADBEEF into a FLASH address, at location 0x8000018. And then, the compiler changed our above LDR instruction to be:

LDR R7,[PC, #8]

It even generated a comment for us. How nice. And it tells us to take the current program counter value (the PC register), add 0x8 to that value, and that's where DEADBEEF has been burned, and get that value and stuff it in R7.

So that also means that the program counter (PC) was pointing to address 0x8000010, which is the start of the main_loop, and that the DEADBEEF value sits at two addresses after the end of main_loop.

Step 15: Finally, a Brief Look at the Program Running

Even if you quit GDB, just re-enter the command. You don't even have to give it any file; we're not flashing anymore, just running it.

Once you've re-connected the GDB client to the GDB server, at the (gdb) command prompt:

(gdb) info registers

You should see something like this:

r0             0x0	0
r1             0x0	0
r2             0x0	0
r3             0x0	0
r4             0x0	0
r5             0x0	0
r6             0x0	0
r7             0x0	0
r8             0x0	0
r9             0x0	0
r10            0x0	0
r11            0x0	0
r12            0x0	0
sp             0x20014000	0x20014000
lr             0xffffffff	4294967295
pc             0x8000008	0x8000008
cpsr           0x1000000	16777216

But then, at the (gdb) prompt, enter:

(gdb) continue

And very quickly hit CTRL-C. That should pause the program. Enter the "info registers" command again.

This time, it looks different:

(gdb) info registers
r0             0x350ffa	3477498
r1             0x0	0
r2             0x0	0
r3             0x0	0
r4             0x0	0
r5             0x0	0
r6             0x0	0
r7             0xdeadbeef	3735928559
r8             0x0	0
r9             0x0	0
r10            0x0	0
r11            0x0	0
r12            0x0	0
sp             0x20014000	0x20014000
lr             0xffffffff	4294967295
pc             0x8000010	0x8000010
cpsr           0x1000000	16777216

What happened? Exactly what we wanted. DEADBEEF was loaded into R7, and R0 has been (extremely fast) incrementing. If you repeat, you'll see R0 again with another value.

Step 16: We Wanted to Create a Read-Only Array in Flash

One way to create the equivalent of an array using assembly and directives, is as follows:

.type myarray, %object	// the name or label 'myarray' is defined as an object type.
myarray:		// this is the start of the declaration of 'myarray'
			// (what it will consist of).
	.word	0x11111111	//the first member or value contained in 'myarray'.
	.word	0x22222222	//the second value (contiguous addresses).
	.word	0x33333333	//and so on.
.size myarray, .-myarray	// the compiler/assembler now knows where the end or
				// boundary of 'myarray'.

Now that we have set it up in FLASH memory, we can use it in the program. Below is a portion:

LDR R1,myarray // this loads data contained at 1st location of 'myarray'.'
// this is not what we want.

LDR R1,=myarray // this loads the location value itself (the 1st address), // not the data.. // this IS what we want.

MOV R2,#0 // R2 will keep a count to make sure we don't walk off // end of array. LDR R3,=myarrsize // R3 will be the equivalent of 'myarrsize'.

// R0 will hold our data

main_loop: LDR R0,[R1] // Load the data pointed to by R1 ('myarray') into R0. CMP R2,R3 // Are we at limit of array? BEQ main_loop // If we are, we're done, so we'll just loop forever.

ADD R2,#1 // Otherwise, we can keep iterating through array.

ADD R1,#4 // Add 4 to register R1, so it points correctly to next // address..

B main_loop // Loop back.

The video goes through all this, and there is a bug in it. It's good; it shows that it's important run and debug code. It shows a classic case of walking off the end of an array.