cancel
Showing results for 
Search instead for 
Did you mean: 

H743 using RAM_D1 can not handle std:string greater than 15

NMunt
Associate II

Hello

I've been working on a project on an nucleo F411RE and everythings been going fine.

I then had to port the project to nucleo H743ZI.

As i am using the DMA i had to change the ram from DTCRAM to RAM_D1.

Before the change to RAM_D1 i could have strings greater than 15 chars, e.g.

std::string test("12345678901234567890");

Would not crash my program.

After changing to RAM_D1 the program would get to the string and then go directly to the void _exit (int status) loop.

Anyone have any ideas what might be the problem ?

1 ACCEPTED SOLUTION

Accepted Solutions
berendi
Principal

You've moved the data and heap segments from DTCM to D1, but not the stack, as the stack pointer (at the top of the linker file) still points to DTCM. Therefore the stack pointer is always lower than the top of the heap.

You have commented out the call to abort(), but the check is still in place, always reporting that there is no heap available. Your program will fail later.

Now you should give some thought to the intended memory layout. Should the stack be moved to D1 as well (so that you can use DMA on local variables on the stack)? Or should the heap rather remain in DTCM, because you'll never do DMA on heap variables? Or move everything back to DTCM, except the DMA buffers? Then you can modify _sbrk() and the linker script accordingly.

View solution in original post

9 REPLIES 9

What toolchain?

If GNU/GCC based, check the __sbrk allocator code understands multiple regions and the placement of the stack/heap. Check also that the startup.s code does it's job properly initializing all the statics correctly and in the right memory areas.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
NMunt
Associate II

Im using gcc-arm-none-eabi-7-2018-q2-update.

I'll have a look into the sbrk and the startup.

It just seems a little silly that the atollic truestudio generated project from cubemx breaks on strings when changing to ram_d1.

But since cubemx generates .c files it can not be expected to work perfectly with c++ 🙂 .

NMunt
Associate II
/*
*****************************************************************************
**
 
**  File        : stm32_flash.ld
**
**  Abstract    : Linker script for STM32H743ZI Device with
**                2048KByte FLASH, 1056KByte RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.
**
**  Target      : STMicroelectronics STM32
**
**  Environment : Atollic TrueSTUDIO(R)
**
**  Distribution: The file is distributed as is, without any warranty
**                of any kind.
**
**  (c)Copyright Atollic AB.
**  You may use this file as-is or modify it according to the needs of your
**  project. This file may only be built (assembled or compiled and linked)
**  using the Atollic TrueSTUDIO(R) product. The use of this file together
**  with other tools than Atollic TrueSTUDIO(R) is not permitted.
**
*****************************************************************************
*/
 
/* Entry Point */
ENTRY(Reset_Handler)
 
/* Highest address of the user mode stack */
_estack = 0x20020000;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */
 
/* Specify the memory areas */
MEMORY
{
DTCMRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw)      : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw)      : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw)      : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw)      : ORIGIN = 0x00000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 2048K
}
 
/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH
 
  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)
 
    KEEP (*(.init))
    KEEP (*(.fini))
 
    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH
 
  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH
 
  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH
 
  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH
 
  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);
 
  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
 
    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM_D1 /* DTCMRAM before */ AT> FLASH
 
 
  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)
 
    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM_D1 /* DTCMRAM before */
 
  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(4);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(4);
  } >RAM_D1 /* DTCMRAM before */
 
  .lwip_sec (NOLOAD) : {
    . = ABSOLUTE(0x30040000);
    *(.RxDecripSection)
 
    . = ABSOLUTE(0x30040060);
    *(.TxDecripSection)
 
    . = ABSOLUTE(0x30040200);
    *(.RxArraySection)
  } >RAM_D2 AT> FLASH
 
  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }
 
  .ARM.attributes 0 : { *(.ARM.attributes) }
}

For good measure i put my linker file, so it might be a little more visible what i mean changing from DTCMRAM to RAM_D1

NMunt
Associate II

So kinda found a solution. In the syscalls.c file there is a defintion of caddr_t _sbrk(int incr).

By out commenting it then strings works.

The reason the caddr_t _sbrk(int incr) fails is because it keeps hitting the "heap collision loop", so my fix might very well introduce some memory errors.

Ill do some further testing, and if i remember to do so, i will report back.

(The full _sbrk from syscalls.c used from cubemx)

caddr_t _sbrk(int incr)
{
	extern char end asm("end");
	static char *heap_end;
	char *prev_heap_end;
 
	if (heap_end == 0)
		heap_end = &end;
 
	prev_heap_end = heap_end;
	if (heap_end + incr > stack_ptr) <-- It always enters this if statement
	{
//		write(1, "Heap and stack collision\n", 25);
//		abort();
		errno = ENOMEM;
		return (caddr_t) -1;
	}
 
	heap_end += incr;
 
	return (caddr_t) prev_heap_end;
}

berendi
Principal

You've moved the data and heap segments from DTCM to D1, but not the stack, as the stack pointer (at the top of the linker file) still points to DTCM. Therefore the stack pointer is always lower than the top of the heap.

You have commented out the call to abort(), but the check is still in place, always reporting that there is no heap available. Your program will fail later.

Now you should give some thought to the intended memory layout. Should the stack be moved to D1 as well (so that you can use DMA on local variables on the stack)? Or should the heap rather remain in DTCM, because you'll never do DMA on heap variables? Or move everything back to DTCM, except the DMA buffers? Then you can modify _sbrk() and the linker script accordingly.

You just need to scope and check the heap properly for the memory mapping you've chosen to use, and not involve the stack pointer in the test.

This could be implemented significantly better.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
NMunt
Associate II

Thanks for the answers.

I had totally missed that the estack was the stack point :S.

Im still pretty new in the whole embedded memory handling.

Lets say id want to keep everything on DTCM, but still need to do somethings with DMA.

How can i best go about that ?

Even some good search / key words would be appreciated.

Method 1:

Just set some pointers directly to D1 to be used as DMA buffers

uint16_t * const adc_dma_buf = (char*)0x24000000;
#define ADC_BUF_LEN = 0x10000
char * const uart1_tx_dma_buffer = (char *)((uint32_t)uart1_tx_dma_buf + ADC_BUF_LEN);
#define UART1_TX_BUF_LEN 0x1000

Then you can work with them as with any other pointers

snprintf(uart1_tx_dma_buffer, UART1_TX_BUFFER_LEN, "First ADC value is %d\r\n", adc_dma_buf[0]);
UART_DMA_Stream->M0AR = uart1_tx_dma_buffer;

Advantage: no need to modify linker files, compatible across different compiler/linker brands.

Disadvantage: you have to track buffer length and placement yourself.

Method 2

Create a new block in the linker file for D1

 .dma (NOLOAD) : {
    *(.dma)
    *(.dma*)
  } >RAM_D1

Then mark variables to be used as DMA buffers with the section attribute

uint16_t adc_dma_buf[0x8000] __attribute__ ((section (".dma.adc")));
char uart_dma_buf[0x1000] __attribute__ ((section (".dma.uart")));

So method 1 works only in the case were i manually handle all pointers/addresses on D1, right ?

Otherwise i could have some overwrite problems.

E.g. i let .bss exists on D1 then i would need to ensure that they dont overlap.

For method 2 the 'section (".dma.adc")'

the .adc or .uart part, in what sense is that required to be define ?

Is it simply for my sake/sanity, or does it do something ?

e.g. it makes a request for something called .adc on .dma for 0x8000 space.

I just want to make sure i understand your reply completely.

Thanks for the otherwise nice and clean answers