Minimaler Code zum Starten eines STM32F4 erforderlich?
14
Welches ist die effizienteste Methode / der minimal erforderliche Code, um einen STM32F4 zu starten? Die Startdateien, die von ST stammen, scheinen viel unnötigen Code zu haben.
Entfernen Sie, was Sie für "unnötig" halten, und versuchen Sie, es auszuführen ...
Tyler
1
Der Code des Chipanbieters versucht, eine Einheitsgröße zu haben, was bedeutet, dass er niemandem gut passt. Sie werden per Definition immer aufgebläht sein, weil sie versuchen, alle wichtigen Anwendungsfälle für alle Peripheriegeräte und Funktionen zu behandeln, die sie unterstützen möchten. Wenn Sie ihren Code verwenden, profitieren Sie von der Unterstützung durch sie und andere Personen, die diesen Code verwenden. Gehen Sie Ihren eigenen Weg und Sie profitieren von Größe und Geschwindigkeit, aber es liegt hauptsächlich an Ihnen, dieses Rad neu zu erfinden.
old_timer
Oder schneiden Sie, wie Tyler sagte, das Zeug aus, das Sie nicht wollen / brauchen.
old_timer
Antworten:
25
Möglicherweise möchten Sie den vom Hersteller bereitgestellten Startcode nicht verwenden. Es gibt nur wenige Gründe, warum dies so ist:
Erstellen Sie effizienteren oder weniger aufgeblähten Code. Haben Sie eine spezielle Anforderung, die der Herstellercode nicht erfüllt. Sie möchten wissen, wie Dinge funktionieren. Sie möchten eine Art universellen Code, der in vielen verschiedenen MCUs verwendet werden kann. Sie möchten die vollständige Kontrolle über den Prozess. etc..
Das Folgende gilt nur für C-Programme (kein C ++, Ausnahmen usw.) und Cortex M-Mikrocontroller (unabhängig von Marke / Modell). Außerdem gehe ich davon aus, dass Sie GCC verwenden, obwohl es möglicherweise keinen oder nur geringen Unterschied zu anderen Compilern gibt. Endlich benutze ich newlib.
Linker-Skript
Als Erstes müssen Sie ein Linker-Skript erstellen. Sie müssen Ihrem Compiler mitteilen, wie die Dinge im Speicher angeordnet werden sollen. Ich werde nicht näher auf das Linker-Skript eingehen, da es ein eigenständiges Thema ist.
/*
* Linker script.
*//*
* Set the output format. Currently set for Cortex M architectures,
* may need to be modified if the library has to support other MCUs,
* or completelly removed.
*/
OUTPUT_FORMAT ("elf32-littlearm","elf32-bigarm","elf32-littlearm")/*
* Just refering a function included in the vector table, and that
* it is defined in the same file with it, so the vector table does
* not get optimized out.
*/
EXTERN(Reset_Handler)/*
* ST32F103x8 memory setup.
*/
MEMORY
{
FLASH (rx): ORIGIN =0x00000000, LENGTH =64k
RAM (xrw): ORIGIN =0x20000000, LENGTH =20k}/*
* Necessary group so the newlib stubs provided in the library,
* will correctly be linked with the appropriate newlib functions,
* and not optimized out, giving errors for undefined symbols.
* This way the libraries can be fed to the linker in any order.
*/
GROUP(
libgcc.a
libg.a
libc.a
libm.a
libnosys.a
)/*
* Stack start pointer. Here set to the end of the stack
* memory, as in most architectures (including all the
* new ARM ones), the stack starts from the maximum address
* and grows towards the bottom.
*/
__stack = ORIGIN(RAM)+ LENGTH(RAM);/*
* Programm entry function. Used by the debugger only.
*/
ENTRY(_start)/*
* Memory Allocation Sections
*/
SECTIONS
{/*
* For normal programs should evaluate to 0, for placing the vector
* table at the correct position.
*/.= ORIGIN(FLASH);/*
* First link the vector table.
*/.vectors : ALIGN(4){
FILL(0xFF)
__vectors_start__ = ABSOLUTE(.);
KEEP(*(.vectors))*(.after_vectors .after_vectors.*)}> FLASH
/*
* Start of text.
*/
_text =.;/*
* Text section
*/.text : ALIGN(4){*(.text)*(.text.*)*(.glue_7t)*(.glue_7)*(.gcc*)}> FLASH
/*
* Arm section unwinding.
* If removed may cause random crashes.
*/.ARM.extab :{*(.ARM.extab*.gnu.linkonce.armextab.*)}> FLASH
/*
* Arm stack unwinding.
* If removed may cause random crashes.
*/.ARM.exidx :{
__exidx_start =.;*(.ARM.exidx*.gnu.linkonce.armexidx.*)
__exidx_end =.;}> FLASH
/*
* Section used by C++ to access eh_frame.
* Generaly not used, but it doesn't harm to be there.
*/.eh_frame_hdr :{*(.eh_frame_hdr)}> FLASH
/*
* Stack unwinding code.
* Generaly not used, but it doesn't harm to be there.
*/.eh_frame : ONLY_IF_RO
{*(.eh_frame)}> FLASH
/*
* Read-only data. Consts should also be here.
*/.rodata : ALIGN(4){.= ALIGN(4);
__rodata_start__ =.;*(.rodata)*(.rodata.*).= ALIGN(4);
__rodata_end__ =.;}> FLASH
/*
* End of text.
*/
_etext =.;/*
* Data section.
*/.data : ALIGN(4){
FILL(0xFF).= ALIGN(4);
PROVIDE(__textdata__ = LOADADDR(.data));
PROVIDE(__data_start__ =.);*(.data)*(.data.*)*(.ramtext).= ALIGN(4);
PROVIDE(__data_end__ =.);}> RAM AT > FLASH
/*
* BSS section.
*/.bss (NOLOAD): ALIGN(4){.= ALIGN(4);
PROVIDE(_bss_start =.);
__bss_start__ =.;*(.bss)*(.bss.*)*(COMMON).= ALIGN(4);
PROVIDE(_bss_end =.);
__bss_end__ =.;
PROVIDE(end =.);}> RAM
/*
* Non-initialized variables section.
* A variable should be explicitly placed
* here, aiming in speeding-up boot time.
*/.noinit (NOLOAD): ALIGN(4){
__noinit_start__ =.;*(.noinit .noinit.*).= ALIGN(4);
__noinit_end__ =.;}> RAM
/*
* Heap section.
*/.heap (NOLOAD):{.= ALIGN(4);
__heap_start__ =.;
__heap_base__ =.;.= ORIGIN(HEAP_RAM)+ LENGTH(HEAP_RAM);
__heap_end__ =.;}> RAM
}
Sie können das bereitgestellte Linker-Skript direkt verwenden. Einige Dinge zu beachten:
Dies ist eine vereinfachte Version des von mir verwendeten Linker-Skripts. Während des Stripping-Downs habe ich möglicherweise Fehler in den Code eingefügt. Bitte überprüfen Sie ihn noch einmal.
Da ich es für andere MCUs als Sie verwende, müssen Sie das MEMORY-Layout an Ihre Bedürfnisse anpassen.
Möglicherweise müssen Sie die unten verlinkten Bibliotheken ändern, um sie mit Ihren eigenen zu verknüpfen. Hier verlinkt es gegen newlib.
Vektortabelle
Sie müssen in Ihren Code eine Vektortabelle einfügen. Dies ist einfach eine Nachschlagetabelle mit Funktionszeigern, auf die die Hardware im Falle eines Interrupts automatisch zugreift. Dies ist in C ziemlich einfach zu bewerkstelligen.
Schauen Sie sich die folgende Datei an. Dies gilt für die STM32F103C8-MCU, ist jedoch sehr einfach an Ihre Anforderungen anzupassen.
#include"stm32f10x.h"#include"debug.h"//Start-up code.externvoid __attribute__((noreturn, weak)) _start (void);// Default interrupt handlervoid __attribute__ ((section(".after_vectors"), noreturn)) __Default_Handler(void);// Reset handlervoid __attribute__ ((section(".after_vectors"), noreturn))Reset_Handler(void);/** Non-maskable interrupt (RCC clock security system) */void NMI_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** All class of fault */voidHardFault_Handler(void) __attribute__ ((interrupt, weak));/** Memory management */voidMemManage_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Pre-fetch fault, memory access fault */voidBusFault_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Undefined instruction or illegal state */voidUsageFault_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** System service call via SWI instruction */void SVC_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Debug monitor */voidDebugMon_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Pendable request for system service */voidPendSV_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** System tick timer */voidSysTick_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Window watchdog interrupt */void WWDG_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** PVD through EXTI line detection interrupt */void PVD_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Tamper interrupt */void TAMPER_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** RTC global interrupt */void RTC_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** Flash global interrupt */void FLASH_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** RCC global interrupt */void RCC_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line0 interrupt */void EXTI0_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line1 interrupt */void EXTI1_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line2 interrupt */void EXTI2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line3 interrupt */void EXTI3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line4 interrupt */void EXTI4_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel1 global interrupt */void DMA1_Channel1_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel2 global interrupt */void DMA1_Channel2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel3 global interrupt */void DMA1_Channel3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel4 global interrupt */void DMA1_Channel4_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel5 global interrupt */void DMA1_Channel5_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel6 global interrupt */void DMA1_Channel6_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA1 Channel7 global interrupt */void DMA1_Channel7_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** ADC1 and ADC2 global interrupt */void ADC1_2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** USB high priority or CAN TX interrupts */void USB_HP_CAN_TX_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** USB low priority or CAN RX0 interrupts */void USB_LP_CAN_RX0_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** CAN RX1 interrupt */void CAN_RX1_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** CAN SCE interrupt */void CAN_SCE_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line[9:5] interrupts */void EXTI9_5_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM1 break interrupt */void TIM1_BRK_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM1 update interrupt */void TIM1_UP_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM1 trigger and commutation interrupts */void TIM1_TRG_COM_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM1 capture compare interrupt */void TIM1_CC_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM2 global interrupt */void TIM2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM3 global interrupt */void TIM3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM4 global interrupt */void TIM4_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** I2C1 event interrupt */void I2C1_EV_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** I2C1 error interrupt */void I2C1_ER_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** I2C2 event interrupt */void I2C2_EV_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** I2C2 error interrupt */void I2C2_ER_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** SPI1 global interrupt */void SPI1_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** SPI2 global interrupt */void SPI2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** USART1 global interrupt */void USART1_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** USART2 global interrupt */void USART2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** USART3 global interrupt */void USART3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** EXTI Line[15:10] interrupts */void EXTI15_10_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** RTC alarm through EXTI line interrupt */voidRTCAlarm_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** USB wakeup from suspend through EXTI line interrupt */voidUSBWakeup_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM8 break interrupt */void TIM8_BRK_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM8 update interrupt */void TIM8_UP_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM8 trigger and commutation interrupts */void TIM8_TRG_COM_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM8 capture compare interrupt */void TIM8_CC_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** ADC3 global interrupt */void ADC3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** FSMC global interrupt */void FSMC_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** SDIO global interrupt */void SDIO_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM5 global interrupt */void TIM5_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** SPI3 global interrupt */void SPI3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** UART4 global interrupt */void UART4_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** UART5 global interrupt */void UART5_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM6 global interrupt */void TIM6_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** TIM7 global interrupt */void TIM7_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA2 Channel1 global interrupt */void DMA2_Channel1_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA2 Channel2 global interrupt */void DMA2_Channel2_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA2 Channel3 global interrupt */void DMA2_Channel3_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));/** DMA2 Channel4 and DMA2 Channel5 global interrupts */void DMA2_Channel4_5_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));// Stack start variable, needed in the vector table.externunsignedint __stack;// Typedef for the vector table entries.typedefvoid(*const pHandler)(void);/** STM32F103 Vector Table */
__attribute__ ((section(".vectors"), used)) pHandler vectors[]={(pHandler)&__stack,// The initial stack pointerReset_Handler,// The reset handler
NMI_Handler,// The NMI handlerHardFault_Handler,// The hard fault handler#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)MemManage_Handler,// The MPU fault handlerBusFault_Handler,// The bus fault handlerUsageFault_Handler,// The usage fault handler#else0,0,0,// Reserved#endif0,// Reserved0,// Reserved0,// Reserved0,// Reserved
SVC_Handler,// SVCall handler#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)DebugMon_Handler,// Debug monitor handler#else0,// Reserved#endif0,// ReservedPendSV_Handler,// The PendSV handlerSysTick_Handler,// The SysTick handler// ----------------------------------------------------------------------
WWDG_IRQHandler,// Window watchdog interrupt
PVD_IRQHandler,// PVD through EXTI line detection interrupt
TAMPER_IRQHandler,// Tamper interrupt
RTC_IRQHandler,// RTC global interrupt
FLASH_IRQHandler,// Flash global interrupt
RCC_IRQHandler,// RCC global interrupt
EXTI0_IRQHandler,// EXTI Line0 interrupt
EXTI1_IRQHandler,// EXTI Line1 interrupt
EXTI2_IRQHandler,// EXTI Line2 interrupt
EXTI3_IRQHandler,// EXTI Line3 interrupt
EXTI4_IRQHandler,// EXTI Line4 interrupt
DMA1_Channel1_IRQHandler,// DMA1 Channel1 global interrupt
DMA1_Channel2_IRQHandler,// DMA1 Channel2 global interrupt
DMA1_Channel3_IRQHandler,// DMA1 Channel3 global interrupt
DMA1_Channel4_IRQHandler,// DMA1 Channel4 global interrupt
DMA1_Channel5_IRQHandler,// DMA1 Channel5 global interrupt
DMA1_Channel6_IRQHandler,// DMA1 Channel6 global interrupt
DMA1_Channel7_IRQHandler,// DMA1 Channel7 global interrupt
ADC1_2_IRQHandler,// ADC1 and ADC2 global interrupt
USB_HP_CAN_TX_IRQHandler,// USB high priority or CAN TX interrupts
USB_LP_CAN_RX0_IRQHandler,// USB low priority or CAN RX0 interrupts
CAN_RX1_IRQHandler,// CAN RX1 interrupt
CAN_SCE_IRQHandler,// CAN SCE interrupt
EXTI9_5_IRQHandler,// EXTI Line[9:5] interrupts
TIM1_BRK_IRQHandler,// TIM1 break interrupt
TIM1_UP_IRQHandler,// TIM1 update interrupt
TIM1_TRG_COM_IRQHandler,// TIM1 trigger and commutation interrupts
TIM1_CC_IRQHandler,// TIM1 capture compare interrupt
TIM2_IRQHandler,// TIM2 global interrupt
TIM3_IRQHandler,// TIM3 global interrupt
TIM4_IRQHandler,// TIM4 global interrupt
I2C1_EV_IRQHandler,// I2C1 event interrupt
I2C1_ER_IRQHandler,// I2C1 error interrupt
I2C2_EV_IRQHandler,// I2C2 event interrupt
I2C2_ER_IRQHandler,// I2C2 error interrupt
SPI1_IRQHandler,// SPI1 global interrupt
SPI2_IRQHandler,// SPI2 global interrupt
USART1_IRQHandler,// USART1 global interrupt
USART2_IRQHandler,// USART2 global interrupt
USART3_IRQHandler,// USART3 global interrupt
EXTI15_10_IRQHandler,// EXTI Line[15:10] interruptsRTCAlarm_IRQHandler,// RTC alarm through EXTI line interruptUSBWakeup_IRQHandler,// USB wakeup from suspend through EXTI line interrupt
TIM8_BRK_IRQHandler,// TIM8 break interrupt
TIM8_UP_IRQHandler,// TIM8 update interrupt
TIM8_TRG_COM_IRQHandler,// TIM8 trigger and commutation interrupts
TIM8_CC_IRQHandler,// TIM8 capture compare interrupt
ADC3_IRQHandler,// ADC3 global interrupt
FSMC_IRQHandler,// FSMC global interrupt
SDIO_IRQHandler,// SDIO global interrupt
TIM5_IRQHandler,// TIM5 global interrupt
SPI3_IRQHandler,// SPI3 global interrupt
UART4_IRQHandler,// UART4 global interrupt
UART5_IRQHandler,// UART5 global interrupt
TIM6_IRQHandler,// TIM6 global interrupt
TIM7_IRQHandler,// TIM7 global interrupt
DMA2_Channel1_IRQHandler,// DMA2 Channel1 global interrupt
DMA2_Channel2_IRQHandler,// DMA2 Channel2 global interrupt
DMA2_Channel3_IRQHandler,// DMA2 Channel3 global interrupt
DMA2_Channel4_5_IRQHandler // DMA2 Channel4 and DMA2 Channel5 global interrupts};/** Default exception/interrupt handler */void __attribute__ ((section(".after_vectors"), noreturn)) __Default_Handler(void){#ifdef DEBUG
while(1);#else
NVIC_SystemReset();while(1);#endif}/** Reset handler */void __attribute__ ((section(".after_vectors"), noreturn))Reset_Handler(void){
_start();while(1);}
Was passiert hier. - Zuerst deklariere ich meine _start-Funktion, damit sie unten verwendet werden kann. - Ich deklariere einen Standard-Handler für alle Interrupts und den Reset-Handler. - Ich deklariere alle Interrupt-Handler, die für meine MCU benötigt werden. Beachten Sie, dass diese Funktionen nur Aliase für den Standardhandler sind, dh, wenn einer von ihnen aufgerufen wird, wird stattdessen der Standardhandler aufgerufen. Außerdem werden sie als Woche deklariert, sodass Sie sie durch Ihren Code überschreiben können. Wenn Sie einen der Handler benötigen, müssen Sie ihn in Ihrem Code neu deklarieren, und Ihr Code wird verknüpft. Wenn Sie keine benötigen, gibt es einfach eine Standardeinstellung, und Sie müssen nichts tun. Der Standardhandler sollte so strukturiert sein, dass er Ihnen beim Debuggen Ihres Codes oder beim Wiederherstellen des Systems hilft, wenn Ihre Anwendung einen Handler benötigt, den Sie jedoch nicht implementieren. - Ich erhalte das im Linker-Skript deklarierte __stack-Symbol. Es wird in der Vektortabelle benötigt. - Ich definiere die Tabelle selbst. Beachten Sie, dass der erste Eintrag ein Zeiger auf den Anfang des Stapels ist und die anderen Zeiger auf die Handler sind. - Abschließend stelle ich eine einfache Implementierung für den Standardhandler und den Rücksetzhandler bereit. Beachten Sie, dass der Rücksetz-Handler derjenige ist, der nach dem Zurücksetzen aufgerufen wird und der den Startcode aufruft.
Beachten Sie, dass das Attribut ((section ())) in der Vektortabelle unbedingt erforderlich ist, damit der Linker die Tabelle an der richtigen Position platziert (normalerweise Adresse 0x00000000).
Welche Änderungen sind an der obigen Datei erforderlich?
Fügen Sie die CMSIS-Datei Ihrer MCU hinzu
Wenn Sie das Linkerskript ändern, ändern Sie die Abschnittsnamen
Ändern Sie die Vektortabelleneinträge entsprechend Ihrer MCU
Ändern Sie die Handler-Prototypen entsprechend Ihrer MCU
Systemaufrufe
Da ich newlib verwende, müssen Sie die Implementierungen einiger Funktionen bereitstellen. Sie können printf, scanf usw. implementieren, diese werden jedoch nicht benötigt. Persönlich biete ich nur Folgendes an:
_sbrk die von malloc benötigt wird. (Keine Modifikationen erforderlich)
#include<sys/types.h>#include<errno.h>caddr_t __attribute__((used)) _sbrk(int incr){externchar __heap_start__;// Defined by the linker.externchar __heap_end__;// Defined by the linker.staticchar* current_heap_end;char* current_block_address;if(current_heap_end ==0){
current_heap_end =&__heap_start__;}
current_block_address = current_heap_end;// Need to align heap to word boundary, else will get// hard faults on Cortex-M0. So we assume that heap starts on// word boundary, hence make sure we always add a multiple of// 4 to it.
incr =(incr +3)&(~3);// align value to 4if(current_heap_end + incr >&__heap_end__){// Heap has overflowed
errno = ENOMEM;return(caddr_t)-1;}
current_heap_end += incr;return(caddr_t) current_block_address;}
_exit, was nicht nötig ist, aber ich mag die Idee. (Möglicherweise müssen Sie nur das CMSIS-Include ändern.)
#include<stdint.h>#include"stm32f10x.h"#include"gpio.h"#include"flash.h"/** Main program entry point. */externint main(void);/** Exit system call. */externvoid _exit(int code);/** Initializes the data section. */staticvoid __attribute__((always_inline)) __initialize_data (unsignedint* from,unsignedint* region_begin,unsignedint* region_end);/** Initializes the BSS section. */staticvoid __attribute__((always_inline)) __initialize_bss (unsignedint* region_begin,unsignedint* region_end);/** Start-up code. */void __attribute__ ((section(".after_vectors"), noreturn, used)) _start(void);void _start (void){//Before switching on the main oscillator and the PLL,//and getting to higher and dangerous frequencies,//configuration of the flash controller is necessary.//Enable the flash prefetch buffer. Can be achieved when CCLK//is lower than 24MHz.Flash_prefetchBuffer(1);//Set latency to 2 clock cycles. Necessary for setting the clock//to the maximum 72MHz.Flash_setLatency(2);// Initialize hardware right after configuring flash, to switch//clock to higher frequency and have the rest of the//initializations run faster.SystemInit();// Copy the DATA segment from Flash to RAM (inlined).
__initialize_data(&__textdata__,&__data_start__,&__data_end__);// Zero fill the BSS section (inlined).
__initialize_bss(&__bss_start__,&__bss_end__);//Core is running normally, RAM and FLASH are initialized//properly, now the system must be fully functional.//Update the SystemCoreClock variable.SystemCoreClockUpdate();// Call the main entry point, and save the exit code.int code = main();//Main should never return. If it does, let the system exit gracefully.
_exit (code);// Should never reach this, _exit() should have already// performed a reset.while(1);}staticinlinevoid __initialize_data (unsignedint* from,unsignedint* region_begin,unsignedint* region_end){// Iterate and copy word by word.// It is assumed that the pointers are word aligned.unsignedint*p = region_begin;while(p < region_end)*p++=*from++;}staticinlinevoid __initialize_bss (unsignedint* region_begin,unsignedint* region_end){// Iterate and clear word by word.// It is assumed that the pointers are word aligned.unsignedint*p = region_begin;while(p < region_end)*p++=0;}
Was passiert hier.
Zuerst konfiguriere ich den Flash-Controller, da dieser von meiner MCU benötigt wird, bevor ich die Frequenz ändere. Sie können hier jeden sehr einfachen und für Ihren Hardware-Code erforderlichen Code hinzufügen. Beachten Sie, dass der hier platzierte Code nicht auf Globals im RAM zugreifen sollte, da diese noch nicht initialisiert sind. Beachten Sie auch, dass die MCU immer noch mit einer niedrigen Frequenz arbeitet, rufen Sie also nur die unbedingt benötigten auf.
Dann rufe ich die CMSIS-Funktion SystemInit () auf. Das ist etwas portabel, deshalb benutze ich es. Es behandelt hauptsächlich den Kern, nicht die MCU selbst, in meinen spezifischen Implementierungen aktiviert es nur die PLL und setzt die MCU auf ihre endgültige Hochfrequenz. Sie können es durch Ihren effizienteren Code ersetzen, aber es ist keine große Sache.
Der nächste Schritt, jetzt, da die MCU schnell ist, besteht darin, den RAM zu initialisieren. Ziemlich einfach.
Die MCU ist jetzt normal in Betrieb. Ich rufe einfach die CMSIS-Funktion SystemCoreClockUpdate () auf, da ich in meinem Code die SystemCoreClock-Variable verwende, aber sie wird nicht benötigt, sondern nur meine Präferenz.
Zum Schluss rufe ich die Hauptfunktion auf. Ihre Anwendung wird jetzt normal ausgeführt.
Wenn der main zurückkehrt, empfiehlt es sich, _exit () aufzurufen, um das System neu zu starten.
Aufgrund der Länge der Antwort kann es beängstigend erscheinen. Während Sie versuchen, das zu verstehen, müssen Sie möglicherweise Ihre Toolchain bekämpfen, damit sie das macht, was Sie wollen. Keine Sorge, endlich werden Sie verstehen, wie einfach und vielseitig der obige Code ist. Möglicherweise können Sie es an einem Abend auf eine beliebige ARM-MCU portieren, wenn Sie wissen, wie die Dinge funktionieren. Oder Sie können es ganz einfach erweitern, um Ihre persönlichen Bedürfnisse zu erfüllen.
Fotis Panagiotopoulos
Ich denke, Sie möchten vielleicht anrufen __initialize_data()und das __initialize_bss()früher als Sie, obwohl das mit langsamer Geschwindigkeit abläuft. Andernfalls müssen Sie sicherstellen, dass SystemInit()und Ihre Flash_*()Routinen überhaupt keine globalen Werte verwenden.
Pål-Kristian Engstad
Das ist mehr als ich fragen könnte! Danke für die ausführliche Antwort!
John
Es ist wirklich schön, das alles an einem Ort zu haben. Vielen Dank für Ihre Zeit und Ihr Wissen!
Bitsmack
@ Pål-Kristian Engstad Genau. Hätte es klarer machen sollen. Ich kann die Antwort bearbeiten, wenn ich Zeit habe, damit die, die den Code kopieren, sicher sind.
Fotis Panagiotopoulos
5
Die Kortex-ms verwenden im Gegensatz zu Armen voller Größe eine Vektortabelle. Sie haben auch keine Modi und Bankregister. Und für Ereignisse / Interrupts entsprechen sie dem ARM-Codierungsstandard. Was bedeutet, dass Sie das absolute Minimum benötigen, jedoch wählen Sie, dass das erste Wort an der Adresse Null der Anfangswert für den Stapelzeiger ist und das zweite Wort die Adresse, zu der beim Zurücksetzen verzweigt werden soll. Sehr einfach mit Montageanweisungen zu tun.
.globl _start
_start:.word 0x20001000.word main
Aber auch hier können Sie tun, was Sie wollen, solange die ersten beiden Wörter die richtigen Werte haben. Beachten Sie, dass für eine Thumb-Adresse zum Verzweigen das lsbit festgelegt ist. Es ist nicht wirklich ein Teil der Adresse, es zeigt nur an, dass wir im Daumenmodus sind (bleiben).
Sie müssen diese vier Bytes mit etwas belegen, aber wenn Sie einen anderen Code verwenden, um den Stapelzeiger zu setzen, müssen Sie die Vektortabelle nicht verwenden, sie lädt das, was Sie dort ablegen, und Sie können ihn jederzeit ändern. Es gibt nur einen Stapelzeiger, allerdings nicht wie bei großen / älteren Armen.
Das Wort Startup ist sehr vage, daher hätte ich es bereits mit diesen Anweisungen behandeln können, oder Sie könnten noch viele tausend Zeilen C-Code benötigen, um den Start Ihres Mikrocontrollers abzuschließen, je nachdem, was Sie meinten.
Insbesondere bei einem STM32 müssen Sie die Peripheriegeräte, die Sie verwenden möchten, im Takt aktivieren. Sie müssen sie für das konfigurieren, was sie tun sollen, und so weiter. Nicht wirklich anders als jeder andere Mikrocontroller, außer dass jeder Anbieter und jede Produktfamilie eine andere Logik hat und auf unterschiedliche Weise initialisiert.
Startdateien, die von einem Hersteller stammen, unterstützen normalerweise eine C-Compiler-Umgebung. Dies beinhaltet eine Menge Dinge, die mit dem Einrichten der Speicherkarte, dem Null-Initialisieren des Speichers, dem Initialisieren von Variablen und dem Einrichten des Startvorgangs (Vektor zurücksetzen) zusammenhängen.
Einige Startdateien enthalten auch das Einrichten der Interruptvektoren und des Interruptcontrollers, obwohl einige Umgebungen, mit denen ich gearbeitet habe, dies in einer separaten Assemblersprachendatei haben.
Manchmal wird in einer Startdatei Komplexität festgestellt, da je nach CPU-Architektur unterschiedliche Modelle unterstützt werden. Die Modelle könnten Dinge wie "kompakt" und "groß" heißen.
Minimal in der Art, wie Sie gefragt haben, wird fast ausschließlich auf das getrieben, was Sie brauchen. Es kommt also wirklich darauf an, Ihre Architektur, die benötigte Umgebung und die Funktionsweise Ihrer Plattform vollständig zu verstehen. Dann können Sie entweder die vom Hersteller bereitgestellten Dateien nach Ihren Wünschen zuschneiden oder Ihre eigenen Dateien von Grund auf neu schreiben.
Wenn Sie jedoch beabsichtigen, Code in C zu schreiben, lassen Sie den Startcode am besten in Ruhe und richten Sie die Dinge für das Programmiermodell ein und konzentrieren Sie Ihren Code auf main ().
Antworten:
Möglicherweise möchten Sie den vom Hersteller bereitgestellten Startcode nicht verwenden. Es gibt nur wenige Gründe, warum dies so ist:
Erstellen Sie effizienteren oder weniger aufgeblähten Code. Haben Sie eine spezielle Anforderung, die der Herstellercode nicht erfüllt. Sie möchten wissen, wie Dinge funktionieren. Sie möchten eine Art universellen Code, der in vielen verschiedenen MCUs verwendet werden kann. Sie möchten die vollständige Kontrolle über den Prozess. etc..
Das Folgende gilt nur für C-Programme (kein C ++, Ausnahmen usw.) und Cortex M-Mikrocontroller (unabhängig von Marke / Modell). Außerdem gehe ich davon aus, dass Sie GCC verwenden, obwohl es möglicherweise keinen oder nur geringen Unterschied zu anderen Compilern gibt. Endlich benutze ich newlib.
Linker-Skript
Als Erstes müssen Sie ein Linker-Skript erstellen. Sie müssen Ihrem Compiler mitteilen, wie die Dinge im Speicher angeordnet werden sollen. Ich werde nicht näher auf das Linker-Skript eingehen, da es ein eigenständiges Thema ist.
Sie können das bereitgestellte Linker-Skript direkt verwenden. Einige Dinge zu beachten:
Dies ist eine vereinfachte Version des von mir verwendeten Linker-Skripts. Während des Stripping-Downs habe ich möglicherweise Fehler in den Code eingefügt. Bitte überprüfen Sie ihn noch einmal.
Da ich es für andere MCUs als Sie verwende, müssen Sie das MEMORY-Layout an Ihre Bedürfnisse anpassen.
Möglicherweise müssen Sie die unten verlinkten Bibliotheken ändern, um sie mit Ihren eigenen zu verknüpfen. Hier verlinkt es gegen newlib.
Vektortabelle
Sie müssen in Ihren Code eine Vektortabelle einfügen. Dies ist einfach eine Nachschlagetabelle mit Funktionszeigern, auf die die Hardware im Falle eines Interrupts automatisch zugreift. Dies ist in C ziemlich einfach zu bewerkstelligen.
Schauen Sie sich die folgende Datei an. Dies gilt für die STM32F103C8-MCU, ist jedoch sehr einfach an Ihre Anforderungen anzupassen.
Was passiert hier. - Zuerst deklariere ich meine _start-Funktion, damit sie unten verwendet werden kann. - Ich deklariere einen Standard-Handler für alle Interrupts und den Reset-Handler. - Ich deklariere alle Interrupt-Handler, die für meine MCU benötigt werden. Beachten Sie, dass diese Funktionen nur Aliase für den Standardhandler sind, dh, wenn einer von ihnen aufgerufen wird, wird stattdessen der Standardhandler aufgerufen. Außerdem werden sie als Woche deklariert, sodass Sie sie durch Ihren Code überschreiben können. Wenn Sie einen der Handler benötigen, müssen Sie ihn in Ihrem Code neu deklarieren, und Ihr Code wird verknüpft. Wenn Sie keine benötigen, gibt es einfach eine Standardeinstellung, und Sie müssen nichts tun. Der Standardhandler sollte so strukturiert sein, dass er Ihnen beim Debuggen Ihres Codes oder beim Wiederherstellen des Systems hilft, wenn Ihre Anwendung einen Handler benötigt, den Sie jedoch nicht implementieren. - Ich erhalte das im Linker-Skript deklarierte __stack-Symbol. Es wird in der Vektortabelle benötigt. - Ich definiere die Tabelle selbst. Beachten Sie, dass der erste Eintrag ein Zeiger auf den Anfang des Stapels ist und die anderen Zeiger auf die Handler sind. - Abschließend stelle ich eine einfache Implementierung für den Standardhandler und den Rücksetzhandler bereit. Beachten Sie, dass der Rücksetz-Handler derjenige ist, der nach dem Zurücksetzen aufgerufen wird und der den Startcode aufruft.
Beachten Sie, dass das Attribut ((section ())) in der Vektortabelle unbedingt erforderlich ist, damit der Linker die Tabelle an der richtigen Position platziert (normalerweise Adresse 0x00000000).
Welche Änderungen sind an der obigen Datei erforderlich?
Systemaufrufe
Da ich newlib verwende, müssen Sie die Implementierungen einiger Funktionen bereitstellen. Sie können printf, scanf usw. implementieren, diese werden jedoch nicht benötigt. Persönlich biete ich nur Folgendes an:
_sbrk die von malloc benötigt wird. (Keine Modifikationen erforderlich)
_exit, was nicht nötig ist, aber ich mag die Idee. (Möglicherweise müssen Sie nur das CMSIS-Include ändern.)
Startcode
Endlich der Startcode!
Was passiert hier.
Mehr oder weniger das ist es.
quelle
__initialize_data()
und das__initialize_bss()
früher als Sie, obwohl das mit langsamer Geschwindigkeit abläuft. Andernfalls müssen Sie sicherstellen, dassSystemInit()
und IhreFlash_*()
Routinen überhaupt keine globalen Werte verwenden.Die Kortex-ms verwenden im Gegensatz zu Armen voller Größe eine Vektortabelle. Sie haben auch keine Modi und Bankregister. Und für Ereignisse / Interrupts entsprechen sie dem ARM-Codierungsstandard. Was bedeutet, dass Sie das absolute Minimum benötigen, jedoch wählen Sie, dass das erste Wort an der Adresse Null der Anfangswert für den Stapelzeiger ist und das zweite Wort die Adresse, zu der beim Zurücksetzen verzweigt werden soll. Sehr einfach mit Montageanweisungen zu tun.
Aber auch hier können Sie tun, was Sie wollen, solange die ersten beiden Wörter die richtigen Werte haben. Beachten Sie, dass für eine Thumb-Adresse zum Verzweigen das lsbit festgelegt ist. Es ist nicht wirklich ein Teil der Adresse, es zeigt nur an, dass wir im Daumenmodus sind (bleiben).
Sie müssen diese vier Bytes mit etwas belegen, aber wenn Sie einen anderen Code verwenden, um den Stapelzeiger zu setzen, müssen Sie die Vektortabelle nicht verwenden, sie lädt das, was Sie dort ablegen, und Sie können ihn jederzeit ändern. Es gibt nur einen Stapelzeiger, allerdings nicht wie bei großen / älteren Armen.
Das Wort Startup ist sehr vage, daher hätte ich es bereits mit diesen Anweisungen behandeln können, oder Sie könnten noch viele tausend Zeilen C-Code benötigen, um den Start Ihres Mikrocontrollers abzuschließen, je nachdem, was Sie meinten.
Insbesondere bei einem STM32 müssen Sie die Peripheriegeräte, die Sie verwenden möchten, im Takt aktivieren. Sie müssen sie für das konfigurieren, was sie tun sollen, und so weiter. Nicht wirklich anders als jeder andere Mikrocontroller, außer dass jeder Anbieter und jede Produktfamilie eine andere Logik hat und auf unterschiedliche Weise initialisiert.
quelle
Startdateien, die von einem Hersteller stammen, unterstützen normalerweise eine C-Compiler-Umgebung. Dies beinhaltet eine Menge Dinge, die mit dem Einrichten der Speicherkarte, dem Null-Initialisieren des Speichers, dem Initialisieren von Variablen und dem Einrichten des Startvorgangs (Vektor zurücksetzen) zusammenhängen.
Einige Startdateien enthalten auch das Einrichten der Interruptvektoren und des Interruptcontrollers, obwohl einige Umgebungen, mit denen ich gearbeitet habe, dies in einer separaten Assemblersprachendatei haben.
Manchmal wird in einer Startdatei Komplexität festgestellt, da je nach CPU-Architektur unterschiedliche Modelle unterstützt werden. Die Modelle könnten Dinge wie "kompakt" und "groß" heißen.
Minimal in der Art, wie Sie gefragt haben, wird fast ausschließlich auf das getrieben, was Sie brauchen. Es kommt also wirklich darauf an, Ihre Architektur, die benötigte Umgebung und die Funktionsweise Ihrer Plattform vollständig zu verstehen. Dann können Sie entweder die vom Hersteller bereitgestellten Dateien nach Ihren Wünschen zuschneiden oder Ihre eigenen Dateien von Grund auf neu schreiben.
Wenn Sie jedoch beabsichtigen, Code in C zu schreiben, lassen Sie den Startcode am besten in Ruhe und richten Sie die Dinge für das Programmiermodell ein und konzentrieren Sie Ihren Code auf main ().
quelle