Search code examples
gccarmruntimeramcortex-m

Determining ARM Cortex M3 RAM Size at run time


I'm developing some software for STM32F103-based ARM microcontrollers (compiling with GCC). A few of my users want to be able to use the same binary with different versions of the same chip (that have more RAM), so I need a way of finding out how much RAM I have at runtime.

There's an address (0x1FFFF7E0) that contains the flash size, but it seems there isn't one that contains the RAM size!

The obvious solution is just to run up the memory addresses checking which ones are read/writable, but I've tried this and the chip HardFaults when reading from a RAM address that's too high (and I'm not sure how to recover).

Any thoughts on the best way to figure this out? Ideally I would do it experimentally as some chips (like the STM32F103RCT6 I'm using now) actually appear to have 64kB of RAM even though the datasheet suggests they have 48. For instance the 0x1FFFF7E0 register reports 256kB of available flash even though 512kB is usable.

It looks like I might be able to set the BFHFNMIGN bit in the CCR register, and then try and access the memory from within a software interrupt - however I have no idea how to call or create a software interrupt in GCC+STM32


Solution

  • Right, I finally figured this out with the help of users on ST's forum.

    First, you need to enable the BusFault IRQ:

    SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA;

    Then, you need to define a BusFault handler which will increment the program counter by 2 in order to skip over the offending instruction (taking a gamble that it is in fact a 2-byte instruction) :

    __attribute__ ((naked)) void BusFault_Handler(void) {
      /* NAKED function so we can be sure that SP is correct when we
       * run our asm code below */
    
      // DO NOT clear the busfault active flag - it causes a hard fault!
    
      /* Instead, we must increase the value of the PC, so that when we
       * return, we don't return to the same instruction.
       *
       * Registers are stacked as follows: r0,r1,r2,r3,r12,lr,pc,xPSR
       * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/Babedgea.html
       *
       * So we want PC - the 6th down * 4 bytes = 24
       *
       * Then we add 2 - which IS DANGEROUS because we're assuming that the op
       * is 2 bytes, but it COULD be 4.
       */
      __asm__(
          "ldr r0, [sp, #24]\n"  // load the PC
          "add r0, #2\n"         // increase by 2 - dangerous, see above
          "str r0, [sp, #24]\n"  // save the PC back
          "bx lr\n"              // Return (function is naked so we must do this explicitly)
      );
    }
    

    And now - FINALLY - we can try and read from an arbitrary memory location. If it's wrong, the BusFault handler gets called, but we skip over the read or write instruction as if it wasn't there.

    This means that it's relatively easy to write to a memory location and then read back - and if you get the same thing, you know it's valid (you just need to make sure that your code isn't fooled by having both str and ldr as no-ops).