3
\$\begingroup\$

I'm attempting to program a Cortex M3 based microcontroller over SWD, more or less from first principles. I've written an SWD interface library and have it running on a second micro.

I've largely been following this blog post: http://markdingst.blogspot.ie/2014/03/programming-internal-sram-over-swd.html

As well as this SiLabs application note: https://www.silabs.com/Support%20Documents/TechnicalDocs/AN0062.pdf

I can halt the core and read and write SRAM. I'm wondering how to obtain the bare-metal code that I can write to SRAM. Can I just take the .hex file the compiler outputs and write that to SRAM word by word starting at 0x20000000?

And if so, what core registers do I have to configure so that the new program in SRAM will run on reset? Will setting the PC to 0x20000000 be enough?

I appreciate all and any help! Thanks!

\$\endgroup\$
2
\$\begingroup\$

If you have a linker script which links your executable to an appropriate address for ram, then you could interpret the hex file and extract the payload bytes.

But it would probably be easier to use arm-none-eabi-objcopy, ie

arm-none-eabi-objcopy -O binary myproject.elf myproject.bin

This will give you a raw binary file that you can write to the chip starting at the base address you linked for. Note that in doing things this way the base address is not preserved in the file itself - you will have to make sure you keep that consistent between the file creation and the file usage. Still, this is a fairly common choice - many simple flashing programs only understand raw binaries, with the destination address given explicitly on the command line.

To fully run from RAM, you need a chip that lets your relocate the vector table there; some do and some do not. If you can do that in a way that survives reset and have the reset vector in the table pointed to your code in RAM it may work. Or your could write a table pointing the reset vector at RAM to the usual location in flash. Keep in mind that the use of resetting to RAM is a bit limited, as there normally would be no program there to execute, unless something put it there. And the something can do a little cleanup and just branch to the starting address.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the response, converting to .bin was indeed the step I was missing. I used the hex2bin tool to do this, and for now I'm content with using the flash controller to write this binary file to flash at address 0. I'll look into writing to SRAM again at a later stage I think, and your description of doing so will be very useful to me! \$\endgroup\$ – D_s Aug 17 '16 at 10:18
2
\$\begingroup\$

I can certainly appreciate the roll your own first principles thing, and applaud that. But, I also recommend walking before you run. Just because you want to do it all yourself doesnt mean you cant nor should not take advantage of what is there.

You are on the right track, maybe with your tools or maybe with other folks tools, or maybe using an instruction set simulator. You need to master (well get good enough) using a toolchain or assembler if you dont want to write in C or write your own assembler for which I would say go for it but use a working assembler and disassembler as a reference/validation.

The cortex-m is a pseudo harvard architecture. You have to read the docs for your particular part. Ideally the program space and data space dont touch, are on separate interfaces, but with these parts you can download a program to ram and just run it. But it is chip specific, just recently dealt with this. The fetch side might use a different address to reach the ram as the data side. A ti msp432 cortex-m4 I am playing with the ram is at 0x20000000 but doesnt seem that you can run code there, that same sram is mapped at 0x01000000 and you can read-write either address space with data transactions, but have to execute at 0x01000000 for it to work.

So once you figure that out, and once you figure out what you need to do write a program and assemble it, then yes it is a "simple" matter of downloading it and then starting execution with the program counter set to the entry point address.

Depending on what part you are using there may be different ways to get in, uart based bootloaders, swd, usb. Some dev boards show up as removable flash drives and you simply copy your .bin file over and a front end mcu loads that into the back end mcu for you and resets it.

As to the state of the machine and what your program needs to do that is up to you, you are not going to be able to cover every possible situation (hung hardware), so first thing is to start easy and assume you just reset by actually resetting, then depending on how you want to proceed you may have to undo everything that any prior program has done. Generally mcu firmware is in flash and the system boots the same way every time so it is basically a known quantity. And the assumption here is that you are using ram to develop a program that will ultimately be in flash and not have to deal with infinite possible startup conditions.

The cortex-m boots in a way that you have the option to set the stack pointer in the vector table and then the vector table takes care of setting the pc and then your code takes if from there. You can choose to have your loader set the stack pointer and the program counter, or you can choose to have your program set the stack pointer. Beyond that setting the sp should get you going. Note that when using the bx instruction (or pop or some other short list of possible instructions) the lsbit of the address needs to be set to indicate you are switching into or remaining in thumb mode. That bit is stripped by the instruction the program counter doesnt actually have that bit set, and it might not be possible to set anyway, so 0x20000000 or 0x01000000 should be the correct address not 0x20000001 nor 0x01000001 IMO.

EDIT

Using gnu tools

sram.s

.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
    ldr r0,stacktop
    mov sp,r0
    bl notmain
    b hang

.thumb_func
hang:   b .

.align
stacktop: .word 0x20001000

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr
.end

blinker01.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );

void notmain ( void )
{
    while(1)
    {
        PUT32(0x20000800,GET32(0x20000800)+1);
    }
}

sram.ld

MEMORY
{
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : { *(.bss*) } > ram
}

Makefile

ARMGNU = arm-none-eabi
#ARMGNU = arm-linux-gnueabi
AOPS = --warn --fatal-warnings -mcpu=cortex-m0
COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mcpu=cortex-m0
all : blinker01.gcc.thumb.sram.bin

clean:
    rm -f *.bin
    rm -f *.o
    rm -f *.elf
    rm -f *.list
    rm -f *.hex
    rm -f *.srec

sram.o : sram.s
    $(ARMGNU)-as $(AOPS) sram.s -o sram.o

blinker01.gcc.thumb.o : blinker01.c
    $(ARMGNU)-gcc $(COPS) -mthumb -c blinker01.c -o blinker01.gcc.thumb.o

blinker01.gcc.thumb.sram.bin : sram.ld sram.o blinker01.gcc.thumb.o
    $(ARMGNU)-ld -o blinker01.gcc.thumb.sram.elf -T sram.ld sram.o blinker01.gcc.thumb.o
    $(ARMGNU)-objdump -D blinker01.gcc.thumb.sram.elf > blinker01.gcc.thumb.sram.list
    $(ARMGNU)-objcopy blinker01.gcc.thumb.sram.elf blinker01.gcc.thumb.sram.hex -O ihex
    $(ARMGNU)-objcopy blinker01.gcc.thumb.sram.elf blinker01.gcc.thumb.sram.srec -O srec --srec-forceS3
    $(ARMGNU)-objcopy blinker01.gcc.thumb.sram.elf blinker01.gcc.thumb.sram.bin -O binary

A simple asm example is...simpler...went ahead and added a small bit of C. For an stm32 you can for example load and run this at 0x20000000 as shown in the linker script sram.ld. On another chip (ti msp432 I happen to know you might need to change that origin to 0x01000000 and load there).

This produces a number of formats for the binary, each are relatively easy to parse, for example an srec:

S0200000626C696E6B657230312E6763632E7468756D622E7372616D2E7372656332
S315200000000248854600F008F8FFE7FEE700100020CA
S31520000010016070470068704710B50448FFF7FAFF83
S31520000020411C0248FFF7F4FFF7E7C046000800200E
S70520000000DA

(hint, this is the real srec, you can extract this and try to load and run it)

and use that to load data into your chip over swd and then run it. It even gives you an entry point in the srec. elf, ihex also do this. the raw binary image file you just read and shove in.

This particular example you can load and then stop check 0x20000800 and see what value it is, then resume and then stop again and see that it is changing. if you need much simpler test programs, say several nops then an infinite loop, you can see if your program is loading and running and you can halt and examine registers. change the number of nops and see if the pc changes the next time or have it modify registers and see. From the makefile, etc you should be able to see that you can have your entire program in sram.s and no need for the C compiler.

The code above works with either variant the none-eabi or the linux-gnueabi.

EDIT2

When I build for flash I use a slightly different bootstrap

.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 .

You can in theory have one do both, by choosing your entry point, for example you could have the reset code set the stack pointer even though it doesnt need to since it is in the vector table, but you could have the reset handler do it, and then figure out where that reset handler is and when loaded to sram enter at that offset, when flash it just boots right. Of course you would have to build position independent, I prefer to have to bootstraps, then build a flash version and an sram version using a different linker script for each and different bootstrap program which only varies in the above _start and reset, the rest being the same.

\$\endgroup\$
  • \$\begingroup\$ Note that (as I unpleasantly discovered) the startup/init business in the GCC toolchain goes back and writes the whole vector again at the start of the program, so even if you have your .hex and write it to an arbitrary location, the startup vector will immediately get overwritten when you jump into your code! I dealt with that by finding the #define that was telling the startup code what to load into the vectors and fixing it. \$\endgroup\$ – Daniel Aug 15 '16 at 17:24
  • \$\begingroup\$ depends on how you use the toolchain, you can own the vector table and control everything, you dont have to use the vector tables nor bootstrap code provided with the gnu tools. it is all up to you. can easily have code that you can simply branch into or set the program counter. \$\endgroup\$ – old_timer Aug 15 '16 at 18:37
  • \$\begingroup\$ if you are using swd you can have it just run without doing a vector based reset. I dont yet know the swd protocol that well, but have used openocd on several of these cortex-m chips and it does work. so if they can do it you can do it too. \$\endgroup\$ – old_timer Aug 15 '16 at 18:39
  • \$\begingroup\$ It wasn't readily apparent how to rip that out of the setup code without making a big mess. \$\endgroup\$ – Daniel Aug 15 '16 at 19:39
  • 1
    \$\begingroup\$ added examples to the answer \$\endgroup\$ – old_timer Aug 15 '16 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.