Minimalny kod wymagany do uruchomienia STM32F4?

14

Jaki jest najbardziej wydajny sposób / minimalny kod wymagany do uruchomienia STM32F4? Pliki startowe, które pochodzą z ST, wydają się zawierać dużo niepotrzebnego kodu.

Jan
źródło
Usuń to, co uważasz za „niepotrzebne”, i spróbuj je uruchomić ...
Tyler,
1
kod dostawcy chipów próbuje być uniwersalny, co oznacza, że ​​nie pasuje do nikogo dobrze. Z definicji zawsze będą one rozdęte, ponieważ próbują obsłużyć wszystkie główne przypadki użycia wszystkich urządzeń peryferyjnych i funkcji, które są skłonne wesprzeć. Skorzystaj z ich kodu, a uzyskasz wsparcie od nich i innych osób korzystających z tego kodu online. Idź własną drogą, a czerpiesz korzyści z rozmiaru i prędkości, ale to od Ciebie zależy, czy wymyślisz to koło.
old_timer
Lub, jak powiedział Tyler, wytnij rzeczy, których nie chcesz / potrzebujesz.
old_timer

Odpowiedzi:

25

Możesz nie chcieć używać kodu startowego dostarczonego przez dostawcę. Jest kilka osób, które tak robią:

Utwórz bardziej wydajny lub mniej rozdęty kod. Mają specjalny wymóg, że kod dostawcy nie spełnia. Chcesz wiedzieć, jak działają rzeczy. Chcesz jakiegoś uniwersalnego kodu, który będzie używany w wielu różnych MCU. Chcesz mieć całkowitą kontrolę nad sobą. itp..

Poniższe informacje dotyczą tylko programów w języku C (bez C ++, wyjątków itp.) I mikrokontrolerów Cortex M (niezależnie od marki / modelu). Zakładam również, że używasz GCC, chociaż może nie być żadnej różnicy z innymi kompilatorami. Wreszcie używam newlib.

Skrypt Linkera

Pierwszą rzeczą do zrobienia jest stworzenie skryptu linkera. Musisz powiedzieć swojemu kompilatorowi, jak uporządkować rzeczy w pamięci. Nie będę wchodził w szczegóły dotyczące skryptu linkera, ponieważ jest to temat sam w sobie.

/*
 * 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

}

Możesz bezpośrednio użyć dostarczonego skryptu linkera. Kilka rzeczy do zapamiętania:

  • To jest uproszczona wersja skryptu linkera, którego używam. Podczas usuwania kodu mogę wprowadzać błędy do kodu, proszę dokładnie to sprawdzić.

  • Ponieważ używam go do innych MCU niż ty, musisz zmienić układ MEMORY, aby pasował do twojego.

  • Może być konieczna zmiana bibliotek połączonych poniżej, aby łączyły się z własnymi. Tutaj łączy się z newlib.

Tabela wektorów

Musisz dołączyć do kodu tabelę wektorów. Jest to po prostu tabela wskaźników wskaźników funkcji, do której sprzęt przeskoczy automatycznie w przypadku przerwania. Jest to dość łatwe do zrobienia w C.

Spójrz na następujący plik. Dotyczy to MCU STM32F103C8, ale bardzo łatwo można go dostosować do swoich potrzeb.

#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);
}

Co tu się dzieje. - Najpierw deklaruję moją funkcję _start, aby można było jej użyć poniżej. - Deklaruję domyślną procedurę obsługi dla wszystkich przerwań i procedurę resetowania - Deklaruję wszystkie procedury obsługi przerwań potrzebne dla mojego MCU. Zauważ, że te funkcje są tylko aliasami do domyślnego modułu obsługi, tzn. Gdy dowolna z nich zostanie wywołana, zamiast tego zostanie wywołany domyślny moduł obsługi. Są one również deklarowane jako tydzień, więc możesz zastąpić je kodem. Jeśli potrzebujesz któregokolwiek z programów obsługi, ponownie umieść go w kodzie, a kod zostanie połączony. Jeśli nie potrzebujesz żadnego z nich, jest po prostu domyślny i nie musisz nic robić. Domyślny moduł obsługi powinien mieć taką strukturę, że jeśli aplikacja potrzebuje modułu obsługi, ale go nie zaimplementujesz, pomoże ci to w debugowaniu kodu lub w odzyskaniu systemu, jeśli jest na wolności. - Otrzymuję symbol __stack zadeklarowany w skrypcie linkera. Jest potrzebny w tabeli wektorów. - Definiuję sam stół. Zauważ, że pierwszy wpis jest wskaźnikiem na początku stosu, a pozostałe są wskaźnikami do procedur obsługi. - Wreszcie zapewniam prostą implementację domyślnego modułu obsługi i modułu resetowania. Zauważ, że procedura resetowania jest wywoływana po resecie i wywołuje kod startowy.

Należy pamiętać, że atrybut ((sekcja ())) w tabeli wektorów jest absolutnie potrzebny, aby linker umieścił tabelę we właściwej pozycji (zwykle adres 0x00000000).

Jakie modyfikacje są potrzebne w powyższym pliku.

  • Dołącz plik CMSIS swojego MCU
  • Jeśli zmodyfikujesz skrypt linkera, zmień nazwy sekcji
  • Zmień wpisy tabeli wektorów, aby pasowały do ​​MCU
  • Zmień prototypy modułów obsługi, aby pasowały do ​​MCU

Połączenia systemowe

Ponieważ używam newlib, wymaga to implementacji niektórych funkcji. Możesz zaimplementować printf, scanf itp., Ale nie są one potrzebne. Osobiście zapewniam tylko następujące elementy:

_sbrk, którego potrzebuje Malloc. (Nie wymaga modyfikacji)

#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, który nie jest potrzebny, ale podoba mi się ten pomysł. (Konieczne może być jedynie zmodyfikowanie uwzględnienia CMSIS).

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


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

    NVIC_SystemReset();

    while(1);
}

Kod startowy

Wreszcie kod startowy!

#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;
}

Co tu się dzieje.

  • Najpierw konfiguruję kontroler Flash, ponieważ jest to wymagane przez mój MCU, przed zmianą częstotliwości. Możesz dodać tutaj bardzo podstawowy i potrzebny kod sprzętowy. Zauważ, że umieszczony tutaj kod nie powinien mieć dostępu do globali w pamięci RAM, ponieważ nie zostały one jeszcze zainicjowane. Zauważ też, że MCU nadal działa na niskiej częstotliwości, więc zadzwoń tylko absolutnie potrzebny.
  • Następnie wywołuję funkcję CMSIS SystemInit (). Jest to trochę przenośne, dlatego go używam. Przeważnie obsługuje rdzeń, a nie MCU ot self, w moich konkretnych implementacjach włącza tylko PLL i ustawia MCU na końcową wysoką częstotliwość. Możesz zastąpić go bardziej wydajnym kodem, ale to nie jest wielka sprawa.
  • Następnym krokiem, teraz, gdy MCU jest szybki, jest inicjalizacja pamięci RAM. Całkiem proste.
  • MCU działa teraz normalnie. Po prostu wywołuję funkcję CMSIS SystemCoreClockUpdate (), ponieważ używam w swoim kodzie zmiennej SystemCoreClock, ale nie jest to potrzebne, tylko moje preferencje.
  • Wreszcie wywołuję główną funkcję. Twoja aplikacja działa teraz normalnie.
  • Jeśli główny zwraca, wywołanie _exit () jest dobrą praktyką, aby zrestartować system.

Mniej więcej to jest to.

Fotis Panagiotopoulos
źródło
4
Ze względu na długość odpowiedzi może się to wydawać przerażające. Również starając się to zrozumieć, być może będziesz musiał walczyć z łańcuchem narzędzi, aby zrobić to, co robisz. Nie martw się, w końcu zrozumiesz, jak prosty i wszechstronny jest powyższy kod. Możesz być w stanie przenieść go na dowolną jednostkę MCU ARM, tylko wieczorem, gdy zrozumiesz, jak to działa. Możesz też z łatwością ulepszyć go, zaspokajając własne potrzeby.
Fotis Panagiotopoulos,
Myślę, że może chcesz zadzwonić __initialize_data()i __initialize_bss()wcześniej niż ty, mimo że będzie działać w zwolnionym tempie. W przeciwnym razie musisz się upewnić, że SystemInit()twoje Flash_*()procedury w ogóle nie używają globałów.
Pål-Kristian Engstad
To więcej niż mógłbym prosić! Dzięki za szczegółową odpowiedź!
John
Jest naprawdę miło mieć to wszystko w jednym miejscu. Dziękujemy za poświęcony czas i wiedzę!
bitsmack
@ Pål-Kristian Engstad Dokładnie. Powinien był to wyjaśnić. Mogę edytować odpowiedź, kiedy mam wolny czas, aby osoby kopiujące i wklejające kod były bezpieczne.
Fotis Panagiotopoulos
5

Kora-ms w przeciwieństwie do ramion pełnowymiarowych, użyj tabeli wektorów. Nie mają również trybów i rejestrów bankowych. A w przypadku zdarzeń / przerwań są one zgodne ze standardem kodowania ARM. Co oznacza absolutne minimum, którego potrzebujesz, jednak decydujesz się na to, aby pierwsze słowo pod adresem zero było wartością początkową wskaźnika stosu, a drugie słowo to adres, na który należy rozgałęzić się po zresetowaniu. Bardzo łatwe do wykonania przy użyciu dyrektyw montażowych.

.globl _start
_start:
.word 0x20001000
.word main

Ale znowu możesz robić, co chcesz, o ile pierwsze dwa słowa mają odpowiednie wartości. Zauważ, że adres kciuka do rozgałęzienia ma ustawiony lsbit. To nie jest tak naprawdę część adresu, to tylko wskazuje, że jesteśmy (pozostajemy) w trybie kciuka.

Musisz zużywać te cztery bajty, ale jeśli masz jakiś inny kod, którego używasz do ustawiania wskaźnika stosu, nie musisz używać tabeli wektorów, załaduje to, co tam umieścisz, a następnie zawsze możesz to zmienić. Jest tylko jeden wskaźnik stosu, ale nie tak jak ramiona pełnowymiarowe / starsze.

Słowo „startup” jest bardzo niejasne, więc mógłbym opisać je już tymi dyrektywami lub zajęłoby ci wiele tysięcy wierszy kodu C, aby zakończyć uruchamianie mikrokontrolera w zależności od tego, co miałeś na myśli.

W przypadku STM32 musisz włączyć urządzenia peryferyjne, których chcesz używać, musisz skonfigurować je tak, aby działały i tak dalej. Tak naprawdę nie różni się niczym od żadnego innego mikrokontrolera, z tym wyjątkiem, że każdy dostawca i rodzina produktów ma inną logikę i inicjuje się w inny sposób.

old_timer
źródło
2

Pliki startowe pochodzące od producenta są zwykle zaprojektowane do obsługi środowiska kompilatora C. Będzie to obejmowało całą masę rzeczy związanych z konfigurowaniem mapy pamięci, zerowaniem inicjalizacji pamięci, inicjowaniem zmiennych i konfigurowaniem uruchamiania (wektor resetowania).

Niektóre pliki startowe obejmują również konfigurację wektorów przerwań i kontrolera przerwań, chociaż niektóre środowiska, z którymi pracowałem, mają to w osobnym pliku języka asemblera.

Czasami w pliku startowym widać złożoność, ponieważ obsługiwane są różne modele oparte na architekturze procesora. Modele można nazwać „kompaktowymi” i „dużymi”.

Minimalny sposób, o który prosisz, będzie prawie całkowicie zależał od tego, czego potrzebujesz. Tak naprawdę sprowadza się to do pełnego zrozumienia architektury, potrzebnego środowiska i sposobu działania platformy. Następnie możesz skrócić pliki dostarczone przez dostawcę, aby dostosować je do swoich potrzeb, LUB napisać własne od zera.

Ale wszystko to powiedziawszy, jeśli zamierzasz napisać kod w C, najlepiej zostawić kod startowy w spokoju i po prostu skonfigurować elementy dla modelu programowania i skoncentrować swój kod jak na main ().

Michael Karas
źródło