How about those AI generated images, that supposed to be a C start file haha!!
Purpose of the Startup File:
The startup file is a crucial part of an embedded system's initialization process. Its primary purpose is to:
Initialize the Hardware: Set up the initial state of the microcontroller, including configuring the stack pointer, initializing static data, and setting up the vector table.
Prepare the Runtime Environment: Zero-initialize the .bss
section (uninitialized data) and copy the initialized data from Flash to RAM (.data
section).
Jump to the Main Program: After setting up the environment, the startup file typically transfers control to the main()
function, where the user’s application code begins execution.
Why It’s Usually Written in Assembly:
Low-Level Control: Assembly language provides direct access to the CPU registers and hardware features, allowing precise control over the initialization process that might be challenging or inefficient to achieve in C.
Minimal Overhead: Startup code needs to run before any other code, including the C runtime library. Writing it in assembly ensures there’s minimal overhead and no dependency on a higher-level runtime, which might not be initialized yet.
CPU-Specific Initialization: Certain tasks, like setting the stack pointer or configuring the CPU’s vector table, require instructions that are specific to the processor and are best implemented in assembly. However as you saw in the vector table we can just place the stack pointer at the right location without any need for code per say.
For typical applications with standard initialization needs, like our use case here, writing the startup file in C is perfectly acceptable. It simplifies development and leverages the C language's capabilities to prepare the system for running the main application. Assembly is more critical when you need fine-grained control over the hardware or when optimizing for the smallest possible code size and maximum performance.
myStartUp.c
The first step is to include the necessary standard integer header and reference all the symbols defined in our linker script:
#include <stdint.h>
// Linker symbols that are used to prepare the vector table
extern uint32_t _top_of_ram_stack_start;
extern uint32_t _data;
extern uint32_t _edata;
extern uint32_t _sidata;
extern uint32_t _bss;
extern uint32_t _ebss;
Keep in mind that these symbols represent address locations, not the values stored at those addresses.
Next, we need to define the reset handler function, as it is the first function executed upon reset. Since the reset handler will eventually call the main function, we should also declare main as an external variable to inform the compiler that it exists elsewhere. Additionally, we'll create a default interrupt handler. All unimplemented interrupt handlers will be mapped to this default handler. By marking this mapping as weak, we ensure that any actual interrupt handlers we implement can override the default one. For now we will make the function prototypes.
extern void main(void);
void Reset_Handler(void);
void Default_Handler(void);
Now lets make the vector table. You can find the layout of the vector table for your device in its reference manual, below is a snippet of mine.
As you can see in the table an per my explanation before, 0x00000000 is reserved this is where the initial stack pointer will go and right after that comes the Reset_Handler, and they are all 1 word apart (4 bytes).
Lets first add the prototypes for the exception and interrupt handlers and define them as weak with the default being Default_Handler
// All handlers will default to Default_Handler until they are reimplemented in the application code
void NMI_Handler(void) __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
void MemManage_Handler(void) __attribute__((weak, alias("Default_Handler")));
void BusFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
void UsageFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
void SVC_Handler(void) __attribute__((weak, alias("Default_Handler")));
void DebugMon_Handler(void) __attribute__((weak, alias("Default_Handler")));
void PendSV_Handler(void) __attribute__((weak, alias("Default_Handler")));
void SysTick_Handler(void) __attribute__((weak, alias("Default_Handler")));
//----------------------------------------------------------------------------------------------
void WWDG_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void PVD_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void RTC_TAMP_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void FLASH_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void RCC_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void EXTI0_1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void EXTI2_3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void EXTI4_15_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void UCPD1_2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void DMA1_Channel1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void DMA1_Channel2_3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void DMA1_Ch4_7_DMAMUX1_OVR_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void ADC1_COMP_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM1_BRK_UP_TRG_COM_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM1_CC_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM3_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM6_DAC_LPTIM1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM7_LPTIM2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM14_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM15_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM16_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM17_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void I2C1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void I2C2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void SPI1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void SPI2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void USART1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void USART2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void USART3_4_LPUART1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void CEC_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
Below is my vector table itself.
uint32_t vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t)&_top_of_ram_stack_start, // Initial Stack Pointer
//--------| System Exception Handlers |-----------------------------
(uint32_t)&Reset_Handler, // Reset Handler
(uint32_t)&NMI_Handler, // NMI Handler
(uint32_t)&HardFault_Handler, // Hard Fault Handler
(uint32_t)&MemManage_Handler, // MPU Fault Handler
(uint32_t)&BusFault_Handler, // Bus Fault Handler
(uint32_t)&UsageFault_Handler, // Usage Fault Handler
0, 0, 0, 0, // Reserved
(uint32_t)&SVC_Handler, // SVCall Handler
(uint32_t)&DebugMon_Handler, // Debug Monitor Handler
0, // Reserved
(uint32_t)&PendSV_Handler, // PendSV Handler
(uint32_t)&SysTick_Handler, // SysTick Handler
//--------| Peripheral Interrupts Handlers |--------------------------
(uint32_t)&WWDG_IRQHandler, /* Window WatchDog */
(uint32_t)&PVD_IRQHandler, /* PVD through EXTI Line detect */
(uint32_t)&RTC_TAMP_IRQHandler, /* RTC through the EXTI line */
(uint32_t)&FLASH_IRQHandler, /* FLASH */
(uint32_t)&RCC_IRQHandler, /* RCC */
(uint32_t)&EXTI0_1_IRQHandler, /* EXTI Line 0 and 1 */
(uint32_t)&EXTI2_3_IRQHandler, /* EXTI Line 2 and 3 */
(uint32_t)&EXTI4_15_IRQHandler, /* EXTI Line 4 to 15 */
(uint32_t)&UCPD1_2_IRQHandler, /* UCPD1, UCPD2 */
(uint32_t)&DMA1_Channel1_IRQHandler, /* DMA1 Channel 1 */
(uint32_t)&DMA1_Channel2_3_IRQHandler, /* DMA1 Channel 2 and Channel 3 */
(uint32_t)&DMA1_Ch4_7_DMAMUX1_OVR_IRQHandler, /* DMA1 Channel 4 to Channel 7, DMAMUX1 overrun */
(uint32_t)&ADC1_COMP_IRQHandler, /* ADC1, COMP1 and COMP2 */
(uint32_t)&TIM1_BRK_UP_TRG_COM_IRQHandler, /* TIM1 Break, Update, Trigger and Commutation */
(uint32_t)&TIM1_CC_IRQHandler, /* TIM1 Capture Compare */
(uint32_t)&TIM2_IRQHandler, /* TIM2 */
(uint32_t)&TIM3_IRQHandler, /* TIM3 */
(uint32_t)&TIM6_DAC_LPTIM1_IRQHandler, /* TIM6, DAC and LPTIM1 */
(uint32_t)&TIM7_LPTIM2_IRQHandler, /* TIM7 and LPTIM2 */
(uint32_t)&TIM14_IRQHandler, /* TIM14 */
(uint32_t)&TIM15_IRQHandler, /* TIM15 */
(uint32_t)&TIM16_IRQHandler, /* TIM16 */
(uint32_t)&TIM17_IRQHandler, /* TIM17 */
(uint32_t)&I2C1_IRQHandler, /* I2C1 */
(uint32_t)&I2C2_IRQHandler, /* I2C2 */
(uint32_t)&SPI1_IRQHandler, /* SPI1 */
(uint32_t)&SPI2_IRQHandler, /* SPI2 */
(uint32_t)&USART1_IRQHandler, /* USART1 */
(uint32_t)&USART2_IRQHandler, /* USART2 */
(uint32_t)&USART3_4_LPUART1_IRQHandler, /* USART3, USART4 and LPUART1 */
(uint32_t)&CEC_IRQHandler /* CEC */
};
The next and final two pieces we need are the Reset_Handler itself and the Default_Handler
void Reset_Handler(void)
{
// Copy .data section from Flash to RAM
uint32_t size = (uint32_t)&_edata - (uint32_t)&_data;
uint8_t *pDst = (uint8_t *)&_data; // pointer to start of RAM
uint8_t *pSrc = (uint8_t *)&_sidata; // pointer to start of Flash
for (uint32_t i = 0; i < size; i++)
{
*pDst++ = *pSrc++;
}
// Initialize the .bss section to zero in RAM
size = (uint32_t)&_ebss - (uint32_t)&_bss;
pDst = (uint8_t *)&_bss;
for (uint32_t i = 0; i < size; i++)
{
*pDst++ = 0;
}
// do other initialization if your application requires it before you get to main
// typically bootloader stuff, other checks, etc.
// Call main() function
main();
while (1)
{
//should never get here unless something went wrong
}
}
void Default_Handler(void)
{
// handle this how ever you want
while (1)
{
}
}
And that is all you need as far as the startup file goes. The C code pretty much speaks for itself so im not going to do into detail about it.
The next step is to use our toolchain to compile, link and flash to the device. See you in the next post!
<< PREVIOUS | NEXT >>
Comments