Question

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

Was it helpful?

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

OTHER TIPS

I found that by setting FAULTMASK bit, which disables all interrupts and fault handlers disabled, at the same time as setting the BFHFNMIGN bit it is possible to probe if accessing an address will generate a bus fault without raising an exception. The benefit of not raising the exception means that then you don't have to write an exception handler. The following example is a C function which does a probe:

/**
 * @brief Probe an address to see if can be read without generating a bus fault
 * @details This function must be called with the processor in privileged mode.
 *          It:
 *          - Clear any previous indication of a bus fault in the BFARV bit
 *          - Temporarily sets the processor to Ignore Bus Faults with all interrupts and fault handlers disabled
 *          - Attempt to read from read_address, ignoring the result
 *          - Checks to see if the read caused a bus fault, by checking the BFARV bit is set
 *          - Re-enables Bus Faults and all interrupts and fault handlers
 * @param[in] read_address The address to try reading a byte from
 * @return Returns true if no bus fault occurred reading from read_address, or false if a bus fault occurred.
 */
bool read_probe (volatile const char *read_address)
{
    bool address_readable = true;

    /* Clear any existing indication of a bus fault - BFARV is write one to clear */
    HWREG (NVIC_FAULT_STAT) |= NVIC_FAULT_STAT_BFARV;

    HWREG (NVIC_CFG_CTRL) |= NVIC_CFG_CTRL_BFHFNMIGN;
    asm volatile ("  CPSID f;");
    *read_address;
    if ((HWREG (NVIC_FAULT_STAT) & NVIC_FAULT_STAT_BFARV) != 0)
    {
        address_readable = false;
    }
    asm volatile ("  CPSIE f;");
    HWREG (NVIC_CFG_CTRL) &= ~NVIC_CFG_CTRL_BFHFNMIGN;

    return address_readable;
}

The code was written for the Texas Instruments ARM compiler, using the Texas Instruments TivaWare software for the register definitions and was tested on Cortex-M4F based TM4C devices. In theory, it should work on other Cortex-M3 or Cortex-M4 devices.

One related problem with running the same binary on MCU with different SRAM sizes - the stack pointer (SP) is initialized at reset with the value in location 0. Typically when you build a program, the compiler/startup file/linker script put the value of the TOP of SRAM (suitably aligned) into location 0. So when your binary is put on a machine with more SRAM, the stack now no longer sits at the top of SRAM. Your program must relocate the stack to the real top of SRAM once you locate it if you want to take advantage of the larger SRAM size.

There should be a register other than the flash size, a part id that should tell you which part you are using, and from there look up a table of ram sizes. Or if nothing else from the flash size can you get a range of possible ram sizes that came with that chipid and flash size? choose the smallest. Not sure what kind of code for a platform like this would need additional ram or a variable sized ram for the application. And pretty quickly you get into a part where the peripherals vary enough to be a problem...

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.

No, you don't need to. Just compile your code for the chip with the smallest amount of RAM. The binary can be used on bigger versions without any change - excess RAM is simply left unused.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top