4
votes

I'm trying to learn to use the cortex m0 processor. I have a stm32f0 development board which lets me view each bit of each address and upload a new binary file easily. I've been reading a lit of manuals learning about a lot of rules and features, but still have no idea where the program counter starts on reset, what type of argument it is expecting, nor do I even know how to write things like add or str/ldr in binary form. Is this basic knowledge left out of the manuals i've read?

It says m0 has a full descending stack, yet it seems to suggest the starting point is at the other end (0x00000000). If a vector table could be explained in laymen's terms too that'd be great.

2
Instruction encodings can be found in the Architecture Reference Manual (a.k.a ARM ARM - freely available, but you have to sign up to accept the license).Notlikethat
Thank you! Hopefully I can get a blinking light program successfully written tonight :DNathan Darker

2 Answers

6
votes

The full sized arms (cortex-A and the like) address 0x00000000 for example is the reset instruction that is executed itself which is a bit strange often you see a list of addresses but that is how they did it. For the cortex-m they not only use a list of addresses but the hardware design conforms enough to the EABI as to allow you to put C function names in the table and not have to have a small amount of assembly (other than the vector table itself).

So for example using gnu assembler.

;@-----------------------
.cpu cortex-m0
.thumb
;@-----------------------

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang

.thumb_func
reset:
    bl notmain
    b hang
.thumb_func
hang:   b .

Before digging in you need to go to what is currently at infocenter.arm.com and then under architecture and then armv6-m get the armv6-m architectural reference manual (ARM ARM for the v6m). These links have been there for a long time but naturally they can change, they do call it and distinguish between the architectural reference manual and technical reference manual. The architectural reference manuals generally cover the family within that architecture. The TRM generally covers a specific core or revision number of a specific core. probably worth getting that as well elsewhere on the infocenter page.

I am looking at what I assume is rev C of the armv6m ARM ARM: ARM DDI 0419C

search for "vector table" in the document and find what may or may not still be in the same section:

Table B1-4 Vector table format

this table shows that at offset 0 in the address space is SP_main. This is the reset value of the Main stack pointer.

And then after that the word offset in the table is the Exception Number, word being 4 bytes in the arm world so exception number 1 is at offset 4 in the address space, exception 2 at 8 and so on.

this always takes me a while to find. Also in the armv6m arm arm.

B1.5.2 Exception number definition

where exception number 1 is reset 2 is nmi and so on. we care about reset.

so that means that at address 0x00000000 in ARMS ADDRESS SPACE we can if we choose pre-load the stack address, we can if we want in the bootstrap code also set the stack, but we dont have to, one place has to do it but not both.

Then at address 0x00000004 in ARMS ADDRESS SPACE we put the address of the reset handler.

So after assembling, compiling and linking the code in my example I get

Disassembly of section .text:

08000000 <_start>:
 8000000:       20001000        andcs   r1, r0, r0
 8000004:       08000041        stmdaeq r0, {r0, r6}
 8000008:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 800000c:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000010:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000014:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000018:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 800001c:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000020:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000024:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000028:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 800002c:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000030:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000034:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 8000038:       08000047        stmdaeq r0, {r0, r1, r2, r6}
 800003c:       08000047        stmdaeq r0, {r0, r1, r2, r6}

08000040 <reset>:
 8000040:       f000 f80a       bl      8000058 <notmain>
 8000044:       e7ff            b.n     8000046 <hang>

08000046 <hang>:
 8000046:       e7fe            b.n     8000046 <hang>

you can see that in the case of gnu assembler putting .thumb_func before a label makes that label a function or address that one might call thus the bx or blx instruction thus the need for setting the lsbit which the bx or blx needs to properly branch (bl doesnt care). which automagically the linker fixes the address in the vector table. reset at offset 0x40 gets a 0x41 for example.

Now why isnt this code being built for address 0x00000000?! That is because you have to then look beyond the arm documentation to the chip vendor documentation, arm does not make chips they make processor cores and some logic to support them, you go to st or nxp or ti or whomever to find the rest of the story in particular where is the bootflash in the address space. No doubt in this case the flast at address 0x08000000 in the arm address space for normal boot is mapped to 0x00000000, some chips will have multiple boot flashes and depending on straps (input pins tied high or low or in various combinations) one flash or another will get mapped into address zero forever or for some period of time.

the cortex m0 (and m1) is (are) armv6m based the cortex m3 and m4 are armv7m based. Huge difference the latter supports thumb2 extensions to the thumb instruction set (formerly undefined instructions become the first half of a two halfword instruction 32 bit instruction not to be confused with 32 bit arm mode instructions) and there are something like 150 or so new thumb2 instructions added to cortex m3 and then cortex m4 has a fraction of a floating point unit (only one floating point size, single perhaps) and all of the instructions that come with that (basically coproessor instructions re-defined). This makes life easier for the cortex-m0, only 16 bit instructions (yes the bl is actually and is defined in the docs as two separate instructions which you can encode separately from each other if you want).

Currently the armv6m ARM ARM also contains the instruction set definition

Chapter A5 The Thumb Instruction Set Encoding

Looking at

A6.7.17 CMP (immediate)

(again the section numbers in mine may not stay the same or match in the future, their docs generally dont change much or from one to the next, but you never know).

The first thing to notice is the encoding

Encoding T1 All versions of the Thumb instruction set.

this means everyone that supports thumb supports this instruction (Back from armv4 to the present)

then the syntax

CMP <Rn>,#<imm8>

which the unified syntax may be different than this documents syntax, also understand ARM has their own toolchain so the syntax if defined is specific to their assembler. Assembly language is not a standard it is defined specifically by the assembler, the program that parses it. Gnu assembler is a separate thing and does not have to conform to this document, it mostly does, but arm also started this unified syntax thing to allow some percentage of assembly language to assemble to thumb, thumb2 extensions and arm instruction sets without re-write, although you still get your hands tied very quickly if you dont specify which one of the three somewhere.

you can see in this instruction the upper bits have to be 00101 bits 15:11 that is how the processor knows this is a compare immediate. The Rn is register r0 through r7 whichever one you are using (to access r8 to r15 you have to use other mov instructions to allow for 16 bit instructions they had to keep most instructions to the lower 7 registers to save bits in the instruction encoding). and then the lower 8 bits are a straight constant value from 0 to 255 (other arm/thumb immediate encodings are not so straight forward and thumb vs arm uses different encodings so you have to just read the manuals).

I highly recommend if you want to see the encoding then write in assembly language assemble then disassemble and let the hopefully debugged toolchain do the work for you then try to reverse engineer what you see and match it to the manual. The head scratchers are the odd addresses, but those are documented (although not necessarily as you would hope) and then anything pc relative, the pc is TWO INSTRUCTIONS AHEAD whenever you do something with it, it isnt really with a pipe, but for reverse compatibility and to set a standard that is what the arm/thumb standard is two instructions ahead. so when computing or reverse engineering the computation of a pc relative address that is why you math is always 4 bytes off.

As with most processors YOU the programmer or at least the programmer you trusted to borrow their code is the one that is setting the stack pointer. You can put it where ever you want, the arm core itself, nor arm themselves have any idea what the chip vendor is going to do with implementation nor does the compiler really know or want to know about all the possible chips out there, so you the programmer has to tell the toolchain which then tells the arm processor where you want the stack to be. Traditionally with a descending stack you want to start at a high address. you start first by looking at the pop and push instructions and pseudo code to see that arm first decrements by number of registers times 4 (for a push) then writes to those addresses then adjusts the sp on the way out. So if your ram ended at say 0x2001FFFF you can safely put your stack pointer at 0x20020000 and the first thing pushed would be at 0x2001FFFC. (Well not first thing but the very bottom of the stack is there) other non-arm processors work differently and have different rules, some you cant get at the stack pointer at all, some you can but the reset value is right, and some like arms you need to worry about. The full sized arms you have multiple stack pointers to manage, also you can make the stack go up or down although I wouldnt go against the grain just because you can.

4
votes

For all Cortex-M, the first two words in the memory map (at addresses 0 and 4 respectively) should be your intial stack pointer and the address of the first instruction you'd like to start executing.

Normally you'd put the stack at the highest RAM address available, and use a linker script to poke in the address of your program's entry point at address 4.