SparkFun Forums 

Where electronics enthusiasts find answers.

Everything ARM and LPC
By Elijah
#132680
Hi all, I'm working with an LPC2148 and can't seem to get malloc working. I'm using the latest codesourcery package to build the project. Everything works fine, except I can't use malloc (not even 4 bytes). It's a bare metal project, so you have to define your own versions of the system calls; which for malloc, is apparently _sbrk_r(). So, here's what I have for that:
Code: Select all
register char *stack_ptr asm ("sp");

caddr_t _sbrk_r(void *reent, size_t incr)
{
    // Defined by the linker
    extern char end asm ("end");

    static char *heap_end = NULL;

    char *prev_heap_end;

    if(heap_end == NULL)
        heap_end = &end;

    prev_heap_end = heap_end;

    if(( heap_end + incr ) > stack_ptr )
        return (caddr_t) -1;

    heap_end += incr;
    return (caddr_t) prev_heap_end;
}
Now when I step through it in the debugger (using OpenOCD), it ends up hitting the line "return (caddr_t) -1;" which I assume is what's causing malloc to fail. So my question is, what exactly does sbrk_r() do and is that check for (heap_end +inc) > stack_ptr correct? The stack grows downward (so the heap grows upward, right?), so it seems to me you would want to initialize heap_end to the very bottom of the stack. But instead, it's being initialized to the top of the stack. Here's my linker script:
Code: Select all
/* Reset vector for linker. */
ENTRY(_boot)

/* Stack sizes for C run-time as well as interrupts, supervisor, abort 
   and undefined conditions.  NOTE:  This is the only place you have to
   change the stack size information.  The crt.s assembler uses these values
   to set and clear the stack. */
C_STACK_SIZE   = 0x1000;
IRQ_STACK_SIZE = 0x400;
FIQ_STACK_SIZE = 0x100;
SVC_STACK_SIZE = 0x100;
ABT_STACK_SIZE = 0x100;
UND_STACK_SIZE = 0x100;

MEMORY
{
    flash (rx)        : ORIGIN = 0x00000000, LENGTH = 512K
    ram_isp_low(a)    : ORIGIN = 0x40000120, LENGTH = 223
    ram(rw)           : ORIGIN = 0x40000000, LENGTH = 0x7f00
    ram_isp_high(a)   : ORIGIN = 0x40007FE0, LENGTH = 32
    ram_usb_dma(rw)   : ORIGIN = 0x7FD00000, LENGTH = 8192
}

SECTIONS
{
    /* Interrupt vectors must start at memory 0x00000000. */
    .crt : 
    {
        . = ALIGN(4);
            *crt.o (.text)  
        . = ALIGN(4);
     } >flash

    /* Code (text) that is written to flash. */
    .text :
    {
        *(.text)                        /* all .text sections (code)  */
        *(.rodata)                      /* all .rodata sections (constants, strings, etc.)  */
        *(.rodata*)
        *(.glue_7);                     /* ARM/Thumb inter-networking glue functions  */
        *(.glue_7t);
        
        . = ALIGN(4);
        KEEP(*(.init))
        
        . = ALIGN(4);
        __preinit_array_start = .;
        KEEP (*(.preinit_array))
        __preinit_array_end = .;
        
        . = ALIGN(4);
        __init_array_start = .;
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array))
        __init_array_end = .;
        
        . = ALIGN(0x4);
        KEEP (*crtbegin.o(.ctors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*crtend.o(.ctors))
        
        . = ALIGN(4);
        KEEP(*(.fini))
        
        . = ALIGN(4);
        __fini_array_start = .;
        KEEP (*(.fini_array))
        KEEP (*(SORT(.fini_array.*)))
        __fini_array_end = .;
        
        KEEP (*crtbegin.o(.dtors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
        KEEP (*(SORT(.dtors.*)))
        KEEP (*crtend.o(.dtors))        
    } >flash
    
    . = ALIGN(4);
  
    /* Location in flash to save initialized data segment. */
    .etext :
    {
        . = ALIGN(4);    
        _etext = .;        
    } >flash
    
    /* All initialized data that is copied to RAM in the crt.s assembler file. */
    .data : AT (_etext)
    {
        . = ALIGN(4);
    
        _data = .;
        *(.data)
        _edata = .;
    } >ram

    /* BSS (Block Started by Symbol) block is the unitilized data segment. */
    .bss : 
    {
        __bss_start = . ;
        *(.bss)
        . = ALIGN (4);
        __bss_end = .;
    } >ram

    /* Stack allocation for the application and interrupts and exceptions. */
    .stack : 
    {
        . = ALIGN(0x100);
    
        __stack_start = . ;

        . += IRQ_STACK_SIZE;
        . = ALIGN (4);
        __irq_stack_top__ = . ;

        . += FIQ_STACK_SIZE;
        . = ALIGN (4);
        __fiq_stack_top__ = . ;

        . += SVC_STACK_SIZE;
        . = ALIGN (4);
        __svc_stack_top__ = . ;

        . += ABT_STACK_SIZE;
        . = ALIGN (4);
        __abt_stack_top__ = . ;

        . += UND_STACK_SIZE;
        . = ALIGN (4);
        __und_stack_top__ = . ;

        . += C_STACK_SIZE;
        . = ALIGN (4);
        __c_stack_top__ = . ;

        __stack_end = .;
    } >ram
    
    end = .;
}
...and the pertinent .map section:
Code: Select all
.stack          0x40001080     0x2880 load address 0x00014760
                0x40001100                . = ALIGN (0x100)
 *fill*         0x40001080       0x80 00
                0x40001100                __stack_start = .
                0x40001500                . = (. + IRQ_STACK_SIZE)
 *fill*         0x40001100      0x400 00
                0x40001500                . = ALIGN (0x4)
                0x40001500                __irq_stack_top__ = .
                0x40001600                . = (. + FIQ_STACK_SIZE)
 *fill*         0x40001500      0x100 00
                0x40001600                . = ALIGN (0x4)
                0x40001600                __fiq_stack_top__ = .
                0x40001700                . = (. + SVC_STACK_SIZE)
 *fill*         0x40001600      0x100 00
                0x40001700                . = ALIGN (0x4)
                0x40001700                __svc_stack_top__ = .
                0x40001800                . = (. + ABT_STACK_SIZE)
 *fill*         0x40001700      0x100 00
                0x40001800                . = ALIGN (0x4)
                0x40001800                __abt_stack_top__ = .
                0x40001900                . = (. + UND_STACK_SIZE)
 *fill*         0x40001800      0x100 00
                0x40001900                . = ALIGN (0x4)
                0x40001900                __und_stack_top__ = .
                0x40003900                . = (. + C_STACK_SIZE)
 *fill*         0x40001900     0x2000 00
                0x40003900                . = ALIGN (0x4)
                0x40003900                __c_stack_top__ = .
                0x40003900                __stack_end = .
                0x40003900                end = .
If I set a breakpoint inside the _sbrk_r() function and dump the top 0x100 of the stack (stack top is at 0x40003900, I get the following:
Code: Select all
0x40003800: deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef 
0x40003820: deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef 
0x40003840: deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef deadbeef 
0x40003860: deadbeef deadbeef deadbeef deadbeef deadbeef 8000005f 000109ac 0000002e 
0x40003880: 00000020 40000010 4000090c 40003900 40000a06 0000b028 4000090c 00000000 
0x400038a0: e01fc040 12345678 16a3cf85 00000050 fb3bf9b3 673deee7 9e5a3e6d 000021a0 
0x400038c0: 4000090c 0000d838 0000bdec 400038e0 16a3cf85 0000c270 0000ffff 0000c2c0 
0x400038e0: 40000f08 40000a1c 4000090c 00000000 00000000 00000000 12345678 0000011c 
0x40003900: b27d4d80 511d7980 47bb5f7b 33917c26 9fd11be2 1859a4b6 bb2fb47d 1398b400 
So clearly there's plenty of space at the bottom of the .stack section to allocate from. The problem seems to be that heap_end is initialized to 0x40003900, which will almost certainly *always* be larger than the stack pointer.... I must not understanding something. Any pointers would be greatly appreciated :-)
By ilektron
#132722
I avoid malloc whenever in an embedded environment. Pre-allocating memory is the surest way to do things. Now, assuming you know how nasty malloc and free can be, are you sure you don't have to allocate space to the heap somehow?
By Elijah
#132737
Yeah, I would like to avoid it as well; right now the FAT library I'm working with is using it, so I want to get that working as-is first. After it's functional, then I can see if it's possible to remove malloc entirely. If I understand sbrk() correctly, I now think that check if(( heap_end + incr ) > stack_ptr ) is wrong. The heap should grow upwards from the top of the stack into unused RAM and hence the heap end will always be greater than the stack pointer.
By frankvh
#132984
Get rid of malloc, and if the fat library you're using requires malloc, you're using the wrong fat library for an embedded processor. Use the Chan FatFS library instead. It's easy to use, well tested, and well suited for embedded applications.
By stevech
#133001
malloc() and free() are part of the common c library. There are many compiler-specific variations of these.

In embedded systems, if you have, say, 32KB or more of RAM, it's OK to use malloc(). Not advised, but OK.
It's not OK to use free() because in small-RAM MCUs, it's not practical to do garbage collection, recombining blocks, and so on. If you use C++ with dynamically created objects, you'll (unknowingly) be using malloc() and free() and have a mess, with a small RAM micro.

Also, try really hard to avoid floating point in low-end microprocessors. There's usually a way to use 16 or 32 bit fixed point. Essentially, fixed point involves simply scaling up the values by a power of two. This creates an imaginary fixed decimal point. So if a=1.25 as a real number, in fixed point with two fractional bits implied, it would be binary 0...101 where the decimal point is implied to be left of the sole "01" bits. With fixed point, you just multiply and divide as if these were integers, but carry in your head that everything's scaled up by n bits. When you go to display this, you right shift n bits to get the integer part for display, then print a ".", then AND to get the fractional bits and print these with a format that requires leading zeros. In C, this would be, for x being a fixed point (2 fractional bits)...

printf("%d.%02d", x>>2, x & 3);

fixed point has been around since the dawn of computers.

As to your malloc() failing (sbrk is, IIRC, a very old Unix notion of malloc() ) - perhaps your build omits a heap for malloc to use, or otherwise has malloc() disabled.
By stevech
#133400
X-Hawk wrote:malloc is evil on MCU.
free() is the devil, not so much malloc()
By stevech
#133587
there are many such library routines out there. But a small micro has no business trying to garbage collect the heap.
malloc() is OK; free() is not.
By nidalpres
#155562
Yes, it is an old thread, but I guess people still find this one from time to time.

Regardles of how malloc and free are devious and evil, this is the thing to do (or to start with and work from here):

extern char end in the _sbrk_r code was defined as end of ram, that is why it will never work.
Here it is defined as heap end:
Code: Select all
   register char *stack_ptr asm ("sp");

    caddr_t _sbrk_r(void *reent, size_t incr)
    {
        // Defined by the linker
        extern char end asm ("__heap_end");

        static char *heap_end = NULL;

        char *prev_heap_end;

        if(heap_end == NULL)
            heap_end = &end;

        prev_heap_end = heap_end;

        if(( heap_end + incr ) > stack_ptr )
            return (caddr_t) -1;

        heap_end += incr;
        return (caddr_t) prev_heap_end;
    }
and you need to define __heap_end somewhere in the linker file, i.e. good starting place would be just before stack section:
Code: Select all
    /* Reset vector for linker. */
    ENTRY(_boot)

    /* Stack sizes for C run-time as well as interrupts, supervisor, abort
       and undefined conditions.  NOTE:  This is the only place you have to
       change the stack size information.  The crt.s assembler uses these values
       to set and clear the stack. */
    C_STACK_SIZE   = 0x1000;
    IRQ_STACK_SIZE = 0x400;
    FIQ_STACK_SIZE = 0x100;
    SVC_STACK_SIZE = 0x100;
    ABT_STACK_SIZE = 0x100;
    UND_STACK_SIZE = 0x100;

    MEMORY
    {
        flash (rx)        : ORIGIN = 0x00000000, LENGTH = 512K
        ram_isp_low(a)    : ORIGIN = 0x40000120, LENGTH = 223
        ram(rw)           : ORIGIN = 0x40000000, LENGTH = 0x7f00
        ram_isp_high(a)   : ORIGIN = 0x40007FE0, LENGTH = 32
        ram_usb_dma(rw)   : ORIGIN = 0x7FD00000, LENGTH = 8192
    }

    SECTIONS
    {
        /* Interrupt vectors must start at memory 0x00000000. */
        .crt :
        {
            . = ALIGN(4);
                *crt.o (.text) 
            . = ALIGN(4);
         } >flash

        /* Code (text) that is written to flash. */
        .text :
        {
            *(.text)                        /* all .text sections (code)  */
            *(.rodata)                      /* all .rodata sections (constants, strings, etc.)  */
            *(.rodata*)
            *(.glue_7);                     /* ARM/Thumb inter-networking glue functions  */
            *(.glue_7t);
           
            . = ALIGN(4);
            KEEP(*(.init))
           
            . = ALIGN(4);
            __preinit_array_start = .;
            KEEP (*(.preinit_array))
            __preinit_array_end = .;
           
            . = ALIGN(4);
            __init_array_start = .;
            KEEP (*(SORT(.init_array.*)))
            KEEP (*(.init_array))
            __init_array_end = .;
           
            . = ALIGN(0x4);
            KEEP (*crtbegin.o(.ctors))
            KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
            KEEP (*(SORT(.ctors.*)))
            KEEP (*crtend.o(.ctors))
           
            . = ALIGN(4);
            KEEP(*(.fini))
           
            . = ALIGN(4);
            __fini_array_start = .;
            KEEP (*(.fini_array))
            KEEP (*(SORT(.fini_array.*)))
            __fini_array_end = .;
           
            KEEP (*crtbegin.o(.dtors))
            KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
            KEEP (*(SORT(.dtors.*)))
            KEEP (*crtend.o(.dtors))       
        } >flash
       
        . = ALIGN(4);
     
        /* Location in flash to save initialized data segment. */
        .etext :
        {
            . = ALIGN(4);   
            _etext = .;       
        } >flash
       
        /* All initialized data that is copied to RAM in the crt.s assembler file. */
        .data : AT (_etext)
        {
            . = ALIGN(4);
       
            _data = .;
            *(.data)
            _edata = .;
        } >ram

        /* BSS (Block Started by Symbol) block is the unitilized data segment. */
        .bss :
        {
            __bss_start = . ;
            *(.bss)
            . = ALIGN (4);
            __bss_end = .;
        } >ram
	
	.heap :
	{
		__heap_start = .;
		*(.heap*)
		__heap_end = .;
	} > ram

        /* Stack allocation for the application and interrupts and exceptions. */
        .stack :
        {
            . = ALIGN(0x100);
       
            __stack_start = . ;

            . += IRQ_STACK_SIZE;
            . = ALIGN (4);
            __irq_stack_top__ = . ;

            . += FIQ_STACK_SIZE;
            . = ALIGN (4);
            __fiq_stack_top__ = . ;

            . += SVC_STACK_SIZE;
            . = ALIGN (4);
            __svc_stack_top__ = . ;

            . += ABT_STACK_SIZE;
            . = ALIGN (4);
            __abt_stack_top__ = . ;

            . += UND_STACK_SIZE;
            . = ALIGN (4);
            __und_stack_top__ = . ;

            . += C_STACK_SIZE;
            . = ALIGN (4);
            __c_stack_top__ = . ;

            __stack_end = .;
        } >ram
       
        end = .;
    }
Now, since .heap section is practically empty, you need to define somewhere in C code an array of bytes of length equal to whatever you want heap size to be and place it into .heap section. Maybe something like this:
Code: Select all
#define HEAP_SIZE 16384
uint8_t HeapArray[HEAP_SIZE] __attribute__ ((section(".heap")));
That will force linker to push stack section towards the top and leave some space for the heap (16kB in this example).

Now, while most of what I wrote above is really bad practice, it is still good learning tool so you can understand what is going on. That way you build a good basis for making your designing decisions.