Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 451
Gordon Williams Avatar asked May 01 '14 16:05

Gordon Williams


2 Answers

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

like image 175
Gordon Williams Avatar answered Nov 15 '22 12:11

Gordon Williams


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.

like image 42
Chester Gillon Avatar answered Nov 15 '22 11:11

Chester Gillon