Actions

EmSys

Writing Startup Code and Linker Script for the TM4C ARM Microcontroller

From EdWiki

Revision as of 06:30, 30 December 2019 by Jshankar (Talk | contribs)

Writing Startup Code and Linker Script for the TM4C ARM Microcontroller

Nearly every ARM Cortex-M needs a startup code file to define it’s Main Stack Pointer, Reset Handler and Interrupt Vector Table and a Linker Script telling it to place which sections into which parts of memory, these two files are crucial, without which even a simple blinky program cannot be run.

Startup Code

First we start off with the Startup code, which has two main functions

  1. Initializing the Vector Table with the Main Stack Pointer
  2. Implementing the Reset_Handler function, which consists of,
    • Copying .data sections of the memory from Internal Flash to Internal SRAM
    • Setting .bss values to 0
    • And finally pointing to the main() function of our program, after these initialization tasks are done

Writing Startup.h

The Vector Table is an array of void functions which is placed into a section in the memory, namely .vector_table in this case, this name has also to be reflected in the linker script. The functions each map to an Interrupt index-wise, which is hard coded into the micro controller itself, the interrupt vector table is defined in the TM4C123GH6PM data sheet, pages 147-149, and we will be using it to write our own vector table. We will be having around ~120 or so function prototypes in our vector table, so we need to alias undefined function prototypes to a default handler function, we do this by defining a macro in our startup.h file like so

#define DEFAULT __attribute__((weak, alias("Default_Handler")))

This macro, DEFAULT will expand to __attribute__((weak, alias("Default_Handler"))) which is a command to the GNU GCC compiler to alias a given function to the Default_Handler if it is not defined. For example, we are going to explicitly define the Reset_Handler function prototype in the startup.c file, but not the NMI_Handler or other functions for now, so we mark them as DEFAULT

void Reset_Handler(void);
DEFAULT void NMI_Handler(void);
DEFAULT void SVC_Handler(void);
DEFAULT void DebugMonitor_Handler(void);
DEFAULT void PendSV_Handler(void);
DEFAULT void SysTick_Handler(void);

We then start writing the ISR function prototypes

DEFAULT void GPIOPortA_ISR(void);
DEFAULT void GPIOPortB_ISR(void);
DEFAULT void GPIOPortC_ISR(void);
DEFAULT void GPIOPortD_ISR(void);
DEFAULT void GPIOPortE_ISR(void);
DEFAULT void UART0_ISR(void);
DEFAULT void UART1_ISR(void);
DEFAULT void SPI0_ISR(void);
DEFAULT void I2C0_ISR(void);
DEFAULT void PWM0Fault_ISR(void);
DEFAULT void PWM0Generator0_ISR(void);
 .........

These function prototypes are of return type void, and standard arrays cannot be declared as void, so we need to define new types for these

typedef void (*element_t)(void);

Here, *element_t is a pointer passed to void function and cast as void


next we define a union for our main stack pointer and our ISRs

typedef union {
    element_t isr;   
    void *stack_top; 
} vector_table_t;

void *stack_top is a pointer to the top of the stack,and the 0th element of the vector table. element_t isr stands for the void functions that will be added to the vector table


Lastly,we have to declare external variables,mainly the sections,and the main() program entry point

extern int main(void);
 
extern uint32_t _stack_ptr;
extern uint32_t _etext;
extern uint32_t _data;
extern uint32_t _edata;
extern uint32_t _bss;
extern uint32_t _ebss;

Here the extern keyword simply tells to compiler to look for these keywords in another file external to this, the uint32_t means an unsigned 32 bit integer. int main(void) is the entry point to our main() program. _stack_ptr is the pointer to the top of the stack, i.e the last address of RAM, this is defined in the linker script. _data is the start of the .data section, and _edata is the end of the .data section.

Writing Startup.c

We start by including our startup.h header file with out definitions and the <stdint.h>

#include <stdint.h>
#include "startup.h"

Next we direct the compiler to place the following vector table into the section .vector_table in the .data segment

__attribute__((section(".vector_table"))) 

Now we define our vector table

const vector_table_t vectors[] = {
    {.stack_top = &_stack_ptr},
    Reset_Handler,        
    NMI_Handler,          
    HardFault_Handler,    
    MemManageFault_Handler,
    BusFault_Handler,           
    UsageFault_Handler,         
    0,                          
    0,                          
    0,                          
    0,                          
    SVC_Handler,                
    DebugMonitor_Handler,       
    0,                          
    PendSV_Handler,             
    SysTick_Handler,            
    GPIOPortA_ISR,              
    GPIOPortB_ISR,              
    GPIOPortC_ISR,              
    GPIOPortD_ISR,              
    GPIOPortE_ISR,              
    UART0_ISR,                  
    UART1_ISR,                  
    SPI0_ISR,                   
    /*MORE ISRS FOLLOW FROM HERE */
};

Here,

  • The 0th element,{.stack_top = &_stack_ptr} assigns the Main Stack Pointer defined in the Linker Script, _stack_ptr to the union element .stack_top defined in vector_table_t
  • The 1st element is the Reset_Handler, that is called when the Button on the microcontroller is pressed,or the reset flag is set

Finally we define the Reset_Handler and Default_Hanlder

void Reset_Handler(void)
{
 
  uint32_t *src, *dest;
 
  src = &_etext;
  for(dest = &_data; dest < &_edata;) {
    *dest++ = *src++;
  }
 
  for(dest = &_bss; dest < &_ebss;) {
    *dest++ = 0;
  }
 
  main();
}

In this function,

  • Two pointers are declared, *src and *dest
  • src is set to the address of _etext,and in the first loop *dest is set to the address of _data
  • Till dest reaches the end of edata it will loop and copy the contents of *src into it
  • This is copying the data from .data residing on the Flash,to the RAM
  • dest is now set to the address of _bss and every element of dest,i.e _bss is now being set to 0
  • Lastly,your main() is called,and control handed over to it

Default_Handler is also defined here,and it just infinitely loops when called

void Default_Handler(void)
{
    while (1) {
        ;
    }
}

With this,we are finally done writing the startup code