RTOS之UCOS(六)---系统启动与固件移植

一、系统启动

前面介绍了操作系统的工作原理,操作系统可以看作是应用程序与底层硬件中间的管理层,对下管理各硬件设备,对上服务各应用程序。对于应用程序的编写,学习编程语言时大家就不陌生了,如果想编写多线程并发的应用程序,之前也有一个系列介绍:C++多线程并发编程,这里就不介绍上层应用程序的开发了,本文把重点放到操作系统与硬件之间的交互上。

访问硬件主要是靠操作该硬件相关的寄存器实现的,前面介绍ARM中断系统存储管理时有过详细的介绍。要想使硬件设备工作,一般需要RCC(Reset and Clock Control)时钟系统驱动,又由于外设通信速度远低于处理器运算速度,为了不让处理器白白等待外设通信过程而浪费运算资源,还需要有中断系统的支持;要操作外设寄存器,当然也离不开存储器随机访问能力的支持。但对这些硬件外设的访问可以在需要时再进行初始化配置,也即可以在主程序或系统启动之后再访问或管理这些硬件;那么,主程序或系统是如何启动的呢?

不管有没有操作系统,一个硬件平台上运行的系统总有一个main主程序(纯汇编程序除外),操作系统的启动也是在这个main主程序内调用相应的函数实现的。那么,硬件是如何进入main主程序的呢?了解计算机原理和编译原理的朋友应该知道,硬件是不识别C/C++这类高级语言的,硬件实际执行的是机器码,C/C++高级语言又是从main函数开始的,在进入main函数之前就需要更低级的语言引导了。机器码太不直观,没法直接用机器码编写程序,处理器设计者在设计处理器时同时设计了操作该处理器寄存器的指令系统,比如ARM处理器的ARM指令集和Thumb指令集,这些指令集就是比C/C++更低级的汇编语言,由于这些指令集可以直接操作硬件寄存器,从硬件上电到进入main主程序开始执行我们的程序或启动操作系统的任务就要靠这些指令集来完成了。

处理器也属于硬件设备的一种,处理器要想工作也需要上面提到的三个条件支持:时钟系统、中断系统、存储系统。处理器要想执行指令,需要设置RCC驱动时钟;要想响应中断,需要配置中断向量表;要想取指令保存运算数据,需要配置相应支持随机寻址的存储空间。但要执行处理器的指令集来配置这三个系统,处理器需要知道第一条指令的地址PC和运行指令需要的堆栈栈顶地址SP,这个就没法靠指令设置,而只能寄希望于硬件设置了。处理器设计者也确实是这么做的,在处理器上电后,PC与SP寄存器会从硬件设置的固定的物理地址处读取该地址保存的数据并赋值给PC与SP寄存器,然后处理器就可以开始执行第一条指令了,接下来的处理器就按照其指令集编写的程序配置RCC时钟、中断向量表、内存空间等基本环境,最后进入main主程序开始执行主程序内的任务。

1.1 设置系统时钟

还记得前面介绍中断管理时谈到的中断向量表吗?下面再附一段上电后的中断向量表:
上电后的向量表
中断向量表的前两项分别保存了MSP与PC的初始值,处理器刚上电复位后,硬件会自动根据向量表偏移地址(查询VTOR向量表偏移量寄存器,在中断管理中介绍过)找到中断向量表,硬件会自动从向量表首地址(映射地址0,在CM3中实际地址为Flash起始地址0x0800 0000)处读取数据并赋给栈指针SP,然后自动从向量表第二项地址处(CM3中一般为0x0800 0004)读取数据赋给程序计数器PC,开始执行PC地址上存储的指令,PC初始值指向复位向量,所以刚上电后首先执行的是复位指令。

下面以STM32F103为例,看看中断向量表前两项的值是多少:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\startup_stm32f10x_hd.s

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
                                                 
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler
                ...
__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

                AREA    |.text|, CODE, READONLY
                
; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP                

从上面的代码来看,SP的初始值为__initial_sp,最开始的代码Stack_Mem SPACE Stack_Size分配了一段1KB(0x0000 0400)大小、不初始化(NOINIT)、可读写(READWRITE)、2^3=8字节对齐(ALIGN=3)的新内存空间,__initial_sp紧挨其后表示栈的结束地址,也即栈顶地址(别忘了栈一般是由高地址向低地址生长的)。PC的初始值为Reset_Handler,也即开始执行复位处理程序,再继续看Reset_Handler程序段的指令,主要作用是先执行SystemInit函数,再执行__main函数,下面先介绍SystemInit函数代码如下:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\system_stm32f10x.c

/**
  * @brief  Setup the microcontroller system
  *         Initialize the Embedded Flash Interface, the PLL and update the 
  *         SystemCoreClock variable.
  * @note   This function should be used only after reset.
  * @param  None
  * @retval None
  */
void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

#ifdef STM32F10X_CL
  /* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x00FF0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;      
#else
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
    
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* DATA_IN_ExtSRAM */
#endif 

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 
}

从上面的代码来看,SystemInit主要是对时钟系统的操作,如果涉及到中断向量表的偏移,还有对中断向量表的操作,时钟系统主要靠RCC(Reset and Clock Control)相关寄存器配置,下面先介绍下RCC相关寄存器:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
#define RCC_BASE              (AHBPERIPH_BASE + 0x1000)
#define RCC                 ((RCC_TypeDef *) RCC_BASE)

/** 
  * @brief Reset and Clock Control
  */
typedef struct
{
  __IO uint32_t CR;
  __IO uint32_t CFGR;
  __IO uint32_t CIR;
  __IO uint32_t APB2RSTR;
  __IO uint32_t APB1RSTR;
  __IO uint32_t AHBENR;
  __IO uint32_t APB2ENR;
  __IO uint32_t APB1ENR;
  __IO uint32_t BDCR;
  __IO uint32_t CSR;

#ifdef STM32F10X_CL  
  __IO uint32_t AHBRSTR;
  __IO uint32_t CFGR2;
#endif /* STM32F10X_CL */ 

#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)   
  uint32_t RESERVED0;
  __IO uint32_t CFGR2;
#endif /* STM32F10X_LD_VL || STM32F10X_MD_VL || STM32F10X_HD_VL */ 
} RCC_TypeDef;


// Libraries\STM32F10x_StdPeriph_Driver\inc\stm32f10x_rcc.h

/** @defgroup RCC_Exported_Types
  * @{
  */

typedef struct
{
  uint32_t SYSCLK_Frequency;  /*!< returns SYSCLK clock frequency expressed in Hz */
  uint32_t HCLK_Frequency;    /*!< returns HCLK clock frequency expressed in Hz */
  uint32_t PCLK1_Frequency;   /*!< returns PCLK1 clock frequency expressed in Hz */
  uint32_t PCLK2_Frequency;   /*!< returns PCLK2 clock frequency expressed in Hz */
  uint32_t ADCCLK_Frequency;  /*!< returns ADCCLK clock frequency expressed in Hz */
}RCC_ClocksTypeDef;

/** @defgroup RCC_Exported_Functions
  * @{
  */
void RCC_DeInit(void);
void RCC_HSEConfig(uint32_t RCC_HSE);
ErrorStatus RCC_WaitForHSEStartUp(void);
void RCC_AdjustHSICalibrationValue(uint8_t HSICalibrationValue);
void RCC_HSICmd(FunctionalState NewState);
void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul);
void RCC_PLLCmd(FunctionalState NewState);

#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL) || defined (STM32F10X_CL)
 void RCC_PREDIV1Config(uint32_t RCC_PREDIV1_Source, uint32_t RCC_PREDIV1_Div);
#endif

#ifdef  STM32F10X_CL
 void RCC_PREDIV2Config(uint32_t RCC_PREDIV2_Div);
 void RCC_PLL2Config(uint32_t RCC_PLL2Mul);
 void RCC_PLL2Cmd(FunctionalState NewState);
 void RCC_PLL3Config(uint32_t RCC_PLL3Mul);
 void RCC_PLL3Cmd(FunctionalState NewState);
#endif /* STM32F10X_CL */ 

void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
uint8_t RCC_GetSYSCLKSource(void);
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
void RCC_PCLK1Config(uint32_t RCC_HCLK);
void RCC_PCLK2Config(uint32_t RCC_HCLK);
void RCC_ITConfig(uint8_t RCC_IT, FunctionalState NewState);

#ifndef STM32F10X_CL
 void RCC_USBCLKConfig(uint32_t RCC_USBCLKSource);
#else
 void RCC_OTGFSCLKConfig(uint32_t RCC_OTGFSCLKSource);
#endif /* STM32F10X_CL */ 

void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);

#ifdef STM32F10X_CL
 void RCC_I2S2CLKConfig(uint32_t RCC_I2S2CLKSource);                                  
 void RCC_I2S3CLKConfig(uint32_t RCC_I2S3CLKSource);
#endif /* STM32F10X_CL */ 

void RCC_LSEConfig(uint8_t RCC_LSE);
void RCC_LSICmd(FunctionalState NewState);
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
void RCC_RTCCLKCmd(FunctionalState NewState);
void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks);
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

#ifdef STM32F10X_CL
void RCC_AHBPeriphResetCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
#endif /* STM32F10X_CL */ 

void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_BackupResetCmd(FunctionalState NewState);
void RCC_ClockSecuritySystemCmd(FunctionalState NewState);
void RCC_MCOConfig(uint8_t RCC_MCO);
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
void RCC_ClearFlag(void);
ITStatus RCC_GetITStatus(uint8_t RCC_IT);
void RCC_ClearITPendingBit(uint8_t RCC_IT);

RCC相关寄存器与封装的操作函数还是蛮多的,毕竟STM32的时钟树也算比较复杂,不管是处理器还是外设,要工作起来都需要时钟系统的周期方波驱动,毕竟不管是数据运算还是传输,都要涉及高低电平信号的变化或触发,所以时钟系统可以算作处理器与外设工作的心跳,处理器开始工作前需要先配置好时钟系统。

1.2 初始化内存空间

处理器工作除了时钟驱动源,当然还需要数据和指令的存储载体,接下来就要初始化系统运行的存储环境了。还记得前面Reset_Handler函数执行完SystemInit函数后接着继续执行__main函数吗?这里的__main函数可不是我们的主程序入口main函数,两者并不是相同的函数,需要注意区分。__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。

__main函数代码没有查到,这里贴出其反汇编代码如下:

                 __main:

0x08000130 F000F802  BL.W     __scatterload (0x08000138)

0x08000134 F000F83A  BL.W     __rt_entry (0x080001AC)

                 __scatterload:

0x08000138 A00A          ADR      r0,{pc}+4  ; @0x08000164

0x0800013A E8900C00  LDM      r0,{r10-r11}

0x0800013E 4482           ADD      r10,r10,r0

0x08000140 4483           ADD      r11,r11,r0

0x08000142 F1AA0701   SUB      r7,r10,#0x01

                 __scatterload_null:

0x08000146 45DA          CMP      r10,r11

0x08000148 D101           BNE      0x0800014E

0x0800014A F000F82F    BL.W     __rt_entry (0x080001AC)

0x0800014E F2AF0E09    ADR.W    lr,{pc}-0x07  ; @0x08000147

0x08000152 E8BA000F    LDM      r10!,{r0-r3}

0x08000156 F0130F01    TST      r3,#0x01

0x0800015A BF18            IT       NE

0x0800015C 1AFB           SUBNE    r3,r7,r3

0x0800015E F0430301    ORR      r3,r3,#0x01

0x08000162 4718            BX       r3

0x08000164 0B08            DCW      0x0B08

0x08000166 0000            DCW      0x0000

0x08000168 0B28            DCW      0x0B28

0x0800016A 0000            DCW      0x0000

                   __scatterload_copy:

0x0800016C 3A10           SUBS     r2,r2,#0x10

0x0800016E BF24            ITT      CS

0x08000170 C878            LDMCS    r0!,{r3-r6}

0x08000172 C178            STMCS    r1!,{r3-r6}

0x08000174 D8FA            BHI      __scatterload_copy (0x0800016C)

0x08000176 0752             LSLS     r2,r2,#29

0x08000178 BF24             ITT      CS

0x0800017A C830            LDMCS    r0!,{r4-r5}

0x0800017C C130            STMCS    r1!,{r4-r5}

0x0800017E BF44             ITT      MI

0x08000180 6804             LDRMI    r4,[r0,#0x00]

0x08000182 600C             STRMI    r4,[r1,#0x00]

0x08000184 4770             BX       lr

0x08000186 0000             MOVS     r0,r0

                 __scatterload_zeroinit:

0x08000188 2300             MOVS     r3,#0x00

0x0800018A 2400             MOVS     r4,#0x00

0x0800018C 2500             MOVS     r5,#0x00

0x0800018E 2600             MOVS     r6,#0x00

0x08000190 3A10            SUBS     r2,r2,#0x10

0x08000192 BF28             IT       CS

0x08000194 C178            STMCS    r1!,{r3-r6}

0x08000196 D8FB            BHI      0x08000190

0x08000198 0752            LSLS     r2,r2,#29

0x0800019A BF28            IT       CS

0x0800019C C130            STMCS    r1!,{r4-r5}

0x0800019E BF48             IT       MI

0x080001A0 600B            STRMI    r3,[r1,#0x00]

0x080001A2 4770            BX       lr

                 __rt_lib_init:

0x080001A4 B51F            PUSH     {r0-r4,lr}

                 __rt_lib_init_alloca_1:

0x080001A6 BD1F            POP      {r0-r4,pc}

                 __rt_lib_shutdown:

0x080001A8 B510            PUSH     {r4,lr}

                 __rt_lib_shutdown_fp_trap_1:

0x080001AA BD10            POP      {r4,pc}

                 __rt_entry:

0x080001AC F000FD27     BL.W     __user_setup_stackheap (0x08000BFE)

0x080001B0 4611             MOV      r1,r2

                 __rt_entry_li:

0x080001B2 F7FFFFF7      BL.W     __rt_lib_init (0x080001A4)

                 __rt_entry_main:

0x080001B6 F000F810      BL.W     main (0x080001DA)

0x080001BA F000FD45     BL.W     exit (0x08000C48)

...

                 __use_two_region_memory:

0x08000BF8 4770       BX       lr

                 __rt_heap_escrow$2region:

0x08000BFA 4770      BX       lr

                 __rt_heap_expand$2region:

0x08000BFC 4770      BX       lr

                 __user_setup_stackheap:

0x08000BFE 4675           MOV      r5,lr

0x08000C00 F000F828   BL.W     __user_libspace (0x08000C54)

0x08000C04 46AE          MOV      lr,r5

0x08000C06 0005          MOVS     r5,r0

0x08000C08 4669          MOV      r1,sp

0x08000C0A 4653          MOV      r3,r10

0x08000C0C F0200007  BIC      r0,r0,#0x07

0x08000C10 4685          MOV      sp,r0

0x08000C12 B018          ADD      sp,sp,#0x60

0x08000C14 B520          PUSH     {r5,lr}

0x08000C16 F7FFFF6B    BL.W     __user_initial_stackheap (0x08000AF0)

0x08000C1A E8BD4020   POP      {r5,lr}

0x08000C1E F04F0600   MOV      r6,#0x00

0x08000C22 F04F0700   MOV      r7,#0x00

0x08000C26 F04F0800   MOV      r8,#0x00

0x08000C2A F04F0B00  MOV      r11,#0x00

0x08000C2E F0210107  BIC      r1,r1,#0x07

0x08000C32 46AC      MOV      r12,r5

0x08000C34 E8AC09C0  STM      r12!,{r6-r8,r11}

0x08000C38 E8AC09C0  STM      r12!,{r6-r8,r11}

0x08000C3C E8AC09C0  STM      r12!,{r6-r8,r11}

0x08000C40 E8AC09C0  STM      r12!,{r6-r8,r11}

0x08000C44 468D      MOV      sp,r1

0x08000C46 4770      BX       lr

                 exit:

0x08000C48 4604      MOV      r4,r0

0x08000C4A F3AF8000  NOP.W    

0x08000C4E 4620      MOV      r0,r4

0x08000C50 F7FFFAB5  BL.W     __rt_exit (0x080001BE)

                 __user_libspace:

0x08000C54 4800      LDR      r0,[pc,#0]  ; @0x08000C58

0x08000C56 4770      BX       lr

0x08000C58 002C      DCW      0x002C

0x08000C5A 2000      DCW      0x2000

                 _sys_exit:

0x08000C5C 4901      LDR      r1,[pc,#4]  ; @0x08000C64

0x08000C5E 2018      MOVS     r0,#0x18

0x08000C60 BEAB      BKPT     0xAB

0x08000C62 E7FE      B        0x08000C62

从代码可以看到,__main函数内部主要也是执行两个函数,其功能主要如下:

  • __scatterload():负责把RW/RO从装载域(Code段)地址复制到运行域地址,并完成ZI运行域(SRAM段)的初始化工作。
  • __rt_entry():负责初始化堆栈,完成库函数的初始化,最后自动跳转向main()函数。

前一篇文章存储管理中介绍过ARM与STM32的存储器映射,在处理器运行过程中,代码只需要执行,所以可以放在Code段NOR Flash区(可按字节随机寻址);但全局变量与静态变量需要可读写访问,只能把其复制到SRAM内存段了,__scatterload()正好完成了初始化变量的复制和未初始化变量的初始化操作。

系统运行堆栈的大小和属性需要开发者设定,所以__rt_entry()函数实际调用了外面定义的__user_setup_stackheap函数,该函数在STM32提供的汇编文件中定义,代码如下:

// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\startup_stm32f10x_hd.s

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
                                                  
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB


;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 IF      :DEF:__MICROLIB
                
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
                
                 ELSE
                
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap

                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

                 ALIGN

                 ENDIF

                 END

以__initial_sp为栈顶的栈Stack大小和属性设置在前面已经介绍过了,堆的设置与此类似,Heap_Mem SPACE Heap_Size分配了一段512B(0x0000 0200)大小、不初始化(NOINIT)、可读写(READWRITE)、2^3=8字节对齐(ALIGN=3)的新内存空间,其中__heap_base为堆的起始地址,__heap_limit为堆的结束地址,堆是从低地址向高地址生长的,主要用于动态内存的分配释放,比如malloc/free函数。

初始化堆栈空间和库函数后,就通过__rt_entry_main跳转到了我们的主程序入口地址main处,开始执行我们在主程序main中用C/C++编写的程序了,主程序启动到这里就完成了。

二、STM32固件库移植

我们开发产品时,出于功能需求和成本的考虑,不会一直使用同一款处理器。更换处理器后,常需要从头开始实现所有需要的功能,但很多时候,需要的功能都是之前开发过的,重复开发浪费资源且延长了产品的开发周期。有什么办法能复用之前的代码,只修改跟处理器直接相关的代码,更上层的代码就可以直接拿来使用呢?

如果看过前面介绍的任务调度器虚拟内存管理的文章,就会想到尝试使用分层模型来实现。在底层处理器等硬件与上层应用之间加一层中间层,这个中间层对上层的应用程序屏蔽了硬件平台差异,对下层硬件提供统一的接口进行管理,应该就能实现复用代码在不同硬件平台上的移植。对于ARM内核来说,对应的这个中间层就是CMSIS(Cortex Microcontroller Software Interface Standard),该层是直接跟硬件交互,比操作系统层更靠近硬件,CMSIS在整个开发系统中所处的位置如下所示:
基于CMSIS应用程序基本结构
有了CMSIS层,我们进行复用代码移植就相对简单了,CMSIS包含了MCU中内核及外设的软件接口标准,换一个硬件平台(比如换个处理器型号),只需要更换CMSIS层的相关接口文件就可以了,而该文件一般会由芯片厂商提供。

下面以STM32F103平台为例,介绍下固件库的移植过程,固件库包含了启动过程的相关代码,让我们专注于main函数内程序的开发。

STM32F1固件库的下载网址如下:
https://www.st.com/content/st_com/en/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32-standard-peripheral-libraries/stsw-stm32054.html#overview
STM32F10X固件库下载界面

下载STM32F10x_StdPeriph_Lib_V3.5.0固件库,解压后的文件结构如下:
STM32F103固件库目录

  • Libraries:有CMSIS与STM32F10x_StdPeriph_Driver两个目录,这两个目录包含了固件库移植的核心文件。其中CMSIS文件夹中包含的就是前面介绍的CMSIS层的相关接口文件;STM32F10x_StdPeriph_Driver只是为了便于我们开发程序,将对寄存器的直接操作封装为更直观易用的函数操作的相关库文件,如果不使用库函数直接操作寄存器,不需要该目录内的文件,但如果开发时使用了库函数,就少不了对相应外设库文件的依赖了;
  • Project:有STM32F10x_StdPeriph_Examples与STM32F10x_StdPeriph_Template两个文件夹,分别存放ST官方提供的实例源码和工程模板,可以为我们的开发提供参考;
  • Utilities:保存一些官方评估板的源码,可能会有一些bug,可以忽略;
  • stm32f10x_stdperiph_lib_um.chm:帮助文档,包含了固件库中数据结构与操作函数的定义,可供开发时查询。

STM32固件库的移植重点就是对Libraries目录下CMSIS文件夹中的内容进行移植,下面详细看看CMSIS文件夹下有哪些文件:
STM32 CMSIS目录

  • CMSIS:有CoreSupport与DeviceSupport两个目录,分别保存CM3内核与ST外设两部分的支持文件;Documentation则保存了一些文档信息,介绍CMSIS目录文件编码的规则与约定,快速了解CMSIS相关接口文件的内容;
  • CoreSupport:有core_cm3.c与core_cm3.h两个文件,主要提供了进入CM3内核的接口,包括前面介绍过的NVIC/SCB/MPU/ITM等内核外设数据结构及相关寄存器操作函数的定义,由ARM公司提供;
  • DeviceSupport:有三个文件和一个startup目录,其中system_stm32f10x.c与system_stm32f10x.h提供了设置系统与总线时钟的函数,前面介绍的SystemInit函数就是在这两个文件中定义的;stm32f10x.h包含了STM32F103芯片支持的所有外设相关寄存器的结构体定义和地址,当然也包括大量的宏定义,这是我们开发程序时会频繁查看的一个重要文件;
  • startup:包含处理器的启动文件,其实真正需要的就一个汇编文件,但该启动文件在不同的开发工具和不同型号的F103系列芯片(Flash与SRAM存储空间大小不同)中会有差异,所以根据你选用的开发工具和具体芯片型号选择相对应的启动文件,本文中选择startup_stm32f10x_hd.s汇编文件作为启动文件,前面介绍的中断向量表__Vectors、复位处理函数Reset_Handler、初始化堆栈函数__user_initial_stackheap就是在该启动文件中定义的。

总结下前面介绍的跟启动过程相关的CMSIS层接口文件主要有如下六个:

文件名功能描述
core_cm3.c
core_cm3.h
提供了进入CM3内核的接口
stm32f10x.h包含了STM32F103芯片支持的所有外设相关寄存器的结构体定义和地址
system_stm32f10x.c
system_stm32f10x.h
提供了设置系统与总线时钟的函数比如SystemInit
startup_stm32f10x_hd.s处理器的启动文件

由于这些文件都是ARM与ST官方针对特定芯片型号编写好的,一般情况下不需要我们修改这些文件的内容,只需要在创建工程项目时添加或包含这些文件就可以了,如果想要使用更直观的库函数编程,还需要添加STM32F10x_StdPeriph_Driver目录下相关外设库文件进入工程中。

将固件库CMSIS与STM32F10x_StdPeriph_Driver添加进工程后,可以参照Project目录下的工程模板或示例源码,看看自己移植的固件库是否缺少什么文件。对照后发现,工程模板中多了stm32f10x_it.c、stm32f10x_it.h与stm32f10x_conf.h三个文件,前两个文件主要定义了系统异常处理函数,最后一个文件则包含了一些外设头文件,把这三个工程配置中的文件添加进你自己的工程即可。

需要提醒的一点是在stm32f10x.h文件中需要选择使用了哪个启动文件(这里选择的大容量_HD)、是否使用标准外设库,具体配置是在stm32f10x.h文件取消第70行的注释(使宏定义#define STM32F10X_HD生效)、取消第105行的注释(使宏定义#define USE_STDPERIPH_DRIVER生效)。

固件库移植完成后编写个程序测试下移植是否有问题,拿板载的LED灯作为示例,两个LED灯分别对应PB.5与PE.5,通过置高相应GPIO电平就可以让对应的LED灯亮起,下面给出示例代码:

// USER\main.c

#include "stm32f10x.h"

void Delay(u32 count)
{
	u32 i=0;
	for(;i<count;i++);
}

void LED_Init(void)
{
 
	GPIO_InitTypeDef  GPIO_InitStructure;
 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	    //使能PB,PE端口时钟
	
  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;			    //LED0-->PB.5 端口配置
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 	 //推挽输出
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 //IO口速度为50MHz
  	GPIO_Init(GPIOB, &GPIO_InitStructure);			     //初始化GPIOB.5
  	GPIO_SetBits(GPIOB,GPIO_Pin_5);					//PB.5 输出高
  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	            //LED1-->PE.5推挽输出
  	GPIO_Init(GPIOE, &GPIO_InitStructure);	  	       //初始化GPIO
  	GPIO_SetBits(GPIOE,GPIO_Pin_5); 			 //PE.5 输出高 	  
}

int main(void)
{	
	LED_Init();		  	//初始化LED设备
	
	while(1)
	{		
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
		GPIO_SetBits(GPIOE,GPIO_Pin_5);
		Delay(8000000);
		GPIO_ResetBits(GPIOE,GPIO_Pin_5);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
		Delay(8000000);
	};
}

编译没有报错,说明没有编译时错误,看运行是否有问题。由于板子实际亮灯情况不方便展示,所以下面给出仿真结果,可以直观显示电平高低:
LED灯示例仿真结果
仿真结果跟预期一致,将HEX程序实际烧录到开发板上运行也正常,说明固件库的移植没有问题。ST固件库官方源码及移植代码:https://github.com/StreamAI/UCOS_STM32

更多文章:

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值