I'm trying to build the foreign function interface library for a Cortex-M3 processor using GCC. According to http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html:
-mthumb
Generate code for the Thumb instruction set. The default is to use the 32-bit ARM instruction set. This option automatically enables either 16-bit Thumb-1 or mixed 16/32-bit Thumb-2 instructions based on the -mcpu=name and -march=name options. This option is not passed to the assembler. If you want to force assembler files to be interpreted as Thumb code, either add a `.thumb' directive to the source or pass the -mthumb option directly to the assembler by prefixing it with -Wa.
I've tried passing various various arguments to the assembler and can't seem to figure it out. Typical output as follows:
Building file: ../source/ffi/sysv.S
Invoking: GCC Assembler
arm-bare_newlib_cortex_m3_nommu-eabi-gcc -Wa,-mthumb-interwork -I"/home/neil/m3projects/robovero/firmware/include" -o"source/ffi/sysv.o" "../source/ffi/sysv.S"
../source/ffi/sysv.S: Assembler messages:
../source/ffi/sysv.S:145: Error: selected processor does not support ARM opcodes
../source/ffi/sysv.S:147: Error: attempt to use an ARM instruction on a Thumb-only processor -- `stmfd sp!,{r0-r3,fp,lr}'
...
Can I use libffi on Cortex-M3 without becoming an assembly expert?
It might be worth noting that when I invoke arm-bare_newlib_cortex_m3_nommu-eabi-as directly I get different errors.
I modify the sysV.S as follolwing, the error is caused by the ".arm" directive, when using cortex-m3, it should be comment out.
#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
#undef __THUMB_INTERWORK__
#endif
#if __ARM_ARCH__ >= 5
# define call_reg(x) blx x
#elif defined (__ARM_ARCH_4T__)
# define call_reg(x) mov lr, pc ; bx x
# if defined(__thumb__) || defined(__THUMB_INTERWORK__)
# define __INTERWORKING__
# endif
#else
# define call_reg(x) mov lr, pc ; mov pc, x
#endif
/* Conditionally compile unwinder directives. */
#ifdef __ARM_EABI__
#define UNWIND
#else
#define UNWIND #
#endif
#if defined(__thumb__) && !defined(__THUMB_INTERWORK__)
.macro ARM_FUNC_START name
.text
.align 0
.thumb
.thumb_func
#ifdef __APPLE__
ENTRY($0)
#else
ENTRY(\name)
#endif
#ifndef __ARM_ARCH_7M__ /* not cortex-m3 */
bx pc
nop
.arm
#endif
UNWIND .fnstart
/* A hook to tell gdb that we've switched to ARM mode. Also used to call
directly from other local arm routines. */
#ifdef __APPLE__
_L__$0:
#else
_L__\name:
#endif
.endm
I hate to say it but it is a porting effort. Doable, not necessarily having to be an assembler expert, but will need to learn some. Going from thumb to arm is easy, thumb2, I would have to look that up, much of thumb2 is just thumb instructions. and thumb has a one to one mapping to arm instructions, but not the other way around. Thumb mostly limits you to the lower 8 registers on all the workhorse instructions, with special versions or special instructions to use the upper registers. So many of your arm instructions are going to turn into more than one thumb instruction.
Initially see if there is a build option to build this package without using assembler or go into that directory and see if there is something you can do in the makefile to use a C program instead of assembler. I assume there is a serious performance issue to using C which is why there is assembler to start with. Thumb2 in theory is more efficient than arm but that does not necessarily mean a direct port from arm to thumb2. So with some experience you may be able to hand port to thumb2 and keep some performance.
EDIT:
Downloaded the file in question. The define stuff up front implies that it is aware of both thumb and armv7m. is that how you are getting to where you were changing stm to push?
The assembler is telling you the truth - ARM assembly code can't be assembled to work successfully on a Thumb-2-only processor like the M3. There are no way for the assembler to map the ARM instruction mnemonics into opcodes that will make sense to a Cortex-M3. You'll need to port the assembly files to Thumb-2 assembly code to get things working. Depending on what the original assembly code does, you might get lucky and be able to port to C instead, but that may cost you a major performance hit.
Add "-Wa,-mimplicit-it=thumb" to the gcc CFLAGS to avoid "thumb conditional instruction should be in IT block" error
--- libffi.orig/src/arm/sysv.S
+++ libffi/src/arm/sysv.S
## -91,6 +91,10 ##
# define __ARM_ARCH__ 7
#endif
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+#undef __THUMB_INTERWORK__
+#endif
+
#if __ARM_ARCH__ >= 5
# define call_reg(x) blx x
#elif defined (__ARM_ARCH_4T__)
## -121,9 +125,11 ##
#else
ENTRY(\name)
#endif
+#ifndef __ARM_ARCH_7M__ /* not cortex-m3 */
bx pc
nop
.arm
+#endif
UNWIND .fnstart
/* A hook to tell gdb that we've switched to ARM mode. Also used to call
directly from other local arm routines. */
## -164,6 +170,10 ## _L__\name:
#endif
.endm
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+ .syntax unified
+#endif
+
# r0: ffi_prep_args
# r1: &ecif
# r2: cif->bytes
## -180,7 +190,11 ## ARM_FUNC_START ffi_call_SYSV
UNWIND .setfp fp, sp
# Make room for all of the new args.
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+ sub sp, sp, r2
+#else
sub sp, fp, r2
+#endif
# Place all of the ffi_prep_args in position
mov r0, sp
## -193,7 +207,12 ## ARM_FUNC_START ffi_call_SYSV
ldmia sp, {r0-r3}
# and adjust stack
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+ mov lr, sp
+ sub lr, fp, lr # cif->bytes == fp - sp
+#else
sub lr, fp, sp # cif->bytes == fp - sp
+#endif
ldr ip, [fp] # load fn() in advance
cmp lr, #16
movhs lr, #16
## -305,7 +324,13 ## ARM_FUNC_START ffi_closure_SYSV
beq .Lretlonglong
.Lclosure_epilogue:
add sp, sp, #16
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+ ldr ip, [sp, #4]
+ ldr sp, [sp]
+ mov pc, ip
+#else
ldmfd sp, {sp, pc}
+#endif
.Lretint:
ldr r0, [sp]
b .Lclosure_epilogue
## -381,7 +406,12 ## LSYM(Lbase_args):
ldmia sp, {r0-r3}
# and adjust stack
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+ mov lr, sp
+ sub lr, ip, lr # cif->bytes == (fp - 64) - sp
+#else
sub lr, ip, sp # cif->bytes == (fp - 64) - sp
+#endif
ldr ip, [fp] # load fn() in advance
cmp lr, #16
movhs lr, #16
## -469,7 +499,13 ## ARM_FUNC_START ffi_closure_VFP
.Lclosure_epilogue_vfp:
add sp, sp, #72
+#ifdef __ARM_ARCH_7M__ /* cortex-m3 */
+ ldr ip, [sp, #4]
+ ldr sp, [sp]
+ mov pc, ip
+#else
ldmfd sp, {sp, pc}
+#endif
.Lretfloat_vfp:
flds s0, [sp]
Related
I am working on an ARMv7 (Cortex-A7) system, and I want to read CPSR from C file in either ARM mode or THUMB mode.
Firstly, I used the embedded ASSEMBLY instruction in C function as follows,
__asm__ volatile("mrs %0, CPSR\n" : "=r"(regval));
When I compiled the C file with -mthumb and ran the code with GDB, it showed that the regval is 0x60000010 which is NOT the 0x60000030 shown by GDB!
So how to write a function to read CPSR in either ARM or THUMB mode?
Updated with compiling option
a) Build the code with following command line to specify the THUMB mode.
arm-linux-gnueabi-gcc -g2 backtrace.c -mcpu=cortex-a7 -static -mthumb -o tbacktrace
Run tbacktrace with qemu and GDB, I got different value as,
(gdb) p/x regval
$7 = 0x60000010
(gdb) p/x $cpsr
$8 = 0x60000030
The question is why my mrs %0, CPSR\n showd CPSR is ARM mode, instead of THUMB mode which the code is built.
b) When build the code with command line (not specify -mcpu=cortex-a7),
arm-linux-gnueabi-gcc -g2 backtrace.c -mthumb -o tbacktrace
there reported the following error.
$ arm-linux-gnueabi-gcc -g2 backtrace.c -mthumb -o tbacktrace
/tmp/ccOg2tlo.s: Assembler messages:
/tmp/ccOg2tlo.s:2256: Error: selected processor does not support `mrs r3,CPSR' in Thumb mode
/tmp/ccOg2tlo.s:2398: Error: selected processor does not support `mrs r3,CPSR' in Thumb mode
c) Build the code without -mcpu or -mthumb, the code can be built and ran well.
So I think there should be some other ways to get right CPSR in both ARM and THUMB modes.
Updated with more assembly codes.
arm-linux-gnueabi-objdump -M force-thumb -d a.elf shows following,
4000014c: 0ff0 lsrs r0, r6, #31
4000014e: e92d 0f30 stmdb sp!, {r4, r5, r8, r9, sl, fp}
40000152: ee30 3407 cdp 4, 3, cr3, cr0, cr7, {0}
40000156: e210 b.n 4000057a <__aeabi_f2d+0x16>
40000158: 3ba3 subs r3, #163 ; 0xa3
4000015a: e1a0 b.n 4000049e <__adddf3+0x1f6>
4000015c: 001b movs r3, r3
4000015e: 0a00 lsrs r0, r0, #8
40000160: a000 add r0, pc, #0 ; (adr r0, 40000164 <B_Loop1>)
40000162: e3a0 b.n 400008a6 <__udivmoddi4+0x19a>
......
400002a8 <__adddf3>:
400002a8: b530 push {r4, r5, lr}
400002aa: ea4f 0441 mov.w r4, r1, lsl #1
400002ae: ea4f 0543 mov.w r5, r3, lsl #1
400002b2: ea94 0f05 teq r4, r5
400002b6: bf08 it eq
400002b8: ea90 0f02 teqeq r0, r2
400002bc: bf1f itttt ne
Here is a part of code of the project, which is built with -mthumb -mcpu=cortex-a7.
As Nate and Frant mentioned, I think the code is running in THUMB mode, and checking Tbit of CPSR to detect ARM or THUMB mode is un-necessary, is it correct?
A way to detect THUMB or ARM mode
After reading Nate's and Frant's comments, I had an idea to detect which mode the CPU is not by reading Tbit of CPSR. The idea is by reading PC register two times, and check the difference. If it is 2 (length of THUMB instruction), CPU is running in THUMB mode, if it is 4 (length of ARM instruction), CPU is in ARM mode.
The code is as follows,
register uint32_t pc1, pc2;
asm volatile("mov %0, pc\n mov %1, pc" : "=r"(pc1), "=r"(pc2));
I built the code with and without -mthumb, with -Os, the code seems to be able to detect the THUMB or ARM mode.
CPSR.c:
#include <stdint.h>
int main(int argc, char* argv[]) {
uint32_t regval;
asm volatile("mrs %0, CPSR" : "=r"(regval));
return regval;
}
If you don't use -mcpu=cortex-a7, your compiler will default to another CPU:
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -O0 -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -S CPSR.c
cat CPSR.s
.cpu arm7tdmi
.arch armv4t
The ARM7TDMI-S was introduced in 2001, and, as pointed out by your compiler, does not seem to support mrs r3,CPSR in Thumb mode. Therefore, you must specify -mcpu=cortex-a7:
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -mcpu=cortex-a7 -O0 -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -S CPSR.c
cat CPSR.s
.cpu cortex-a7
.arch armv7-a
CPU and architecture are now as expected.
Testing your code on real hardware - a Cortex-A7 running u-boot - in Arm and Thumb mode:
Arm:
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -O0 -mcpu=cortex-a7 -marm -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -o CPSR-arm.elf CPSR.c
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000080800000
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objcopy -O srec CPSR-arm.elf CPSR-arm.srec
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objdump -j .text -D CPSR-arm.elf
CPSR-arm.elf: file format elf32-littlearm
Disassembly of section .text:
80800000 <main>:
80800000: e52db004 push {fp} ; (str fp, [sp, #-4]!)
80800004: e28db000 add fp, sp, #0
80800008: e24dd01c sub sp, sp, #28
8080000c: e50b0010 str r0, [fp, #-16]
80800010: e50b1014 str r1, [fp, #-20] ; 0xffffffec
80800014: e50b2018 str r2, [fp, #-24] ; 0xffffffe8
80800018: e10f3000 mrs r3, CPSR
8080001c: e50b3008 str r3, [fp, #-8]
80800020: e51b3008 ldr r3, [fp, #-8]
80800024: e1a00003 mov r0, r3
80800028: e28bd000 add sp, fp, #0
8080002c: e49db004 pop {fp} ; (ldr fp, [sp], #4)
80800030: e12fff1e bx lr
I.MX7d running u-boot:
# loads
## Ready for S-Record download ...
## First Load Addr = 0x80800000
## Last Load Addr = 0x80800033
## Total Size = 0x00000034 = 52 Bytes
CACHE: Misaligned operation at range [80800000, 80800034]
## Start Addr = 0x80800000
# go 0x80800000
## Starting application at 0x80800000 ...
## Application terminated, rc = 0x200000D3
Thumb:
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc -O0 -mcpu=cortex-a7 -mthumb -nostartfiles -nostdlib -Wl,--section-start=.text=0x80800000 -o CPSR-thumb.elf CPSR.c
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000080800000
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objcopy -O srec CPSR-thumb.elf CPSR-thumb.srec
/opt/arm/10/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-objdump -j .text -D CPSR-thumb.elf
CPSR-thumb.elf: file format elf32-littlearm
Disassembly of section .text:
80800000 <main>:
80800000: b480 push {r7}
80800002: b087 sub sp, #28
80800004: af00 add r7, sp, #0
80800006: 60f8 str r0, [r7, #12]
80800008: 60b9 str r1, [r7, #8]
8080000a: 607a str r2, [r7, #4]
8080000c: f3ef 8300 mrs r3, CPSR
80800010: 617b str r3, [r7, #20]
80800012: 697b ldr r3, [r7, #20]
80800014: 4618 mov r0, r3
80800016: 371c adds r7, #28
80800018: 46bd mov sp, r7
8080001a: bc80 pop {r7}
8080001c: 4770 bx lr
I.MX7d running u-boot:
# loads
## Ready for S-Record download ...
## First Load Addr = 0x80800000
## Last Load Addr = 0x8080001D
## Total Size = 0x0000001E = 30 Bytes
CACHE: Misaligned operation at range [80800000, 8080001e]
## Start Addr = 0x80800000
#
# go 0x80800001
## Starting application at 0x80800001 ...
## Application terminated, rc = 0x200000D3
Bottom-line, both versions returned the same value for CPSR, i.e. 0x200000D3.
To the question
How to write a function to read ARM CPSR in either ARM or THUMB mode?
The answer would then be: The way you did.
Asking why p/x regval and p/x $cpsr are not returning the same value should be the topic for a different question, may be on the GDB forum.
Update #1: Nate Eldredge explained why the value read into the register has always the T bit set to zero.
Testing on a different Cortex-A7 (Allwinner H3), a JLink probe and the Ozone debugger, we can see that even though the value read by the MRS instruction is 0x200000D3, the value of CPSR_USR read by the JTAG probe and Ozone is 0x200001F3 when executing the Thumb version, and 0x200000D3 when executing the Arm version:
Arm:
Thumb:
This would I.M.H.O. perfectly validate his explanation.
Update #2
Still using the JLink debug probe, but in combination with JLinkGDBServerExe and arm-none-eabi-gdb 12.1 in TUI mode:
Arm:
Thumb:
The value for the CPSR register read by the JTAG probe is the one you would expect, i.e. has the Tbit set in Thumb mode.
You probably would get the same result in Linux using a TRACE32 JTAG probe.
Not sure this could be useful, but note that some pre-defined symbols differ when building an Arm or Thumb executable:
/opt/arm/11/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -dM -E -mcpu=cortex-a7 -marm - < /dev/null | grep -i arm
#define __ARM_SIZEOF_WCHAR_T 4
#define __ARM_FEATURE_SAT 1
#define __ARM_ARCH_ISA_ARM 1
#define __ARMEL__ 1
#define __ARM_FEATURE_IDIV 1
#define __ARM_SIZEOF_MINIMAL_ENUM 1
#define __ARM_FEATURE_LDREX 15
#define __ARM_PCS 1
#define __ARM_FEATURE_QBIT 1
#define __ARM_ARCH_PROFILE 65
#define __ARM_32BIT_STATE 1
#define __ARM_FEATURE_CLZ 1
#define __ARM_ARCH_ISA_THUMB 2
#define __ARM_ARCH 7
#define __ARM_FEATURE_UNALIGNED 1
#define __arm__ 1
#define __ARM_ARCH_7A__ 1
#define __ARM_FEATURE_SIMD32 1
#define __ARM_FEATURE_COPROC 15
#define __ARM_FEATURE_DSP 1
#define __ARM_ARCH_EXT_IDIV__ 1
#define __ARM_EABI__ 1
/opt/arm/11/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -dM -E -mcpu=cortex-a7 -mthumb - < /dev/null | grep -i thumb
#define __thumb2__ 1
#define __THUMB_INTERWORK__ 1
#define __thumb__ 1
#define __ARM_ARCH_ISA_THUMB 2
#define __THUMBEL__ 1
You could therefore use #ifdef __arm__ and #ifdef __thumb2__ statements in your code in order to know if you are executing the Arm or the Thumb version.
The instruction is working as designed and documented.
The discrepancy is in bit 5, which according to the ARMv7-A Architecture Reference Manual, is the T bit, indicating whether the processor is in Thumb state. It's one of the "execution state bits". Lower down on that page, under "Accessing the execution state bits", it says:
The execution state bits, other than the E bit, are RAZ [read as zero] when read by an MRS instruction.
So mrs rN, CPSR masks off those bits. I'm not sure why it's designed this way. But in principle you should already know whether you're in Thumb state or not, so it shouldn't really be necessary to read this information from CPSR.
On the other hand, gdb doesn't get its CPSR value from mrs rN, CPSR. I haven't checked, but I presume what happens is this: when your program hits a breakpoint, an exception is generated. This causes CPSR to be saved into SPSR (without masking any bits!), and the kernel's exception handler retrieves it from there to store as part of the saved context of your process, along with register values, etc. The saved context is made available to the debugger via appropriate system calls (e.g. ptrace(2)) and that's how it is able to display register contents and such. In particular, it gets the CPSR value that was saved at the breakpoint and which isn't masked.
I'm building a tiny microcontroller with only the bare essentials for self-educational purposes. This way, I can refresh my knowledge about topics like the linkerscript, the startup code, ...
EDIT:
I got quite a lot of comments pointing out that the "absolute minimal STM32-application" shown below is no good. You are absolutely right when noticing that the vector table is not complete, the .bss-section is not taken care of, the peripheral addresses are not complete, ... Please allow me to explain why.
It has never been the purpose of the author to write a complete and useful application in this particular chapter. His purpose was to explain step-by-step how a linkerscript works, how startup code works, what the boot procedure of an STM32 looks like, ... purely for educational purposes. I can appreciate this approach, and learned a lot.
The example I have put below is taken from the middle of the chapter in question. The chapter keeps adding more parts to the linkerscript and startup code (for example initialization of .bss-section) as it goes forward.
The reason I put files here from the middle of his chapter, is because I got stuck at a particular error message. I want to get that fixed before continuing.
The chapter in question is somewhere at the end of his book. It is intended for the more experienced or curious reader who wants to gain deeper knowledge about topics most people don't even consider (most people use the standard linkerscript and startup code given by the manufacturer without ever reading it).
Keeping this in mind, please let us focus on the technical issue at hand (as described below in the error messages). Please also accept my sincere apologies that I didn't clarify the intentions of the writer earlier. But I've done it now, so we can move on ;-)
1. Absolute minimal STM32-application
The tutorial I'm following is chapter 20 from this book: "Mastering STM32" (https://leanpub.com/mastering-stm32). The book explains how to make a tiny microcontroller application with two files: main.c and linkerscript.ld. As I'm not using an IDE (like Eclipse), I also added build.bat and clean.bat to generate the compilation commands. So my project folder looks like this:
Before I continue, I should perhaps give some more details about my system:
OS: Windows 10, 64-bit
Microcontroller: NUCLEO-F401RE board with STM32F401RE microcontroller.
Compiler: arm-none-eabi-gcc version 6.3.1 20170620 (release) [ARM/embedded-6-branch revision 249437].
The main file looks like this:
/* ------------------------------------------------------------ */
/* Minimal application */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
typedef unsigned long uint32_t;
/* Memory and peripheral start addresses (common to all STM32 MCUs) */
#define FLASH_BASE 0x08000000
#define SRAM_BASE 0x20000000
#define PERIPH_BASE 0x40000000
/* Work out end of RAM address as initial stack pointer
* (specific of a given STM32 MCU) */
#define SRAM_SIZE 96*1024 //STM32F401RE has 96 KB of RAM
#define SRAM_END (SRAM_BASE + SRAM_SIZE)
/* RCC peripheral addresses applicable to GPIOA
* (specific of a given STM32 MCU) */
#define RCC_BASE (PERIPH_BASE + 0x23800)
#define RCC_APB1ENR ((uint32_t*)(RCC_BASE + 0x30))
/* GPIOA peripheral addresses
* (specific of a given STM32 MCU) */
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_MODER ((uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR ((uint32_t*)(GPIOA_BASE + 0x14))
/* Function headers */
int main(void);
void delay(uint32_t count);
/* Minimal vector table */
uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t*)SRAM_END, // initial stack pointer (MSP)
(uint32_t*)main // main as Reset_Handler
};
/* Main function */
int main() {
/* Enable clock on GPIOA peripheral */
*RCC_APB1ENR = 0x1;
/* Configure the PA5 as output pull-up */
*GPIOA_MODER |= 0x400; // Sets MODER[11:10] = 0x1
while(1) { // Always true
*GPIOA_ODR = 0x20;
delay(200000);
*GPIOA_ODR = 0x0;
delay(200000);
}
}
void delay(uint32_t count) {
while(count--);
}
The linkerscript looks like this:
/* ------------------------------------------------------------ */
/* Linkerscript */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
/* Memory layout for STM32F401RE */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
}
/* The ENTRY(..) directive overrides the default entry point symbol _start.
* Here we define the main-routine as the entry point.
* In fact, the ENTRY(..) directive is meaningless for embedded chips,
* but it is informative for debuggers. */
ENTRY(main)
SECTIONS
{
/* Program code into FLASH */
.text : ALIGN(4)
{
*(.isr_vector) /* Vector table */
*(.text) /* Program code */
*(.text*) /* Merge all .text.* sections inside the .text section */
KEEP(*(.isr_vector)) /* Don't allow other tools to strip this off */
} >FLASH
_sidata = LOADADDR(.data); /* Used by startup code to initialize data */
.data : ALIGN(4)
{
. = ALIGN(4);
_sdata = .; /* Create a global symbol at data start */
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .; /* Define a global symbol at data end */
} >SRAM AT >FLASH
}
The build.bat file calls the compiler on main.c, and next the linker:
#echo off
setlocal EnableDelayedExpansion
echo.
echo ----------------------------------------------------------------
echo. )\ ***************************
echo. ( =_=_=_=^< ^| * build NUCLEO-F401RE *
echo. )( ***************************
echo. ""
echo.
echo.
echo. Call the compiler on main.c
echo.
#arm-none-eabi-gcc main.c -o main.o -c -MMD -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O0 -g3 -Wall -fmessage-length=0 -Werror-implicit-function-declaration -Wno-comment -Wno-unused-function -ffunction-sections -fdata-sections
echo.
echo. Call the linker
echo.
#arm-none-eabi-gcc main.o -o myApp.elf -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -specs=nosys.specs -specs=nano.specs -T linkerscript.ld -Wl,-Map=output.map -Wl,--gc-sections
echo.
echo. Post build
echo.
#arm-none-eabi-objcopy -O binary myApp.elf myApp.bin
arm-none-eabi-size myApp.elf
echo.
echo ----------------------------------------------------------------
The clean.bat file removes all the compiler output:
#echo off
setlocal EnableDelayedExpansion
echo ----------------------------------------------------------------
echo. __ **************
echo. __\ \___ * clean *
echo. \ _ _ _ \ **************
echo. \_`_`_`_\
echo.
del /f /q main.o
del /f /q main.d
del /f /q myApp.bin
del /f /q myApp.elf
del /f /q output.map
echo ----------------------------------------------------------------
Building this works. I get the following output:
C:\Users\Kristof\myProject>build
----------------------------------------------------------------
)\ ***************************
( =_=_=_=< | * build NUCLEO-F401RE *
)( ***************************
""
Call the compiler on main.c
Call the linker
Post build
text data bss dec hex filename
112 0 0 112 70 myApp.elf
----------------------------------------------------------------
2. Proper startup code
Maybe you have noticed that the minimal application didn't have proper startup code to initialize the global variables in the .data-section. Chapter 20.2.2 .data and .bss Sections initialization from the "Mastering STM32" book explains how to do this.
As I follow along, my main.c file now looks like this:
/* ------------------------------------------------------------ */
/* Minimal application */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
typedef unsigned long uint32_t;
/* Memory and peripheral start addresses (common to all STM32 MCUs) */
#define FLASH_BASE 0x08000000
#define SRAM_BASE 0x20000000
#define PERIPH_BASE 0x40000000
/* Work out end of RAM address as initial stack pointer
* (specific of a given STM32 MCU) */
#define SRAM_SIZE 96*1024 //STM32F401RE has 96 KB of RAM
#define SRAM_END (SRAM_BASE + SRAM_SIZE)
/* RCC peripheral addresses applicable to GPIOA
* (specific of a given STM32 MCU) */
#define RCC_BASE (PERIPH_BASE + 0x23800)
#define RCC_APB1ENR ((uint32_t*)(RCC_BASE + 0x30))
/* GPIOA peripheral addresses
* (specific of a given STM32 MCU) */
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_MODER ((uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR ((uint32_t*)(GPIOA_BASE + 0x14))
/* Function headers */
void __initialize_data(uint32_t*, uint32_t*, uint32_t*);
void _start (void);
int main(void);
void delay(uint32_t count);
/* Minimal vector table */
uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t*)SRAM_END, // initial stack pointer (MSP)
(uint32_t*)_start // _start as Reset_Handler
};
/* Variables defined in linkerscript */
extern uint32_t _sidata;
extern uint32_t _sdata;
extern uint32_t _edata;
volatile uint32_t dataVar = 0x3f;
/* Data initialization */
inline void __initialize_data(uint32_t* flash_begin, uint32_t* data_begin, uint32_t* data_end) {
uint32_t *p = data_begin;
while(p < data_end)
*p++ = *flash_begin++;
}
/* Entry point */
void __attribute__((noreturn,weak)) _start (void) {
__initialize_data(&_sidata, &_sdata, &_edata);
main();
for(;;);
}
/* Main function */
int main() {
/* Enable clock on GPIOA peripheral */
*RCC_APB1ENR = 0x1;
/* Configure the PA5 as output pull-up */
*GPIOA_MODER |= 0x400; // Sets MODER[11:10] = 0x1
while(dataVar == 0x3f) { // Always true
*GPIOA_ODR = 0x20;
delay(200000);
*GPIOA_ODR = 0x0;
delay(200000);
}
}
void delay(uint32_t count) {
while(count--);
}
I've added the initialization code just above the main(..) function. The linkerscript has also some modification:
/* ------------------------------------------------------------ */
/* Linkerscript */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
/* Memory layout for STM32F401RE */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
}
/* The ENTRY(..) directive overrides the default entry point symbol _start.
* In fact, the ENTRY(..) directive is meaningless for embedded chips,
* but it is informative for debuggers. */
ENTRY(_start)
SECTIONS
{
/* Program code into FLASH */
.text : ALIGN(4)
{
*(.isr_vector) /* Vector table */
*(.text) /* Program code */
*(.text*) /* Merge all .text.* sections inside the .text section */
KEEP(*(.isr_vector)) /* Don't allow other tools to strip this off */
} >FLASH
_sidata = LOADADDR(.data); /* Used by startup code to initialize data */
.data : ALIGN(4)
{
. = ALIGN(4);
_sdata = .; /* Create a global symbol at data start */
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .; /* Define a global symbol at data end */
} >SRAM AT >FLASH
}
The little application doesn't compile anymore. Actually, the compilation from main.c to main.o is still okay. But the linking process gets stuck:
C:\Users\Kristof\myProject>build
----------------------------------------------------------------
)\ ***************************
( =_=_=_=< | * build NUCLEO-F401RE *
)( ***************************
""
Call the compiler on main.c
Call the linker
c:/gnu_arm_embedded_toolchain/bin/../lib/gcc/arm-none-eabi/6.3.1/../../../../arm-none-eabi/lib/thumb/v7e-m/fpv4-sp/hard/crt0.o: In function `_start':
(.text+0x64): undefined reference to `__bss_start__'
c:/gnu_arm_embedded_toolchain/bin/../lib/gcc/arm-none-eabi/6.3.1/../../../../arm-none-eabi/lib/thumb/v7e-m/fpv4-sp/hard/crt0.o: In function `_start':
(.text+0x68): undefined reference to `__bss_end__'
collect2.exe: error: ld returned 1 exit status
Post build
arm-none-eabi-objcopy: 'myApp.elf': No such file
arm-none-eabi-size: 'myApp.elf': No such file
----------------------------------------------------------------
3. What I've tried
I've omitted this part, otherwise this question gets too long ;-)
4. Solution
#berendi provided the solution. Thank you #berendi! Apparently I need to add the flags -nostdlib and -ffreestanding to gcc and the linker. The build.bat file now looks like this:
#echo off
setlocal EnableDelayedExpansion
echo.
echo ----------------------------------------------------------------
echo. )\ ***************************
echo. ( =_=_=_=^< ^| * build NUCLEO-F401RE *
echo. )( ***************************
echo. ""
echo.
echo.
echo. Call the compiler on main.c
echo.
#arm-none-eabi-gcc main.c -o main.o -c -MMD -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O0 -g3 -Wall -fmessage-length=0 -Werror-implicit-function-declaration -Wno-comment -Wno-unused-function -ffunction-sections -fdata-sections -ffreestanding -nostdlib
echo.
echo. Call the linker
echo.
#arm-none-eabi-gcc main.o -o myApp.elf -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -specs=nosys.specs -specs=nano.specs -T linkerscript.ld -Wl,-Map=output.map -Wl,--gc-sections -ffreestanding -nostdlib
echo.
echo. Post build
echo.
#arm-none-eabi-objcopy -O binary myApp.elf myApp.bin
arm-none-eabi-size myApp.elf
echo.
echo ----------------------------------------------------------------
Now it works!
In his answer, #berendi also gives a few interesting remarks about the main.c file. I've applied most of them:
Missing volatile keyword
Empty loop
Missing Memory Barrier (did I put the memory barrier in the correct place?)
Missing delay after RCC enable
Misleading symbolic name (apparently it should be RCC_AHB1ENR instead of RCC_APB1ENR).
The vector table: this part I've skipped. Right now I don't really need a HardFault_Handler, MemManage_Handler, ... as this is just a tiny test for educational purposes.
Nevertheless, I did notice that #berendi put a few interesting modifications in the way he declares the vector table. But I'm not entirely grasping what he's doing exactly.
The main.c file now looks like this:
/* ------------------------------------------------------------ */
/* Minimal application */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
typedef unsigned long uint32_t;
/**
\brief Data Synchronization Barrier
\details Acts as a special kind of Data Memory Barrier.
It completes when all explicit memory accesses before this instruction complete.
*/
__attribute__((always_inline)) static inline void __DSB(void)
{
__asm volatile ("dsb 0xF":::"memory");
}
/* Memory and peripheral start addresses (common to all STM32 MCUs) */
#define FLASH_BASE 0x08000000
#define SRAM_BASE 0x20000000
#define PERIPH_BASE 0x40000000
/* Work out end of RAM address as initial stack pointer
* (specific of a given STM32 MCU) */
#define SRAM_SIZE 96*1024 //STM32F401RE has 96 KB of RAM
#define SRAM_END (SRAM_BASE + SRAM_SIZE)
/* RCC peripheral addresses applicable to GPIOA
* (specific of a given STM32 MCU) */
#define RCC_BASE (PERIPH_BASE + 0x23800)
#define RCC_AHB1ENR ((volatile uint32_t*)(RCC_BASE + 0x30))
/* GPIOA peripheral addresses
* (specific of a given STM32 MCU) */
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_MODER ((volatile uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR ((volatile uint32_t*)(GPIOA_BASE + 0x14))
/* Function headers */
void __initialize_data(uint32_t*, uint32_t*, uint32_t*);
void _start (void);
int main(void);
void delay(uint32_t count);
/* Minimal vector table */
uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t*)SRAM_END, // initial stack pointer (MSP)
(uint32_t*)_start // _start as Reset_Handler
};
/* Variables defined in linkerscript */
extern uint32_t _sidata;
extern uint32_t _sdata;
extern uint32_t _edata;
volatile uint32_t dataVar = 0x3f;
/* Data initialization */
inline void __initialize_data(uint32_t* flash_begin, uint32_t* data_begin, uint32_t* data_end) {
uint32_t *p = data_begin;
while(p < data_end)
*p++ = *flash_begin++;
}
/* Entry point */
void __attribute__((noreturn,weak)) _start (void) {
__initialize_data(&_sidata, &_sdata, &_edata);
asm volatile("":::"memory"); // <- Did I put this instruction at the right spot?
main();
for(;;);
}
/* Main function */
int main() {
/* Enable clock on GPIOA peripheral */
*RCC_AHB1ENR = 0x1;
__DSB();
/* Configure the PA5 as output pull-up */
*GPIOA_MODER |= 0x400; // Sets MODER[11:10] = 0x1
while(dataVar == 0x3f) { // Always true
*GPIOA_ODR = 0x20;
delay(200000);
*GPIOA_ODR = 0x0;
delay(200000);
}
}
void delay(uint32_t count) {
while(count--){
asm volatile("");
}
}
PS: The book "Mastering STM32" from Carmine Noviello is an absolute masterpiece. You should read it! => https://leanpub.com/mastering-stm32
You can tell gcc not to use the library.
The Compiler
By default, gcc assumes that you are using a standard C library, and can emit code that calls some functions. For example, when optimizations are enabled, it detects loops that copy a piece of memory, and may substitute them with a call to memcpy(). Disable it with -ffreestanding.
The Linker
The linker assumes as well that you want to link your program with the C library and startup code. The library startup code is responsible for initializing the library and the program execution environment. It has a function named _start() which has to be called after reset. One of its functions is to fill the .bss segment (see below) with zero. If the symbols that delimit .bss are not defined, then _startup() cannot be linked. Had you named your startup function anything else but _startup(), then the library startup would have been siletly dropped by the linker as an unused function, and the code could have been linked.
You can tell the linker not to link any standard library or startup code with -nostdlib, then the library supplied startup function name would not conflict with yours, and you would get a linker error every time you accidentally invoked a library function.
Missing volatile
Your register definitions are missing the volatile qualifier. Without it, subsequent writes to *GPIOA_ODR will be optimized out. The compiler will move this "invariant code" out of the loop. Changing the type in the register definitions to (volatile uint32_t*) would fix that.
Empty loop
The optimizer can recognize that the delay loop does nothing, and eliminate it completely to speed up execution. Add an empty but non-removable asm volatile(""); instruction to the delay loop.
Missing Memory Barrier
You are initializing the .data section that holds dataVar in a C function. The *p in __initialize_data() is effectively an alias for dataVar, and the compiler has no way to know it. The optimizer could theoretically rearrange the test of dataVar before __initialize_data(). Even if dataVar is volatile, *p is not, therefore ordering is not guaranteed.
After the data initialization loop, you should tell the compiler that program variables are changed by a mechanism unknown to the compiler:
asm volatile("":::"memory");
It's an old-fashioned gcc extension, the latest C standards might have defined a portable way to do this (which is not recognized by older gcc versions).
Missing delay after RCC enable
The Errata is saying,
A delay between an RCC peripheral clock enable and the effective peripheral enabling should be taken into account in order to manage the peripheral read/write to registers.
This delay depends on the peripheral mapping:
• If the peripheral is mapped on AHB: the delay should be equal to 2 AHB cycles.
• If the peripheral is mapped on APB: the delay should be equal to 1 + (AHB/APB prescaler) cycles.
Workarounds
Use the DSB instruction to stall the Cortex®-M4 CPU pipeline until the instruction is completed.
Therefore, insert a
__DSB();
after *RCC_APB1ENR = 0x1; (which should be called something else)
Misleading symbolic name
Although the address for enabling GPIOA in RCC seems to be correct, the register is called RCC_AHB1ENR in the documentation. It will confuse people trying to understand your code.
The Vector Table
Although technically you can get away with having only a stack pinter and a reset handler in it, I'd too recommend having a few more entries, at least the fault handlers for simple troubleshooting.
__attribute__ ((section(".isr_vector"),used))
void (* const _vectors[]) (void) = {
(void (*const)(void))(&__stack),
Reset_Handler,
NMI_Handler,
HardFault_Handler,
MemManage_Handler,
BusFault_Handler,
UsageFault_Handler
}
The Linker Script
At the bare minimum, it must define a section for your vector table, and the code. A program must have a start address and some code, static data is optional. The rest depends on what kind of data your program is using. You could technically omit them from the linker script if there are no data of a particular type.
.rodata: read-only data, const arrays and structs go here. They remain in flash. (simple const variables are usually put in the code)
.data: initialized variables, everything you declare with an = sign, and without const.
.bss: variables that should be zero-initialized in C, i.e. global and static ones.
As you don't need .rodata or .bss now, it's fine.
Linker scripts in general are an artform, they are their own programming language and gnu's are certainly a bit of a nightmare. Divide the task into figuring out the linker script from making a working binary, once you can see the linker script is doing what you want then make the bootstrap code to use it. Take advantage of the toolchain.
The example the author used was derived from code written specifically to be used as baremetal examples that maximize success. Avoided common language and toolchain issues, yet be portable across many versions of the toolchain and to be easily ported to other toolchains (minimal reliance on the toolchain, in particular the linker script which leads to the bootstrap). The author of the book used that code but added risk to it to not be as reliable of an example.
Avoiding .data specifically and not relying on .bss to be zeroed when you write baremetal code goes a very long way toward long term success.
It was also modified such that optimization would prevent that code from working (well blinking at a rate you can see).
An example somewhat minimal linker script for binutils that you can modify to work toward .data and .bss initialization looks generically like this
test.ld
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
__data_rom_start__ = .;
.data : {
__data_start__ = .;
*(.data*)
} > ted AT > bob
__data_end__ = .;
__data_size__ = __data_end__ - __data_start__;
.bss : {
__bss_start__ = .;
*(.bss*)
} > ted
__bss_end__ = .;
__bss_size__ = __bss_end__ - __bss_start__;
}
(note memory names dont have to be rom or ram or flash or data or whatever bob is program space and ted is memory btw, change the addresses as desired)
How you see what is going on is you can link with a simple example or with your code, you need some .data and some .bss (and some .text).
vectors.s
.thumb
.globl _start
_start:
.word 0x20001000
.word reset
.thumb_func
reset:
bl notmain
b .
.globl bss_start
bss_start: .word __bss_start__
.globl bss_end
bss_end: .word __bss_end__
.word __bss_size__
.globl data_rom_start
data_rom_start:
.word __data_rom_start__
.globl data_start
data_start:
.word __data_start__
.globl data_end
data_end:
.word __data_end__
.word __data_size__
so.c
unsigned int a=1;
unsigned int b=2;
unsigned int c;
unsigned int d;
unsigned int e;
unsigned int notmain ( void )
{
return(a+b+c+d+e);
}
build
arm-none-eabi-as vectors.s -o vectors.o
arm-none-eabi-gcc -O2 -c -mthumb so.c -o so.o
arm-none-eabi-ld -T test.ld vectors.o so.o -o vectors.elf
arm-none-eabi-objdump -D vectors.elf
The code so far is not specific to arm-none-whatever or arm-linux-whatever versions of the toolchain. If/when you need gcclib items you can use gcc instead of ld but you have to be careful when doing that...or provide the path to libgcc and use ld.
What we get from this code is linker script debugging on the cheap:
Disassembly of section .text:
00008000 <_start>:
8000: 20001000 andcs r1, r0, r0
8004: 00008009 andeq r8, r0, r9
00008008 <reset>:
8008: f000 f810 bl 802c <notmain>
800c: e7fe b.n 800c <reset+0x4>
0000800e <bss_start>:
800e: 0000a008 andeq sl, r0, r8
00008012 <bss_end>:
8012: 0000a014 andeq sl, r0, r4, lsl r0
8016: 0000000c andeq r0, r0, ip
0000801a <data_rom_start>:
801a: 00008058 andeq r8, r0, r8, asr r0
0000801e <data_start>:
801e: 0000a000 andeq sl, r0, r0
00008022 <data_end>:
8022: 0000a008 andeq sl, r0, r8
8026: 00000008 andeq r0, r0, r8
...
We care about the 32 bit values being created the andeq disassembly is because the disassembler is trying to disassemble those values as instructions which they are not. The reset instructions are real the rest is 32 bit values we are generating. might be able to use readelf, but getting used to disassembling, insuring the vector table is correct as step one, which is easy to see in the disassembly. Using the disassembler as a habit can then lead to using it as above to show you what the linker generated.
If you dont get the linker script variables right you wont be able to write a successful bootstrap, if you dont have a good way to see what the linker is producing you will fail on a regular basis.
Yes, you could have exposed them in C and not assembly, the toolchain would still help you there.
You can work toward this now that you can see what the linker is doing:
.thumb
.globl _start
_start:
.word 0x20001000
.word reset
.thumb_func
reset:
ldr r0,=__bss_start__
ldr r1,=__bss_size__
# zero this
ldr r0,=__data_rom_start__
ldr r1,=__data_start__
ldr r2,=__data_size__
# copy this
bl notmain
b .
giving something like this
00008000 <_start>:
8000: 20001000 andcs r1, r0, r0
8004: 00008009 andeq r8, r0, r9
00008008 <reset>:
8008: 4803 ldr r0, [pc, #12] ; (8018 <reset+0x10>)
800a: 4904 ldr r1, [pc, #16] ; (801c <reset+0x14>)
800c: 4804 ldr r0, [pc, #16] ; (8020 <reset+0x18>)
800e: 4905 ldr r1, [pc, #20] ; (8024 <reset+0x1c>)
8010: 4a05 ldr r2, [pc, #20] ; (8028 <reset+0x20>)
8012: f000 f80b bl 802c <notmain>
8016: e7fe b.n 8016 <reset+0xe>
8018: 0000a008 andeq sl, r0, r8
801c: 0000000c andeq r0, r0, ip
8020: 00008058 andeq r8, r0, r8, asr r0
8024: 0000a000 andeq sl, r0, r0
8028: 00000008 andeq r0, r0, r8
0000802c <notmain>:
802c: 4b06 ldr r3, [pc, #24] ; (8048 <notmain+0x1c>)
802e: 6818 ldr r0, [r3, #0]
8030: 685b ldr r3, [r3, #4]
8032: 18c0 adds r0, r0, r3
If you then align the items in the linker script the copy/zero code gets even simpler you can stick to 1 to some number N whole registers rather than dealing with bytes or halfwords, can use ldr/str, ldrd/strd (if available) or ldm/stm (and not need ldrb/strb nor ldrh/strh), tight simple few line loops to complete the job.
I highly recommend you do not use C for your bootstrap.
Note that the ld linker script variables are very sensitive to position (inside or outside curly braces)
The above linker script is somewhat typical of what you will find in stock linker scripts a defined start and end, sometimes the size is computed in the linker script sometimes the bootstrap code computes the size or the bootstrap code can just loop until the address equals the end value, depends on the overall system design between the two.
Your specific issue BTW is you linked in two bootstraps, at the time I wrote this I dont see your command line(s) in the question so that would tell us more. That is why you are seeing the bss_start, etc, things that you didnt put in your linker script but are often found in stock ones that come with a pre-built toolchain (similar to the above but more complicated)
It could be by using gcc instead of ld and without the the various -nostartfiles options (that it pulled in crt0.o), just try ld instead of gcc and see what changes. You would have failed with the original example had it been something like this though so I dont think that is the issue here. If you used the same command lines the failure should have been on both examples not just the latter.
The book you're reading has led you astray. Discard it and start learning from another source.
I see at least four major problems with what it has told you to do:
The linker script and _start function you included is missing a number of important sections, and will either malfunction or fail to link many executables. Most notably, it lacks any handling for BSS (zero-filled) sections.
The vector table in main.c is beyond "minimal"; it lacks the required definitions for even the standard ARM interrupt vectors. Without these, debugging hardfaults will become very difficult, as the microcontroller will treat random code following the vector table as an interrupt vector when a fault occurs, which will probably lead to a secondary fault as it fails to load code from that "address".
The startup functions given by your book bypass the libc startup functions. This will cause some portions of the standard C library, as well as any C++ code, to fail to work correctly.
You are defining peripheral addresses yourself in main.c. These addresses are all defined in standard ST header files (e.g. <stm32f4xx.h>), so there is no need to define them yourself.
As a starter, I would recommend that you refer to the startup code provided by ST in any of their examples. These will all include a complete linker script and startup code.
As old_timer hinted in the comments, using gcc to link is a problem.
If you change the linker call in your batch file to use ld, it links without error. Try the following:
echo.
echo. Call the linker
echo.
#arm-none-eabi-ld main.o -o myApp.elf -T linkerscript.ld
I am trying to upload this simple assembly program:
.global _start
.text
reset: b _start
undefined: b undefined
software_interrupt: b software_interrupt
prefetch_abort: b prefetch_abort
data_abort: b data_abort
nop
interrupt_request: b interrupt_request
fast_interrupt_request: b fast_interrupt_request
_start:
mov r0, #0
mov r1, #1
increase:
add r0, r0, r1
cmp r0, #10
bne increase
decrease:
sub r0, r0, r1
cmp r0, #0
bne decrease
b increase
stop: b stop
to my LPC4088 (I am using Embedded artists LPC4088 QSB) via SEGGER's JLink so I could later debug it using GDB.
First I compiled my sources with all the debugging symbols using GCC toolchain:
arm-none-eabi-as -g -gdwarf-2 -o program.o program.s
arm-none-eabi-ld -Ttext=0x0 -o program.elf program.o
arm-none-eabi-objcopy -O binary program.elf program.bin
But uploading binary program.bin to LPC4088 was unsuccessful. Then user #old_timer reminded me in the comments that LPC4088's boot ROM does a checksum test after every reset like described on a page 876 of LPC4088 user manual:
So I mad sure my binary would pass a checksum test by following steps described here. So I first created a C source file checksum.c:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv) {
int fw, count, crc;
char buf[28];
fw = open(argv[1], O_RDWR);
// read fist 28 bytes
read(fw, &buf, 28);
// find 2's complement of entries 0 to 6
for (count=0, crc=0; count < 7; count++) {
crc += *((int*)(buf+count*4));
}
crc = (~crc) + 1;
// write it at offset 0x0000001C
lseek(fw, 0x0000001C, SEEK_SET);
write(fw, &crc, 4);
close(fw);
return 0;
}
compiled it using gcc -o checksum.bin checksum.c and then I fed it the original program.bin as an argument like this ./checksum.bin program.bin. So I got a modified program.bin which really had a value at 0x1C modified! Here is the comparison of the original:
and the modified version:
So the value at 0x1C was modified from 0xFEFFFFEA to 0x0400609D. This is all that was modified as can be seen from the images.
I then opened terminal application JLinkExe which presented a prompt. In the prompt I:
powered on my board using power on,
connected to the LPC4088 using command connect,
halted the MCPU using command h,
erased entire FLASH memory using command erase,
uploaded my modified binary to FLASH loadbin program.bin 0x0,
set the program counter to start at the beginning SetPC 0x4.
started stepping into the program using s.
When I started stepping into the program in first step I got some errors as can be seen at the end of the procedure inside JLinkExe prompt:
SEGGER J-Link Commander V6.30a (Compiled Jan 31 2018 18:14:21)
DLL version V6.30a, compiled Jan 31 2018 18:14:14
Connecting to J-Link via USB...O.K.
Firmware: J-Link V9 compiled Jan 29 2018 15:41:50
Hardware version: V9.30
S/N: 269300437
License(s): FlashBP, GDB
OEM: SEGGER-EDU
VTref = 3.293V
Type "connect" to establish a target connection, '?' for help
J-Link>connect
Please specify device / core. <Default>: LPC4088
Type '?' for selection dialog
Device>
Please specify target interface:
J) JTAG (Default)
S) SWD
TIF>
Device position in JTAG chain (IRPre,DRPre) <Default>: -1,-1 => Auto-detect
JTAGConf>
Specify target interface speed [kHz]. <Default>: 4000 kHz
Speed>
Device "LPC4088" selected.
Connecting to target via JTAG
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
Scanning AP map to find all available APs
AP[1]: Stopped AP scan as end of AP map has been reached
AP[0]: AHB-AP (IDR: 0x24770011)
Iterating through AP map to find AHB-AP to use
AP[0]: Core found
AP[0]: AHB-AP ROM base: 0xE00FF000
CPUID register: 0x410FC241. Implementer code: 0x41 (ARM)
Found Cortex-M4 r0p1, Little endian.
FPUnit: 6 code (BP) slots and 2 literal slots
CoreSight components:
ROMTbl[0] # E00FF000
ROMTbl[0][0]: E000E000, CID: B105E00D, PID: 000BB00C SCS-M7
ROMTbl[0][1]: E0001000, CID: B105E00D, PID: 003BB002 DWT
ROMTbl[0][2]: E0002000, CID: B105E00D, PID: 002BB003 FPB
ROMTbl[0][3]: E0000000, CID: B105E00D, PID: 003BB001 ITM
ROMTbl[0][4]: E0040000, CID: B105900D, PID: 000BB9A1 TPIU
ROMTbl[0][5]: E0041000, CID: B105900D, PID: 000BB925 ETM
Cortex-M4 identified.
J-Link>h
PC = 000001B2, CycleCnt = 825F97DB
R0 = 00000000, R1 = 20098038, R2 = 2009803C, R3 = 000531FB
R4 = 00000000, R5 = 00000000, R6 = 12345678, R7 = 00000000
R8 = 6C2030E3, R9 = 0430DB64, R10= 10000000, R11= 00000000
R12= 899B552C
SP(R13)= 1000FFF0, MSP= 1000FFF0, PSP= 6EBAAC08, R14(LR) = 00000211
XPSR = 21000000: APSR = nzCvq, EPSR = 01000000, IPSR = 000 (NoException)
CFBP = 00000000, CONTROL = 00, FAULTMASK = 00, BASEPRI = 00, PRIMASK = 00
FPS0 = 93310C50, FPS1 = 455D159C, FPS2 = 01BA3FC2, FPS3 = E851BEED
FPS4 = D937E8F4, FPS5 = 82BD7BF6, FPS6 = 8F16D263, FPS7 = B0E8C039
FPS8 = 302C0A38, FPS9 = 8007BC9C, FPS10= 9A1A276F, FPS11= 76C9DCFE
FPS12= B2FFFA20, FPS13= B55786BB, FPS14= 2175F73E, FPS15= 5D35EC5F
FPS16= 98917B32, FPS17= C964EEB6, FPS18= FEDCA529, FPS19= 1703B679
FPS20= 2F378232, FPS21= 973440E3, FPS22= 928C911C, FPS23= 20A1BF55
FPS24= 4AE3AD0C, FPS25= 4F47CC1E, FPS26= C7B418D5, FPS27= 3EAB9244
FPS28= 73C795D0, FPS29= A359C85E, FPS30= 823AEA80, FPS31= EC9CBCD5
FPSCR= 00000000
J-Link>erase
Erasing device (LPC4088)...
J-Link: Flash download: Only internal flash banks will be erased.
To enable erasing of other flash banks like QSPI or CFI, it needs to be enabled via "exec EnableEraseAllFlashBanks"
Comparing flash [100%] Done.
Erasing flash [100%] Done.
Verifying flash [100%] Done.
J-Link: Flash download: Total time needed: 3.357s (Prepare: 0.052s, Compare: 0.000s, Erase: 3.301s, Program: 0.000s, Verify: 0.000s, Restore: 0.002s)
Erasing done.
J-Link>loadbin program.bin 0x0
Downloading file [program.bin]...
Comparing flash [100%] Done.
Erasing flash [100%] Done.
Programming flash [100%] Done.
Verifying flash [100%] Done.
J-Link: Flash download: Bank 0 # 0x00000000: 1 range affected (4096 bytes)
J-Link: Flash download: Total time needed: 0.076s (Prepare: 0.056s, Compare: 0.001s, Erase: 0.000s, Program: 0.005s, Verify: 0.000s, Restore: 0.012s)
O.K.
J-Link>SetPC 0x4
J-Link>s
**************************
WARNING: T-bit of XPSR is 0 but should be 1. Changed to 1.
**************************
J-Link>s
****** Error: Failed to read current instruction.
J-Link>s
****** Error: Failed to read current instruction.
J-Link>s
****** Error: Failed to read current instruction.
J-Link>
So this code must have come from somewhere and it may be the LPC4088's Boot ROM which is remapped to 0x0 at boot time as is stated on page 907 of the LPC4088 user manual:
Do you have any idea on how to overcome this Boot ROM & checksum problem, so I could debug my program normally?
After a while I found out that warning:
**************************
WARNING: T-bit of XPSR is 0 but should be 1. Changed to 1.
**************************
is actually saying that I am trying to execute ARM instruction on a Cortex-M4 which is Thumb only! This T-bit mentioned in the warning is described on page 100 of ARMv7-M architecture reference manual:
And this is exactly what user #old_timer is saying.
You are trying to run arm instructions (0xExxxxxxxx is a big giveaway, not to mention the exception table being a lot of 0xEAxxxxxx instructions) on a cortex-m4. The cortex-m boots differently (vector table rather than executable instructions) and is thumb only (the thumb2 extensions in armv7-m are also...just thumb, dont be confused by that, what thumb2 extensions do matter but the early/original thumb is portable across all of them). So whether or not you need an additional checksum somewhere like older ARM7TDMI based NXP chips in order for the bootloader to allow the user/application code to run, you first need something that will run on the cortex-m4.
start with this, yes I know you have a cortex-m4 use cortex-m0 for now.
so.s
.cpu cortex-m0
.thumb
.thumb_func
.globl _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
# ...
.thumb_func
hang: b hang
.thumb_func
reset:
mov r1,#0
outer:
mov r0,#0xFF
inner:
nop
nop
add r1,#1
sub r0,#1
bne inner
nop
nop
b outer
build
arm-none-eabi-as so.s -o so.o
arm-none-eabi-ld -Ttext=0 so.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf -O binary so.bin
examine so.list to make sure the vector table is correct.
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 0000000f andeq r0, r0, pc
8: 0000000d andeq r0, r0, sp
0000000c <hang>:
c: e7fe b.n c <hang>
0000000e <reset>:
e: 2100 movs r1, #0
00000010 <outer>:
10: 20ff movs r0, #255 ; 0xff
00000012 <inner>:
12: 46c0 nop ; (mov r8, r8)
14: 46c0 nop ; (mov r8, r8)
16: 3101 adds r1, #1
18: 3801 subs r0, #1
1a: d1fa bne.n 12 <inner>
1c: 46c0 nop ; (mov r8, r8)
1e: 46c0 nop ; (mov r8, r8)
20: e7f6 b.n 10 <outer>
The reset entry point is 0x00E which is correctly indicated in the vector table at offset 0x4 as 0x00F. You can flash it to 0x000 and then reset and see if it works (need a debugger to stop it to see if it is stepping through that code).
To run from sram there is nothing position dependent here, so you can load the .bin as is to 0x20000000 and execute from 0x2000000E (or whatever address your toolchain ends up creating for the reset entry point).
Or you can remove the vector table
.cpu cortex-m0
.thumb
.thumb_func
reset:
mov r1,#0
outer:
mov r0,#0xFF
inner:
nop
nop
add r1,#1
sub r0,#1
bne inner
nop
nop
b outer
And link with -Ttext=0x20000000, then download to sram and start execution with the debugger at 0x20000000.
You should see r0 counting some, r1 should just keep counting forever then roll over and keep counting so if you stop it check the registers, resume, stop, etc you should see that activity.
There is a strange result while compiling Interrupt Handler for ARM GIC using Linaro gcc.
The code is:
void foo1(void) __attribute__(( interrupt("IRQ") ));
void foo2(void) __attribute__(( interrupt("IRQ") ));
void foo1() {
dummy();
return;
}
void foo2() {
return;
}
The result assembler code:
foo1:
sub lr, lr, #4
push {r0, r1, r2, r3, r4, fp, ip, lr}
add fp, sp, #28
bl dummy
nop
sub sp, fp, #28
pop {r0, r1, r2, r3, r4, fp, ip, pc}^
foo2:
str fp, [sp, #-4]!
add fp, sp, #0
nop
sub sp, fp, #0
ldr fp, [sp], #4
subs pc, lr, #4
So, SUBS is used to return from interrupt if no subroutines inside handler code and PUSH{lr}/POP{pc} is used if there is any subroutine.
The problem is that SUBS automatically switch from processor IRQ mode to SVC mode, but POP{pc} does not. So, SUBS should be used and for foo1 it is necessary to add external SUBS instruction to switch from IRQ to SVC mode.
Is it feature or bug?
Is any way to force compiler to use SUBS everytime?
I can't find out the reference, but IIRC, pop {} is equivalent to LDM sp ...
For LDM, the ARM ARM (A4.1.22 LDM (3)) states that:
^ For an LDM instruction that loads the PC, this indicates that the
SPSR of the current mode is copied to the CPSR.
That is why the generated pop instruction restores the CPSR from the SPSR, hence returning in the previous CPU mode.
I'm using Keil uVision with gcc compiler (Sourcery Codebenchlite for ARM EABI ) to program the STM32F4 cortex M4 chip.
The compiler control strings I have set are:
-march=armv7e-m -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -std=gnu99 -fsingle-precision-constant
When the debugger encounters some mathematical functions (e.g. asinf(), atan2f() etc), it stops.
I have checked that the arguments for these functions are also single-precision.
I think it is because of some missing compiler directives for the use of VFP floating point, but was unable to identify it.
Is there anything I have missed out?
The disassembly code of an example I did:
The debugger can evaluate atan2f(0.3,0.4), but stops at 0x0803B9CA when it evaluates atan2f(a,b). Didn't know why the number works but not variables.
377: float a = 0.3;
0x0803B9BA 4B1E LDR r3,[pc,#120] ; #0x0803BA34
0x0803B9BC 63BB STR r3,[r7,#0x38]
378: float b = 0.4;
379:
0x0803B9BE 4B1E LDR r3,[pc,#120] ; #0x0803BA38
0x0803B9C0 637B STR r3,[r7,#0x34]
380: float c = atan2f(0.3,0.4);
0x0803B9C2 4B1E LDR r3,[pc,#120] ; #0x0803BA3C
0x0803B9C4 633B STR r3,[r7,#0x30]
381: float d = atan2f(a,b);
382:
0x0803B9C6 6BB8 LDR r0,[r7,#0x38]
0x0803B9C8 6B79 LDR r1,[r7,#0x34]
0x0803B9CA F004F993 BL.W atan2f (0x0803FCF4)
0x0803B9CE 62F8 STR r0,[r7,#0x2C]
On the STM32F4 you first need to enable the FPU - otherwise the CPU will jump into the HardFault_Handler or BusFault_Handler (I'm not shure which one).
You can do it in C/C++ anywhere before you use floating point instructions (maybe at the beginning of main()?). Assuming you use the CMSIS library and have the core_m4.h included (maybe through stm32f4xx.h):
void cortexm4f_enable_fpu() {
/* set CP10 and CP11 Full Access */
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
}
The alternative is assembler code in the startup file:
/*enable fpu begin*/
ldr r0, =0xe000ed88 /*; enable cp10,cp11 */
ldr r1,[r0]
ldr r2, =0xf00000
orr r1,r1,r2
str r1,[r0]
/*enable fpu end*/
(I found the code somewhere on the internet, don't know where though. I used it myself, it works).
Maybe your problem is located there?