开发板:TB不知名STM32F103ZET6最小系统板工具链:arm-none-eabi-gcc开发环境:Windows开发工具:VScode/STM32CubeMX/Ozone功能预期:PB5周期性闪烁

一份基于STM32的C语言跑马灯代码可以多简单?

本文希望还原不使用官方库、IDE的情况下,一份跑马灯程序从编写代码到编译打包再到实际运行的完整过程。

工程文件

代码main.c

void Delay_ms(unsigned int ms)
{while(ms--){__asm volatile ("nop");};
}
int main(void)
{*(unsigned int *)0x40021018 |= 1<<3;*(unsigned int *)0x40010C00 &= 0xFF0FFFFF;*(unsigned int *)0x40010C00 |= 0X00300000;while (1){*(unsigned int *)0x40010C10 = 1<<5;Delay_ms(0x100000);*(unsigned int *)0x40010C10 = (1<<5)<<16;Delay_ms(0x100000);}
}

启动文件startup_stm32f103xe.s

  .syntax unified.cpu cortex-m3.fpu softvfp.thumb.global g_pfnVectors
.global Default_Handler/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss.equ  BootRAM,        0xF1E0F85F
/*** @brief  This is the code that gets called when the processor first*          starts execution following a reset event. Only the absolutely*          necessary set is performed, after which the application*          supplied main() routine is called.* @param  None* @retval : None
*/.section .text.Reset_Handler.weak Reset_Handler.type Reset_Handler, %function
Reset_Handler:/* Copy the data segment initializers from flash to SRAM */ldr r0, =_sdataldr r1, =_edataldr r2, =_sidatamovs r3, #0b LoopCopyDataInitCopyDataInit:ldr r4, [r2, r3]str r4, [r0, r3]adds r3, r3, #4LoopCopyDataInit:adds r4, r0, r3cmp r4, r1bcc CopyDataInit/* Zero fill the bss segment. */ldr r2, =_sbssldr r4, =_ebssmovs r3, #0b LoopFillZerobssFillZerobss:str  r3, [r2]adds r2, r2, #4LoopFillZerobss:cmp r2, r4bcc FillZerobss/* Call the clock system intitialization function.*/@ bl  SystemInit
/* Call static constructors */@ bl __libc_init_array
/* Call the application's entry point.*/bl mainbx lr
.size Reset_Handler, .-Reset_Handler/*** @brief  This is the code that gets called when the processor receives an*         unexpected interrupt.  This simply enters an infinite loop, preserving*         the system state for examination by a debugger.** @param  None* @retval : None
*/.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:b Infinite_Loop.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/.section .isr_vector,"a",%progbits.type g_pfnVectors, %object.size g_pfnVectors, .-g_pfnVectorsg_pfnVectors:.word _estack.word Reset_Handler.word NMI_Handler.word HardFault_Handler.word MemManage_Handler.word BusFault_Handler.word UsageFault_Handler.word 0.word 0.word 0.word 0.word SVC_Handler.word DebugMon_Handler.word 0.word PendSV_Handler.word SysTick_Handler.word WWDG_IRQHandler.word PVD_IRQHandler.word TAMPER_IRQHandler.word RTC_IRQHandler.word FLASH_IRQHandler.word RCC_IRQHandler.word EXTI0_IRQHandler.word EXTI1_IRQHandler.word EXTI2_IRQHandler.word EXTI3_IRQHandler.word EXTI4_IRQHandler.word DMA1_Channel1_IRQHandler.word DMA1_Channel2_IRQHandler.word DMA1_Channel3_IRQHandler.word DMA1_Channel4_IRQHandler.word DMA1_Channel5_IRQHandler.word DMA1_Channel6_IRQHandler.word DMA1_Channel7_IRQHandler.word ADC1_2_IRQHandler.word USB_HP_CAN1_TX_IRQHandler.word USB_LP_CAN1_RX0_IRQHandler.word CAN1_RX1_IRQHandler.word CAN1_SCE_IRQHandler.word EXTI9_5_IRQHandler.word TIM1_BRK_IRQHandler.word TIM1_UP_IRQHandler.word TIM1_TRG_COM_IRQHandler.word TIM1_CC_IRQHandler.word TIM2_IRQHandler.word TIM3_IRQHandler.word TIM4_IRQHandler.word I2C1_EV_IRQHandler.word I2C1_ER_IRQHandler.word I2C2_EV_IRQHandler.word I2C2_ER_IRQHandler.word SPI1_IRQHandler.word SPI2_IRQHandler.word USART1_IRQHandler.word USART2_IRQHandler.word USART3_IRQHandler.word EXTI15_10_IRQHandler.word RTC_Alarm_IRQHandler.word USBWakeUp_IRQHandler.word TIM8_BRK_IRQHandler.word TIM8_UP_IRQHandler.word TIM8_TRG_COM_IRQHandler.word TIM8_CC_IRQHandler.word ADC3_IRQHandler.word FSMC_IRQHandler.word SDIO_IRQHandler.word TIM5_IRQHandler.word SPI3_IRQHandler.word UART4_IRQHandler.word UART5_IRQHandler.word TIM6_IRQHandler.word TIM7_IRQHandler.word DMA2_Channel1_IRQHandler.word DMA2_Channel2_IRQHandler.word DMA2_Channel3_IRQHandler.word DMA2_Channel4_5_IRQHandler.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word BootRAM       /* @0x1E0. This is for boot in RAM mode forSTM32F10x High Density devices. *//*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/.weak NMI_Handler.thumb_set NMI_Handler,Default_Handler.weak HardFault_Handler.thumb_set HardFault_Handler,Default_Handler.weak MemManage_Handler.thumb_set MemManage_Handler,Default_Handler.weak BusFault_Handler.thumb_set BusFault_Handler,Default_Handler.weak UsageFault_Handler.thumb_set UsageFault_Handler,Default_Handler.weak SVC_Handler.thumb_set SVC_Handler,Default_Handler.weak DebugMon_Handler.thumb_set DebugMon_Handler,Default_Handler.weak PendSV_Handler.thumb_set PendSV_Handler,Default_Handler.weak SysTick_Handler.thumb_set SysTick_Handler,Default_Handler.weak WWDG_IRQHandler.thumb_set WWDG_IRQHandler,Default_Handler.weak PVD_IRQHandler.thumb_set PVD_IRQHandler,Default_Handler.weak TAMPER_IRQHandler.thumb_set TAMPER_IRQHandler,Default_Handler.weak RTC_IRQHandler.thumb_set RTC_IRQHandler,Default_Handler.weak FLASH_IRQHandler.thumb_set FLASH_IRQHandler,Default_Handler.weak RCC_IRQHandler.thumb_set RCC_IRQHandler,Default_Handler.weak EXTI0_IRQHandler.thumb_set EXTI0_IRQHandler,Default_Handler.weak EXTI1_IRQHandler.thumb_set EXTI1_IRQHandler,Default_Handler.weak EXTI2_IRQHandler.thumb_set EXTI2_IRQHandler,Default_Handler.weak EXTI3_IRQHandler.thumb_set EXTI3_IRQHandler,Default_Handler.weak EXTI4_IRQHandler.thumb_set EXTI4_IRQHandler,Default_Handler.weak DMA1_Channel1_IRQHandler.thumb_set DMA1_Channel1_IRQHandler,Default_Handler.weak DMA1_Channel2_IRQHandler.thumb_set DMA1_Channel2_IRQHandler,Default_Handler.weak DMA1_Channel3_IRQHandler.thumb_set DMA1_Channel3_IRQHandler,Default_Handler.weak DMA1_Channel4_IRQHandler.thumb_set DMA1_Channel4_IRQHandler,Default_Handler.weak DMA1_Channel5_IRQHandler.thumb_set DMA1_Channel5_IRQHandler,Default_Handler.weak DMA1_Channel6_IRQHandler.thumb_set DMA1_Channel6_IRQHandler,Default_Handler.weak DMA1_Channel7_IRQHandler.thumb_set DMA1_Channel7_IRQHandler,Default_Handler.weak ADC1_2_IRQHandler.thumb_set ADC1_2_IRQHandler,Default_Handler.weak USB_HP_CAN1_TX_IRQHandler.thumb_set USB_HP_CAN1_TX_IRQHandler,Default_Handler.weak USB_LP_CAN1_RX0_IRQHandler.thumb_set USB_LP_CAN1_RX0_IRQHandler,Default_Handler.weak CAN1_RX1_IRQHandler.thumb_set CAN1_RX1_IRQHandler,Default_Handler.weak CAN1_SCE_IRQHandler.thumb_set CAN1_SCE_IRQHandler,Default_Handler.weak EXTI9_5_IRQHandler.thumb_set EXTI9_5_IRQHandler,Default_Handler.weak TIM1_BRK_IRQHandler.thumb_set TIM1_BRK_IRQHandler,Default_Handler.weak TIM1_UP_IRQHandler.thumb_set TIM1_UP_IRQHandler,Default_Handler.weak TIM1_TRG_COM_IRQHandler.thumb_set TIM1_TRG_COM_IRQHandler,Default_Handler.weak TIM1_CC_IRQHandler.thumb_set TIM1_CC_IRQHandler,Default_Handler.weak TIM2_IRQHandler.thumb_set TIM2_IRQHandler,Default_Handler.weak TIM3_IRQHandler.thumb_set TIM3_IRQHandler,Default_Handler.weak TIM4_IRQHandler.thumb_set TIM4_IRQHandler,Default_Handler.weak I2C1_EV_IRQHandler.thumb_set I2C1_EV_IRQHandler,Default_Handler.weak I2C1_ER_IRQHandler.thumb_set I2C1_ER_IRQHandler,Default_Handler.weak I2C2_EV_IRQHandler.thumb_set I2C2_EV_IRQHandler,Default_Handler.weak I2C2_ER_IRQHandler.thumb_set I2C2_ER_IRQHandler,Default_Handler.weak SPI1_IRQHandler.thumb_set SPI1_IRQHandler,Default_Handler.weak SPI2_IRQHandler.thumb_set SPI2_IRQHandler,Default_Handler.weak USART1_IRQHandler.thumb_set USART1_IRQHandler,Default_Handler.weak USART2_IRQHandler.thumb_set USART2_IRQHandler,Default_Handler.weak USART3_IRQHandler.thumb_set USART3_IRQHandler,Default_Handler.weak EXTI15_10_IRQHandler.thumb_set EXTI15_10_IRQHandler,Default_Handler.weak RTC_Alarm_IRQHandler.thumb_set RTC_Alarm_IRQHandler,Default_Handler.weak USBWakeUp_IRQHandler.thumb_set USBWakeUp_IRQHandler,Default_Handler.weak TIM8_BRK_IRQHandler.thumb_set TIM8_BRK_IRQHandler,Default_Handler.weak TIM8_UP_IRQHandler.thumb_set TIM8_UP_IRQHandler,Default_Handler.weak TIM8_TRG_COM_IRQHandler.thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler.weak TIM8_CC_IRQHandler.thumb_set TIM8_CC_IRQHandler,Default_Handler.weak ADC3_IRQHandler.thumb_set ADC3_IRQHandler,Default_Handler.weak FSMC_IRQHandler.thumb_set FSMC_IRQHandler,Default_Handler.weak SDIO_IRQHandler.thumb_set SDIO_IRQHandler,Default_Handler.weak TIM5_IRQHandler.thumb_set TIM5_IRQHandler,Default_Handler.weak SPI3_IRQHandler.thumb_set SPI3_IRQHandler,Default_Handler.weak UART4_IRQHandler.thumb_set UART4_IRQHandler,Default_Handler.weak UART5_IRQHandler.thumb_set UART5_IRQHandler,Default_Handler.weak TIM6_IRQHandler.thumb_set TIM6_IRQHandler,Default_Handler.weak TIM7_IRQHandler.thumb_set TIM7_IRQHandler,Default_Handler.weak DMA2_Channel1_IRQHandler.thumb_set DMA2_Channel1_IRQHandler,Default_Handler.weak DMA2_Channel2_IRQHandler.thumb_set DMA2_Channel2_IRQHandler,Default_Handler.weak DMA2_Channel3_IRQHandler.thumb_set DMA2_Channel3_IRQHandler,Default_Handler.weak DMA2_Channel4_5_IRQHandler.thumb_set DMA2_Channel4_5_IRQHandler,Default_Handler/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

编译脚本Makefile

######################################
# target
######################################
TARGET = GPIO######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization# OPT = -O0
OPT = -Og#######################################
# paths
#######################################
# Build path
BUILD_DIR = build######################################
# source
######################################
# C sources
C_SOURCES =  \
Core/Src/main.c \# ASM sources
ASM_SOURCES =  \
startup_stm32f103xe.s#######################################
# binaries
#######################################
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S#######################################
# CFLAGS
#######################################
# cpu
CPU = -mcpu=cortex-m3# fpu
# NONE for Cortex-M0/M0+/M3# float-abi# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)# macros for gcc
# AS defines
AS_DEFS = # C defines
C_DEFS =  # AS includes
AS_INCLUDES = # C includes
C_INCLUDES =  # compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sectionsCFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sectionsifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103ZETx_FLASH.ld# libraries
# LIBS = -lc -lm -lnosys
LIBS =
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) $(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)$(AS) -c $(CFLAGS) $< -o $@$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile$(CC) $(OBJECTS) $(LDFLAGS) -o $@$(SZ) $@$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)$(HEX) $< $@$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)$(BIN) $< $@  $(BUILD_DIR):mkdir $@      #######################################
# clean up
#######################################
clean:-rm -fR $(BUILD_DIR)#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)# *** EOF ***

链接脚本STM32F103ZETx_FLASH.ld

/* Entry Point */
ENTRY(Reset_Handler)/* Highest address of the user mode stack */
_estack = 0x20010000;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack *//* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K
}/* Define output sections */
SECTIONS
{/* The startup code goes first into FLASH */.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)) /* Startup code */. = ALIGN(4);} >FLASH/* The program code and other data goes into FLASH */.text :{. = ALIGN(4);*(.text)           /* .text sections (code) */*(.text*)          /* .text* sections (code) */*(.glue_7)         /* glue arm to thumb code */*(.glue_7t)        /* glue thumb to arm code */*(.eh_frame)KEEP (*(.init))KEEP (*(.fini)). = ALIGN(4);_etext = .;        /* define a global symbols at end of code */} >FLASH/* Constant data goes into FLASH */.rodata :{. = ALIGN(4);*(.rodata)         /* .rodata sections (constants, strings, etc.) */*(.rodata*)        /* .rodata* sections (constants, strings, etc.) */. = ALIGN(4);} >FLASH/* used by the startup to initialize data */_sidata = LOADADDR(.data);/* Initialized data sections goes into RAM, load LMA copy after code */.data : {. = ALIGN(4);_sdata = .;        /* create a global symbol at data start */*(.data)           /* .data sections */*(.data*)          /* .data* sections */. = ALIGN(4);_edata = .;        /* define a global symbol at data end */} >RAM AT> FLASH/* Uninitialized data section */. = ALIGN(4);.bss :{/* This is used by the startup in order to initialize the .bss secion */_sbss = .;         /* define a global symbol at bss start */__bss_start__ = _sbss;*(.bss)*(.bss*)*(COMMON). = ALIGN(4);_ebss = .;         /* define a global symbol at bss end */__bss_end__ = _ebss;} >RAM/* User_heap_stack section, used to check that there is enough RAM left */._user_heap_stack :{. = ALIGN(8);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;. = ALIGN(8);} >RAM/* Remove information from the standard libraries *//DISCARD/ :{libc.a ( * )libm.a ( * )libgcc.a ( * )}.ARM.attributes 0 : { *(.ARM.attributes) }
}

流程

和所有的C语言代码一样,逃不过4个步骤:

预编译、编译、汇编、链接

不过现在GCC以及把预编译和编译合并为一个步骤,预编译是为了展开宏定义、头文件、插入行号等一些初步处理,编译则把.c转化为.s汇编文件,而汇编则是把.s翻译为一种包含很多段的文件.o,这些段保存的内容由汇编器决定,而 你可以理解为同类型数据的集合,比如.text段保存的就是由.s翻译为机器码的代码段,.data保存的是已初始化的全局变量或局部静态变量,当然还包括很多重定向的段、包含调试信息的段、包含库函数加载的段等等,最后一步链接由链接器把所有.o整合为一个文件,而这个文件就是可以直接烧录入单片机的可执行文件,这一步主要是段的整合以及符号重定位表的计算与合并。

其中.s文件懂ARM汇编的人其实可以直接看懂,但是.o文件却无法直接查看。因为和链接脚本与启动文件有关,所以这里想展开讲一点。

.o文件即Object文件,因为它们经过链接后可以形成最终可执行文件,所以也可以叫中间目标文件。一个.c文件转变过程如下:

a.c -> a.i -> a.s -> a.o

.o文件中包含了文件头和各个不同的段,在Ubuntu下使用readelf可以看到main.o文件的详细信息:

Windows下如果安装的有git则可以在git终端使用objdump查看.o文件:

总的来说一份代码,编译后分为两种段,代码段和数据段,一般代码段为.text,而数据段又分为已初始化的.data和未初始化的.bss ,当然在更加复杂的代码中会有更多段的划分,比如数据段内一般还有.rodata,代表const常量所存的段,还有很多是由编译器添加的段,甚至使用attribute可以自定义段。

而在.o文件中函数与变量统称为符号,我们可以想象,假如全局变量TMP在a.c和b.c中都被调用,最终合并到一个可执行文件中时,会有两个TMP吗?

答案是否定的。在每个.o文件中都有一个符号表,它的符号值一般代表了符号的地址,它在链接阶段会被合并,最后每个符号的符号值都是唯一的。这也就是为什么使用extern的原因,它表明该符号信息保存在在其他文件的符号表中。

代码

首先启动文件编译脚本链接脚本都是STM32CubeMX生成,有做改动,为的是尽可能去掉不需要的部分,只保留最基本的实现跑马灯的部分,让整个过程更加清晰。

启动文件

启动文件一般芯片厂商会提供,当然你也可以基于厂商提供的修改。

在这个工程的启动文件中,除去重复的符号定义,其实核心内容就一段代码:

它完成了两步:

1.把.data段(即已初始化的全局变量、局部静态变量段)由FLASH搬运至RAM(原因在下文解释)

2.把.bss段(即未初始化的全局变量、局部静态变量段)填充至RAM并保证清0

完成后就跳转至main()函数中:

其他的则是设置中断向量及中断回调函数,在本工程中,因为没有使用任何中断,所以所有中断都为Default_Handler。详细可以参考笔者之前的文章:

(GCC)STM32CubeMX中s启动文件详解

这个启动文件中,因为main.c里面没有使用任何的库,且没有设置时钟,所以注释掉了:

bl:带返回跳转,也就是跳转到目标label,执行完成后还返回到跳转前的位置。而SystemInit即系统默认初始化,__lib_init_array即c库初始化。

链接脚本

跑马灯代码到底从哪里开始执行的?

答案就在链接脚本中:

链接脚本到底还做了什么工作?

上文已经提到链接的过程,而链接脚本是把不同段分配到不同的地址

根据STM32的地址映射:

其中FLASH中以中断向量表开始,后面紧跟代码,然后是只读变量,最后是已初始化的全局变量等。而上电后,RAM才会初始化。它以已初始化的全局变量等开始,后面跟未初始化的全局变量等,再后面跟堆栈空间。所以我们在链接脚本中要决定每个段是分配到FLASH里,即以0x08000000为起始,以0x807FFFF为结束的地址空间里,还是分配到RAM里,即以0x20000000为起始,以0x2000FFFF为结束的地址空间里!

这些分配规则构成了链接脚本。这样就是为什么启动文件里直接调用全局标号_sdata、_edata、_sbss、_ebss等就可以搬运FLASH数据到RAM里,因为它们的地址范围被分配在RAM的地址映射范围内。

至于为什么要这样分配,打开.map文件可以看到:

它被设置在链接脚本里:

首先STM32的代码是无法在运行时更改的,它被保存在FLASH里,实际运行也在FLASH里,它的权限为XR,即可执行、可读,而普通变量很显然是随时可以被改变的,所以它需要放在RAM里,它的权限为XRW,即可执行、可读、可写。而一些变量是有初始值的,RAM里面的数据断电即消失,而FLASH不会,所以为了每次上电这些已初始化的值都能被正确初始化,它们的初始值应该也被保存在FLASH里,而那些未初始化的变量本身就默认为0,所以不需要保存它们的初始值,只需要在代码运行时,保留它们所被分配地址的空间即可。最后栈是程序局部变量所使用的,我们只需要在RAM里给它分配空间,大小由我们决定,程序会自然使用,而堆通常是我们使用malloc才能使用的,此工程未使用libc库,所以根本无法通过malloc使用,但是我们仍然可以通过计算,算出堆的起始地址,直接使用指针去访问它们。

最终可执行文件的加载地址如下图:

可以对我们的ld链接脚本印证,各段:

.isr_vector:中断向量表,虚拟内存地址为0x0800 0000

.text:代码段,虚拟内存地址为0x0800 01e4

.init:来源于交叉编译链,程序初始化段,虚拟内存地址为0x0800 0278

.fini:来源于交叉编译链,程序终结段,虚拟内存地址为0x0800 027c

.rodata:只读数据段,因为不需要改变,所以不需要搬运到RAM里,虚拟内存地址为0x0800 0280

.data:有初始值的全局变量或局部静态变量所存段,虚拟内存地址为0x2000 0000,此工程中其实为空

.bss:未赋值的全局变量或局部静态变量所存段,虚拟内存地址为0x2000 0000,此工程中为空

._user_heap_stack:用户堆栈,虚拟内存地址为0x2000 0000

链接脚本比较STM32CubeMX生成的注释掉了:

.ARM.extab包含了外部拓展空间地址的段,.ARM包含解析堆栈信息的段,.preinit_array包含.init之前执行的内容,.init_array是程序初始化执行内容的段,.fini_array是代码终结执行内容的段。

其实连.data和.bss都可以不要,因为本工程没有使用任何全局变量。

main.c

可以看到main.c没有使用任何头文件,它只包含了寄存器操作。寄存器配置我们需要查看STM32F103的Datasheet,其中我们需要开启外设时钟,并设置外设:

RCC时钟寄存器地址映射范围:

有关RCC中设置使能GPIOB的寄存器的相对偏移:

RCC_APB2ENR = RCC(0x40021000) + 偏移(0x18) = 0x40021018

bit3为1则开启Prot B时钟,为0关闭。

配置PB5有关寄存器的地址映射范围:

有关GPIO引脚模式配置相关寄存器的相对偏移:

GPIOB_CRL = Port B(0x0x40010C00) + 偏移(0x00) = 0x0x40010C00

每个引脚占用4个bit用于设置输入输出模式,Pin5使用的是bit20~bit23 。

设置PB5高低电平寄存器:

GPIOB_BSRR = Port B(0x0x40010C00) + 偏移(0x10) = 0x0x40010C10

bit0~bit15分别用来设置Pin0~Pin15输出高电平,BS可以理解为Bit Set,而bit16~bit31用来设置Pin0~Pin15输出低电平,BR可以理解为Bit Reset

配合Datasheet能很容易看明白main.c的内容。而此处的延时使用的是汇编指令nop,表示什么也不做。可以看到,通篇代码没有设置时钟相关内容,因为此时时钟使用的是默认配置,也就是STM32上电后时钟是默认开启的,如果你需要倍频,一定要配置时钟相关内容。

推荐可以直接使用正点原子寄存器版本例程配置时钟:

void MY_NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
{        SCB->VTOR = NVIC_VectTab|(Offset & (uint32_t)0x1FFFFF80);//设置NVIC的向量表偏移寄存器//用于标识向量表是在CODE区还是在RAM区
}
void MYRCC_DeInit(void)
{   RCC->APB1RSTR = 0x00000000;//复位结束            RCC->APB2RSTR = 0x00000000; RCC->AHBENR = 0x00000014;  //睡眠模式闪存和SRAM时钟使能.其他关闭.   RCC->APB2ENR = 0x00000000; //外设时钟关闭.             RCC->APB1ENR = 0x00000000;   RCC->CR |= 0x00000001;     //使能内部高速时钟HSION                                                               RCC->CFGR &= 0xF8FF0000;   //复位SW[1:0],HPRE[3:0],PPRE1[2:0],PPRE2[2:0],ADCPRE[1:0],MCO[2:0]                     RCC->CR &= 0xFEF6FFFF;     //复位HSEON,CSSON,PLLONRCC->CR &= 0xFFFBFFFF;     //复位HSEBYP        RCC->CFGR &= 0xFF80FFFF;   //复位PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE RCC->CIR = 0x00000000;     //关闭所有中断        //配置向量表
#ifdef  VECT_TAB_RAMMY_NVIC_SetVectorTable(0x20000000, 0x0);
#else   MY_NVIC_SetVectorTable(0x08000000,0x0);
#endif
}
void Stm32_Clock_Init(uint8_t PLL)
{unsigned char temp=0;   MYRCC_DeInit();         //复位并配置向量表RCC->CR|=0x00010000;  //外部高速时钟使能HSEONwhile(!(RCC->CR>>17));//等待外部时钟就绪RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;PLL-=2;                //抵消2个单位(因为是从2开始的,设置0就是2)RCC->CFGR|=PLL<<18;   //设置PLL值 2~16RCC->CFGR|=1<<16;      //PLLSRC ON FLASH->ACR|=0x32;   //FLASH 2个延时周期RCC->CR|=0x01000000;  //PLLONwhile(!(RCC->CR>>25));//等待PLL锁定RCC->CFGR|=0x00000002;//PLL作为系统时钟   while(temp!=0x02)     //等待PLL作为系统时钟设置成功{   temp=RCC->CFGR>>2;temp&=0x03;}
}   

调用Stm32_Clock_Init(9)函数,即使用外部8M时钟倍频9倍=72MhHz。使用时需包含

#include "stm32f1xx.h"

可由CubeMX生成或直接使用原子工程内的。

编译脚本

关于编译脚本不再详细介绍,有兴趣的可以查看之前的文章:

(GCC)STM32CubeMX生成的Makefile详解

编译结果

可以看到编译极其简单,只有一个.c和一个.s,最终被链接为GPIO.elf.

运行分析

可以通过Ozone或者直接查看.map文件查看内存加载情况:

可以看到FLASH加载情况为:先加载的终端向量表,后面紧跟代码段,包含了四个函数

Delay_ms()、main()、Reset_Handler()、Default_Handler()。但是其实我们可以看到除了这些还有一些并非由我们编写的段也被加载FLASH中:

这些段来自于链接器或者是GCC的启动程序。而RAM中加载的内容如下:

可以看到链接情况和ld脚本中的内容都可以对应上,而实际我们没有使用任何全局或者静态变量,所以.data和.bss段及记录符号偏移的段.igot.plt都为空,即不占用RAM,所以其实从0x20000000开始就是堆栈空间。

在.lst文件中可以查看机器码:

可以通过运行时内存查看实际内存中的机器码与汇编代码对应情况。

更新:2022-01-24

突然觉得对于一个C语音的跑马灯来说,工程还是太臃肿了,重新删掉了不使用的部分,主要是.s启动文件和链接脚本:

.s启动文件:

  .syntax unified.cpu cortex-m3.fpu softvfp.thumb.global g_pfnVectors.section .isr_vector,"a",%progbits.type g_pfnVectors, %object.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:.word _estack.word main

.ld链接脚本:

/* Entry Point */
ENTRY(main)/* Highest address of the user mode stack */
_estack = 0x20010000;    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K
}/* Define output sections */
SECTIONS
{/* The startup code goes first into FLASH */.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)) /* Startup code */. = ALIGN(4);} >FLASH/* The program code and other data goes into FLASH */.text :{. = ALIGN(4);*(.text)           /* .text sections (code) */*(.text*)          /* .text* sections (code) */. = ALIGN(4);_etext = .;        /* define a global symbols at end of code */} >FLASH
}

更新:2022-05-11

前文中的:

这里说的有些问题。由编译脚本可以看出,链接器最终生成的文件时GPIO.elf,而我们单片机烧写代码时,可以使用bin和hex文件,但是实际debug时从FLASH读出的数据可以知道,最终烧写进FLASH的是bin文件,它是不需要做任何处理可以直接烧写进FLASH的。而bin是由工具链中的:

arm-none-eabi-objcopy

把elf文件转化为hex和bin文件。

ELF是什么?

它是一种目标文件格式,全称:Executable and Linking Format(可执行和链接格式) 。它有好几种类型,比如可执行文件、core文件、共享对象文件等,每种类型用处不同,但是格式统一。你可能会好奇,既然有可执行文件,那假如我们的编译脚本生成的是可执行文件,那单片机为什么不能直接使用?

因为这个可执行文件是给exec函数使用的。单片机是裸机,不支持这种方式。当然你在Linux或者Windows这种支持exec函数的操作系统下是可以执行的。(你是否会好奇exec函数是哪里来的?它是被烧写进我们电脑进去的吗?很明显不是,它是操作系统中的一个程序,那操作系统是怎么来的?它明显也不是被烧写进电脑的。在windows上一切的起点是bios,它就是那个被直接烧写进去的程序)

所以,我们需要使用工具链工具把一个需要加载的elf文件,转化成和CPU架构相关的bin文件,它可以直接被烧写进指定的flash地址,然后直接运行。

附最近一篇我自己写的认为更为详细的博文:

内存详解

(GCC)STM32跑马灯代码的前世今生相关推荐

  1. Arduino IDE搭建合宙ESP32C3开发环境(最简单) 附跑马灯代码

    Arduino IDE搭建合宙ESP32C3开发环境(最简单) 附跑马灯代码 一.安装Arduino IDE 二.搭建合宙简约版ESP32C3开发环境 1.产品示例&管脚定义&原理图 ...

  2. 小白学STM32——跑马灯库函数版本

    一.STM32 GPIO 1. GPIO_MODE_AIN 模拟输入 输入信号不经施密特触发器直接接入,输入信号为模拟量而非数字量,其余输入方式输入数字量. 2. GPIO_MODE_IN_FLOAT ...

  3. stm32跑马灯实验

    本文记录STM32mini开发板实验 普通跑马灯 回归GPIO基础知识 每组IO口含有下面7个寄存器,7个寄存器一共可以控制一组GPIO的16个IO口. GPIOx_CRL:端口配置低寄存器 GPIO ...

  4. 参考学习的各种跑马灯代码

    实现跑马灯的方法很多,其中最简单的是采用一句Html代码来实现,我们在需要出现跑马灯效果的地方插入"<marquee>滚动的文字</marquee>"语句, ...

  5. html跑马灯编程,求一个HTML无缝的跑马灯代码。

    属性要求:胆识.福缘.机敏 效果:中毒.抗毒 蛤蟆功: 首部曲 1.巳午未时点扬州武器商附近的全知道触发到对话..说有事情想请教,一阵对话后给他10万两.接着说要到华山去(若给他1000两的是昆仑剑法 ...

  6. 嵌入式系统stm32 跑马灯实验

    一.实验目的和实验要求 实验要求:短按实验板扩展板上的按键SW18,依次点亮核心板上D2-D4指示灯.长按按键2秒,实现四个灯循环点亮(跑马灯). 二.实验原理 如上图所示,四个 LED 正极通过电阻 ...

  7. STM32跑马灯实验的基本步骤(库函数)

    1.硬件设计 本次用到的硬件只有 LED(DS0 和 DS1).其电路在 ALIENTEK 探索者 STM32F4 开发板 上默认是已经连接好了的.DS0 接 PF9,DS1 接 PF10.所以在硬件 ...

  8. STM32——跑马灯实现

    /*走马灯实现*//*实现原理:依次从头到尾点亮8个灯*列如:第一个灯赋值低电平(点亮),*延迟之后(暂未确定多少s),*不使用灯寄存器之后,系统自动熄灭, *在点亮下一个灯,依次往下*/ #incl ...

  9. html文本框中加入跑马灯,js文本框走动跑马灯效果代码分享

    本文实例讲述了js实现文本框走动跑马灯效果.分享给大家供大家参考.具体如下: 运行效果图: 小提示:直接复制下面分享的代码即可运行,大家可以自定义文字. 为大家分享的js实现文本框走动跑马灯效果代码如 ...

  10. STM32-GPIO学习-跑马灯实验和按键实验-寄存器版本和HAL库版本

    一.stm32跑马灯实验 a.GPIO general purpose input output 通用输入输出端口,可以做输入也可做输出,GPIO端口可通过程序配置成输入或输出. STM32FXXXI ...

最新文章

  1. c语言 链表 库,玩转C链表
  2. WindowsService服务程序开发
  3. 2018/7/19-纪中某C组题【jzoj3461,jzoj3462,jzoj3463,jzoj3464】
  4. 看完本文若不能让你学通“Python”,我将永远退出IT界
  5. oracle 查询temporary table,【TEMPORARY TABLE】Oracle临时表使用注意事项
  6. vue 时间插件_Vue插件丨vxe-table初体验
  7. 快速截图工具——百度输入法的扩展功能
  8. 第一节、同步回调和异步回调?
  9. 使用 HTML/CSS 实现 Educoder 顶部导航栏
  10. P2P技术详解(一):NAT详解——详细原理、P2P简介(转)
  11. yolov5训练结果解析
  12. ZGC的运行过程以及读屏障
  13. 以太坊NFT二层网络之Immutable X(IMX)
  14. 卡巴斯基KAV/KIS 6.0.1.411正式版下载 附MP1版中文汉化+注册码
  15. 小程序构建npm问题
  16. 显示桌面图标没有怎么解决
  17. PPT使用VBA批量删除图形
  18. mongodb连接报错:connect@src/mongo/shell/mongo.js:374:17
  19. models.py 文件中的类及相关属性
  20. 怎么画动漫人物的眼睛

热门文章

  1. 学生成绩管理系统简单c语言源代码,c语言学生成绩管理系统源代码
  2. 处女座的期末复习-贪心
  3. 贪心算法几个经典例子python-Python贪心算法实例小结
  4. 计算机的3d软件家庭版,3DOne家庭版 64位
  5. Arduino IDE下载安装ESP8266/32慢的解决办法
  6. Fiddler中文版汉化插件 0.1
  7. Ubuntu16.04安装(QQ.exe)
  8. 超简单的Matlab附加功能安装包的安装方法
  9. 高等代数第3版下 [丘维声 著] 2015年版_黄哥友情提示:学习线性代数的书和视频...
  10. hypermesh 错误 2005