3
\$\begingroup\$

I am using the ATSAME70Q21 processor with a Cortex-M7 CPU.

My development environment is Atmel Studio 7. I am using the SAME70 XPlained demo board.

I am creating some code in assembly language.

As a test I setup a vector table at the start of flash (0x00400000) which contain the initial stack pointer value and the location of the Reset_Handler in the first 8 bytes.

When I hit reset on the debugger the Program counter gets set to 0x0040001C (which is correct). The instruction at location 0x0040001C is 0xC046 which is a NOP. The next several instructions in memory are NOPs also.

If I single step the debugger (execute the NOP) the program counter immediately jumps to the HardFault_Handler.

I don't see how executing a NOP can generate a HardFault.

What are likely reasons for a HardFault to occur immediately after reset.

I don't think its a hardware issue since I am using an off the shelf demo board and the problem doesn't occur if I use one of the example C projects in Atmel studio.

\$\endgroup\$
  • \$\begingroup\$ Why don't you take a look at the assembly code generated by your C compiler. One issue I see is that the default location for the reset vector is 0x00000004, not 0x00400000. Is your processor relocating the interrupt vectors? \$\endgroup\$ – Elliot Alderson Dec 2 '18 at 13:03
  • \$\begingroup\$ @Elliot Alderson I believe the processor is reading my vector table at reset. The reason for my belief is that following reset the PC is actually being set to the value contained in the table. The stack pointer is also being set correctly. \$\endgroup\$ – user4574 Dec 2 '18 at 18:38
  • \$\begingroup\$ @Elliot Alderson The default boot location is selectable via some bits you can set in flash. It can be set to boot from an internal ROM, or from the start of flash. \$\endgroup\$ – user4574 Dec 2 '18 at 18:47
2
\$\begingroup\$

Note that assembly language is defined by the tool not the target. For gnu assembler, to date for thumb, particularly with these cortex-ms where you need/want to get the vector table right you started with this problem:

.thumb
.globl _start
_start:
    .word 0x20001000
    .word reset
    .word loop
    .word loop
reset:
    b reset
loop:
    b loop

assemble/dissassemble and you can see the problem

Disassembly of section .text:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000010    andeq   r0, r0, r0, lsl r0
   8:   00000014    andeq   r0, r0, r4, lsl r0
   c:   00000014    andeq   r0, r0, r4, lsl r0

00000010 <reset>:
  10:   eafffffe    b   10 <reset>

00000014 <loop>:
  14:   eafffffe    b   14 <loop>

(once you know that the vector addresses need to be the address orred with 1)

.thumb
.globl _start
_start:
    .word 0x20001000
    .word reset
    .word loop
    .word loop
.thumb_func
reset:
    b reset
loop:
    b loop

the minimum but clean solution is to add .thumb_func to indicate that the next label you find is a thumb function not a generic address.

(you have to link not just assemble/disassemble)

Disassembly of section .text:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000011    andeq   r0, r0, r1, lsl r0
   8:   00000012    andeq   r0, r0, r2, lsl r0
   c:   00000012    andeq   r0, r0, r2, lsl r0

00000010 <reset>:
  10:   e7fe        b.n 10 <reset>

00000012 <loop>:
  12:   e7fe        b.n 12 <loop>

and that fixed one vector.

.thumb
.globl _start
_start:
    .word 0x20001000
    .word reset
    .word loop
    .word loop
.thumb_func
reset:
    b reset
.thumb_func
loop:
    b loop

and that fixed the other.

Disassembly of section .text:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000011    andeq   r0, r0, r1, lsl r0
   8:   00000013    andeq   r0, r0, r3, lsl r0
   c:   00000013    andeq   r0, r0, r3, lsl r0

00000010 <reset>:
  10:   e7fe        b.n 10 <reset>

00000012 <loop>:
  12:   e7fe        b.n 12 <loop>

add some C

.thumb
.globl _start
_start:
    .word 0x20001000
    .word reset
    .word loop
    .word fun
.thumb_func
reset:
    b reset
.thumb_func
loop:
    b loop

void fun ( void )
{
}

Disassembly of section .text:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000011    andeq   r0, r0, r1, lsl r0
   8:   00000013    andeq   r0, r0, r3, lsl r0
   c:   00000015    andeq   r0, r0, r5, lsl r0

00000010 <reset>:
  10:   e7fe        b.n 10 <reset>

00000012 <loop>:
  12:   e7fe        b.n 12 <loop>

00000014 <fun>:
  14:   4770        bx  lr
  16:   46c0        nop         ; (mov r8, r8)

Then look at the output of the C compiler

    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "fun.c"
    .text
    .align  1
    .p2align 2,,3
    .global fun
    .arch armv4t
    .syntax unified
    .code   16
    .thumb_func
    .fpu softvfp
    .type   fun, %function
fun:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    @ sp needed
    bx  lr
    .size   fun, .-fun
    .ident  "GCC: (GNU) 8.2.0"

(doesnt matter that I defaulted to the arm7, but there is .thumb_func)

some assembly languages will have directives like proc or function or other similar things that you use to declare a label as a function rather than just a generic address to something, so when trying other assemblers for arm you may run into that.

if this uses the gnu assembler then .thumb_func should fix it. I would in your mind think if this as an orring with one not an adding with one in case the address is correct you might end up breaking it with the just add one to everything notion.

Same goes for function pointer stuff in C you have to be careful and can run into this.

void loop ( void );
void hop ( unsigned int );
void fun ( void )
{
    unsigned int x;
    x = (unsigned int) loop;
    hop(x);
}
void more_fun ( void )
{
    unsigned int x;
    unsigned short z[4];
    z[0]=0x46c0;
    z[1]=0x4770;
    z[2]=0x46c0;
    z[3]=0x46c0;
    x = (unsigned int) z;
    hop(x);
}

00000012 <loop>:
  12:   e7fe        b.n 12 <loop>

00000014 <hop>:
  14:   4700        bx  r0

00000018 <fun>:
  18:   b510        push    {r4, lr}
  1a:   4803        ldr r0, [pc, #12]   ; (28 <fun+0x10>)
  1c:   f7ff fffa   bl  14 <hop>
  20:   bc10        pop {r4}
  22:   bc01        pop {r0}
  24:   4700        bx  r0
  26:   46c0        nop         ; (mov r8, r8)
  28:   00000013    andeq   r0, r0, r3, lsl r0

0000002c <more_fun>:
  2c:   b500        push    {lr}
  2e:   4b05        ldr r3, [pc, #20]   ; (44 <more_fun+0x18>)
  30:   b083        sub sp, #12
  32:   9300        str r3, [sp, #0]
  34:   4b04        ldr r3, [pc, #16]   ; (48 <more_fun+0x1c>)
  36:   4668        mov r0, sp
  38:   9301        str r3, [sp, #4]
  3a:   f7ff ffeb   bl  14 <hop>
  3e:   b003        add sp, #12
  40:   bc01        pop {r0}
  42:   4700        bx  r0
  44:   477046c0    
  48:   46c046c0    

not that you are going to run code like that but you can see the problem.

and a fix

void more_fun ( void )
{
    unsigned int x;
    unsigned short z[4];
    z[0]=0x46c0;
    z[1]=0x4770;
    z[2]=0x46c0;
    z[3]=0x46c0;
    x = (unsigned int) z;
    hop(x|1);
}

0000002c <more_fun>:
  2c:   b500        push    {lr}
  2e:   4b06        ldr r3, [pc, #24]   ; (48 <more_fun+0x1c>)
  30:   b083        sub sp, #12
  32:   9300        str r3, [sp, #0]
  34:   4b05        ldr r3, [pc, #20]   ; (4c <more_fun+0x20>)
  36:   4668        mov r0, sp
  38:   9301        str r3, [sp, #4]
  3a:   2301        movs    r3, #1
  3c:   4318        orrs    r0, r3
  3e:   f7ff ffe9   bl  14 <hop>
  42:   b003        add sp, #12
  44:   bc01        pop {r0}
  46:   4700        bx  r0
  48:   477046c0 
  4c:   46c046c0 

what you may very well do is write a bootloader and to get the hop into the loaded code right you need to solve it there are use a function pointer things you can try and maybe not write some asm, but I recommend the asm and orr with one, more likely to get it to work.

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

In the ARM architecture machine instructions can never reside at an odd address. The least significant address bit of all jump instructions or vector tables would therefore be 0. Rather than just having a constant 0 bit, the designers chose to reuse address bit 0 to indicate the instruction set encoding for a jump/branch target. A 0 indicates that the target is 32-bit ARM code and a 1 indicates that the target is Thumb code.

It appears that the problem was that the values in the vector table were even (indicating to the processor that the vectors pointed to 32-bit ARM code). But the ARM core in the ATSAME70Q21 supports the Armv7-M ISA (which is a Thumb instruction set). The assembler was generating thumb instructions but jumping to that code with the T bit clear caused a HardFault (even when executing a NOP).

I noticed the problem while looking at ARM document ID120114 page B1-573 which shows the Execution Program Status Register(EPSR) register. The T bit (bit-24) indicates weather the processor is in the Thumb mode (1) or 32-bit ARM mode (0).

My assembly code originally looked like...

 .section .vectors
 vec_table:
    .word _estack
    .word Reset_Handler
...
.section .text
Reset_Handler:
    NOP
    NOP

In order to ensure that address bit 0 was set in the vector table I changed it to...

 .section .vectors
 vec_table:
    .word _estack
    .word Reset_Handler + 0x00000001
...
.section .text
Reset_Handler:
    NOP
    NOP

Now everything works.

Apparently the C programs worked fine because the compiler was already doing this. But in my assembly programs I need to do this manually.

\$\endgroup\$
  • \$\begingroup\$ you should avoid using a plus when you want a logical or. If/when the tools get the address right then you are in trouble again. With gnu assembler you put .thumb_func on a line in front of Reset_Handler: in this case and it will put the proper address, but this is not gnu assembler right it is another toolchain (assembly is defined by the tool not the target). There is likely, well there is, syntax that your assembler uses to get this right. \$\endgroup\$ – old_timer Dec 3 '18 at 3:16
  • \$\begingroup\$ good lesson though before downloading a new build/project to an mcu should always check the disassembly/image to see that it was built right so you dont brick the mcu. \$\endgroup\$ – old_timer Dec 3 '18 at 3:16
  • \$\begingroup\$ @old_timer I believe it is GNU. Thanks I didn't know about the thumb_func assembly directive. \$\endgroup\$ – user4574 Dec 3 '18 at 15:38

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.