Minimaler Code zum Starten eines STM32F4 erforderlich?

Was ist der effizienteste Weg/minimaler Code, der zum Starten eines STM32F4 erforderlich ist? Die Startdateien, die von ST stammen, scheinen viel unnötigen Code zu enthalten.

Entfernen Sie, was Sie für "unnötig" halten, und versuchen Sie, es auszuführen ...
Der Code der Chipanbieter versucht, eine Einheitsgröße zu sein, 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 bewältigen, die sie zu unterstützen bereit sind. Verwenden Sie ihren Code und Sie profitieren von der Unterstützung durch sie und andere Online, die diesen Code verwenden. Gehe deinen eigenen Weg und profitiere von Größe und Geschwindigkeit, aber es liegt hauptsächlich an dir, das Rad neu zu erfinden.
Oder wie Tyler sagte, schneide das Zeug aus, das du nicht willst/brauchst.

Antworten (3)

Möglicherweise möchten Sie den vom Anbieter bereitgestellten Startcode nicht verwenden. Es gibt wenige Gründe, warum Menschen dies tun:

Erstellen Sie effizienteren oder weniger aufgeblähten Code. Sie haben eine spezielle Anforderung, die der Lieferantencode nicht erfüllt. Sie wollen wissen, wie Dinge funktionieren. Sie möchten eine Art universellen Code, der in vielen verschiedenen MCUs verwendet werden kann. Sie wollen die totale 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). Ich gehe auch davon aus, dass Sie GCC verwenden, obwohl es möglicherweise keinen oder nur einen geringen Unterschied zu anderen Compilern gibt. Schließlich verwende ich newlib.

Linker-Skript

Als erstes müssen Sie ein Linker-Skript erstellen. Sie müssen Ihrem Compiler mitteilen, wie er die Dinge im Speicher anordnen soll. Ich werde nicht näher auf das Linker-Skript eingehen, da es ein eigenes 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 Abbaus kann ich Fehler in den Code einführen, bitte überprüfen Sie ihn noch einmal.

  • Da ich es für andere MCUs als Sie verwende, müssen Sie das MEMORY-Layout an Ihr eigenes anpassen.

  • Möglicherweise müssen Sie die unten verlinkten Bibliotheken ändern, um sie mit Ihren eigenen zu verknüpfen. Hier wird gegen newlib verlinkt.

Vektortabelle

Sie müssen in Ihren Code eine Vektortabelle aufnehmen. Dies ist einfach eine Nachschlagetabelle von Funktionszeigern, zu denen die Hardware im Falle eines Interrupts automatisch springt. Dies ist in C ziemlich einfach zu bewerkstelligen.

Sehen Sie sich die folgende Datei an. Dies ist für die STM32F103C8 MCU, aber es ist sehr einfach, sie an Ihre Bedürfnisse anzupassen.

#include "stm32f10x.h"
#include "debug.h"

//Start-up code.
extern void __attribute__((noreturn, weak)) _start (void);

// Default interrupt handler
void __attribute__ ((section(".after_vectors"), noreturn)) __Default_Handler(void);

// Reset handler
void __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 */
void HardFault_Handler(void) __attribute__ ((interrupt, weak));

/** Memory management */
void MemManage_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));

/** Pre-fetch fault, memory access fault */
void BusFault_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));

/** Undefined instruction or illegal state */
void UsageFault_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 */
void DebugMon_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));

/** Pendable request for system service */
void PendSV_Handler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));

/** System tick timer */
void SysTick_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 */
void RTCAlarm_IRQHandler(void) __attribute__ ((interrupt, weak, alias("__Default_Handler")));

/** USB wakeup from suspend through EXTI line interrupt */
void USBWakeup_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.
extern unsigned int __stack;

// Typedef for the vector table entries.
typedef void (* const pHandler)(void);

/** STM32F103 Vector Table */
__attribute__ ((section(".vectors"), used)) pHandler vectors[] =
{
    (pHandler) &__stack,                // The initial stack pointer
    Reset_Handler,                      // The reset handler
    NMI_Handler,                        // The NMI handler
    HardFault_Handler,                  // The hard fault handler

#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
    MemManage_Handler,                  // The MPU fault handler
    BusFault_Handler,// The bus fault handler
    UsageFault_Handler,// The usage fault handler
#else
    0, 0, 0,                  // Reserved
#endif
    0,                                  // Reserved
    0,                                  // Reserved
    0,                                  // Reserved
    0,                                  // Reserved
    SVC_Handler,                        // SVCall handler
#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
    DebugMon_Handler,                   // Debug monitor handler
#else
    0,                    // Reserved
#endif
    0,                                  // Reserved
    PendSV_Handler,                     // The PendSV handler
    SysTick_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] interrupts
    RTCAlarm_IRQHandler,                // RTC alarm through EXTI line interrupt
    USBWakeup_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 Interrupts-Handler, die für meine MCU benötigt werden. Beachten Sie, dass diese Funktionen nur Aliase für den Standard-Handler sind, dh wenn eine von ihnen aufgerufen wird, wird stattdessen der Standard-Handler aufgerufen. Sie werden auch als Woche deklariert, sodass Sie sie mit Ihrem Code überschreiben können. Wenn Sie einen der Handler benötigen, deklarieren Sie ihn erneut in Ihrem Code, und Ihr Code wird verknüpft. Wenn Sie keine davon benötigen, gibt es einfach eine Standardeinstellung, und Sie müssen nichts tun. Der Standard-Handler sollte so strukturiert sein, dass, wenn Ihre Anwendung einen Handler benötigt, Sie ihn aber nicht implementieren, er Ihnen beim Debuggen Ihres Codes hilft oder das System wiederherstellt, wenn es in freier Wildbahn ist. - Ich bekomme das im Linker-Skript deklarierte __stack-Symbol. Es wird in der Vektortabelle benötigt. - Ich definiere den Tisch selbst. Beachten Sie, dass der erste Eintrag ein Zeiger auf den Anfang des Stapels ist und die anderen Zeiger auf die Handler sind. - Schließlich stelle ich eine einfache Implementierung für den Standard-Handler und den Reset-Handler bereit. Beachten Sie, dass der Reset-Handler derjenige ist, der nach dem Zurücksetzen aufgerufen wird und den Startcode aufruft.

Beachten Sie, dass das Attribut ((section())) in der Vektortabelle unbedingt benötigt wird, 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 so, dass sie zu Ihrer MCU passen

Systemaufrufe

Da ich newlib verwende, müssen Sie die Implementierungen einiger Funktionen bereitstellen. Sie können printf, scanf usw. implementieren, aber sie werden nicht benötigt. Ich persönlich biete nur folgendes an:

_sbrk, das von malloc benötigt wird. (Keine Änderungen erforderlich)

#include <sys/types.h>
#include <errno.h>


caddr_t __attribute__((used)) _sbrk(int incr)
{
    extern char __heap_start__; // Defined by the linker.
    extern char __heap_end__; // Defined by the linker.

    static char* 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 4
    if (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 benötigt wird, aber ich mag die Idee. (Möglicherweise müssen Sie nur das CMSIS-Include ändern).

#include <sys/types.h>
#include <errno.h>
#include "stm32f10x.h"


void __attribute__((noreturn, used)) _exit(int code)
{
    (void) code;

    NVIC_SystemReset();

    while(1);
}

Startcode

Endlich der Startcode!

#include <stdint.h>
#include "stm32f10x.h"
#include "gpio.h"
#include "flash.h"


/** Main program entry point. */
extern int main(void);

/** Exit system call. */
extern void _exit(int code);

/** Initializes the data section. */
static void __attribute__((always_inline)) __initialize_data (unsigned int* from, unsigned int* region_begin, unsigned int* region_end);

/** Initializes the BSS section. */
static void __attribute__((always_inline)) __initialize_bss (unsigned int* region_begin, unsigned int* 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);
}

static inline void __initialize_data (unsigned int* from, unsigned int* region_begin, unsigned int* region_end)
{
    // Iterate and copy word by word.
    // It is assumed that the pointers are word aligned.
    unsigned int *p = region_begin;
    while (p < region_end)
        *p++ = *from++;
}

static inline void __initialize_bss (unsigned int* region_begin, unsigned int* region_end)
{
    // Iterate and clear word by word.
    // It is assumed that the pointers are word aligned.
    unsigned int *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 grundlegenden und für Ihre Hardware 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 das absolut Notwendige an.
  • Dann rufe ich die CMSIS-Funktion SystemInit() auf. Das ist etwas tragbar, deshalb benutze ich es. Es behandelt hauptsächlich den Kern, nicht die MCU selbst, in meinen spezifischen Implementierungen aktiviert es nur die PLL und stellt die MCU auf ihre endgültige Hochfrequenz ein. Sie können es durch Ihren effizienteren Code ersetzen, aber es ist keine große Sache.
  • Der nächste Schritt, jetzt wo die MCU schnell ist, ist die Initialisierung des RAM. Ziemlich einfach.
  • Die MCU läuft jetzt normal. Ich rufe einfach die CMSIS-Funktion SystemCoreClockUpdate() auf, da ich in meinem Code die SystemCoreClock-Variable verwende, aber sie wird nicht benötigt, nur meine Präferenz.
  • Schließlich rufe ich die Hauptfunktion auf. Ihre Anwendung wird jetzt normal ausgeführt.
  • Wenn der Main zurückkehrt, ist ein Aufruf von _exit() eine gute Übung, um Ihr System neu zu starten.

Mehr oder weniger ist es das.

Aufgrund der Länge der Antwort kann es beängstigend wirken. Während Sie versuchen, das zu verstehen, müssen Sie möglicherweise Ihre Toolchain bekämpfen, damit sie das tut, was Sie tun. Keine Sorge, endlich werden Sie verstehen, wie einfach und vielseitig der obige Code ist. Möglicherweise können Sie es an nur einem Abend auf jede ARM-MCU portieren, wenn Sie verstehen, wie die Dinge funktionieren. Oder Sie können es erweitern, um Ihre eigenen persönlichen Bedürfnisse zu befriedigen.
Ich denke, Sie möchten vielleicht anrufen __initialize_data()und __initialize_bss()früher als Sie es tun, auch wenn das mit langsamer Geschwindigkeit läuft. Andernfalls müssen Sie sicherstellen, dass SystemInit()Ihre Flash_*()Routinen überhaupt keine Globals verwenden.
Das ist mehr, als ich verlangen könnte! Danke für die ausführliche Antwort!
Es ist wirklich schön, das alles an einem Ort zu haben. Danke für deine Zeit und dein Wissen!
@Pål-Kristian Engstad Genau. Hätte es deutlicher machen sollen. Ich kann die Antwort bearbeiten, wenn ich Freizeit habe, damit diejenigen, die den Code kopieren und einfügen, sicher sind.

Die Cortex-ms verwenden im Gegensatz zu großen Armen einen Vektortisch. Sie haben auch keine Modi und Bankregister. Und für Ereignisse/Unterbrechungen entsprechen sie dem ARM-Codierungsstandard. Das heißt, das absolute Minimum, das Sie benötigen, wie auch immer Sie es wählen, ist das erste Wort bei Adresse Null der Anfangswert für den Stapelzeiger, und das zweite Wort ist die Adresse, zu der beim Zurücksetzen verzweigt werden soll. Sehr einfach mit Assembler-Richtlinien 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 bei einer Thumb-Adresse zum Verzweigen das lsbit gesetzt ist. Es ist nicht wirklich Teil der Adresse, es zeigt nur an, dass wir uns im Daumenmodus befinden (bleiben).

Sie müssen diese vier Bytes mit etwas verbrauchen, aber wenn Sie einen anderen Code haben, den Sie verwenden, um den Stapelzeiger zu setzen, müssen Sie die Vektortabelle nicht verwenden, es wird geladen, was Sie dort abgelegt haben, dann können Sie es jederzeit ändern. Es gibt nur einen Stapelzeiger, jedoch nicht wie bei großen/älteren Armen.

Das Wort Start ist sehr vage, also hätte ich es bereits mit diesen Anweisungen abdecken können, oder es könnten Sie viele tausend weitere 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, takten, 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 andere Weise initialisiert wird.

Startup-Dateien, die von einem Hersteller stammen, sind normalerweise so konzipiert, dass sie eine C-Compiler-Umgebung unterstützen. Dazu gehört eine ganze Menge Dinge, die sich auf das Einrichten der Speicherkarte, das Initialisieren des Speichers auf Null, das Initialisieren von Variablen und das Einrichten des Startvorgangs (Rücksetzvektor) beziehen.

Einige Startdateien enthalten auch das Einrichten der Interrupt-Vektoren und des Interrupt-Controllers, obwohl einige Umgebungen, mit denen ich gearbeitet habe, dies in einer separaten Assembler-Sprachdatei haben.

Manchmal ist eine Startdatei komplex, 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 und Weise, wie Sie gefragt haben, wird fast ausschließlich von dem abhängen, 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 Anbieter bereitgestellten Dateien auf Ihre Bedürfnisse zuschneiden ODER Ihre eigenen von Grund auf neu schreiben.

Aber alles in allem, wenn Sie beabsichtigen, Code in C zu schreiben, lassen Sie am besten den Startcode in Ruhe und richten einfach die Dinge für das Programmiermodell ein und konzentrieren Ihren Code so, dass er bei main() beginnt.