如何使用GCC手动编译stm32程序

如何不使用任何IDE(集成开发环境)编译stm32程序?
集成开发环境将编辑器、编译器、链接器、调试器等开发工具集成在一个统一的软件中,使得开发人员可以更加简单、高效地完成软件开发过程。如果我们不使用KEIL,IAR等集成开发环境,就需要手动完成对stm32程序的编译,得到一个stm32可以执行的文件。

这个问题看似很闲的蛋疼,其实不然。集成开发环境降低了开发难度,隐藏了开发细节。如果我们能手动实现程序的编译,这将使我们更深入的理解程序编译和stm32启动。
开发步骤分为以下两个步骤:
1、编译环境的搭建
2、编写和编译程序
开发环境:
Windows10
stm32f103rb
1.搭建编译环境
搭建编译环境的目的是,实现编译stm32的程序代码,得到一个stm32可执行文件。搭建编译环境分为以下两个步骤:
1、安装编译器
2、安装make工具
1.1安装编译器
在Windows10系统下适用于STM32的编译器为gcc-arm-none-eabi 。
gcc-arm-none-eabi是GNU项目下的软件,是一个面向ARM架构的芯片的交叉编译器。交叉编译器是一种特殊的编译器,它能够使得开发者可以在自己的主机(如PC)上编写和编译代码,然后将编译后的二进制代码部署到目标嵌入式系统(如ARM架构的微控制器)上运行。
前往ARM的官方网站下载gcc-arm-none-eabi
https://developer.arm.com/downloads/-/gnu-rm

安装gcc-arm-none-eabi
使用默认选项安装,在最后的完成界面,一定要勾选“add path to environment variable”

测试gcc-arm-none-eabi
按下win+r按键输入cmd启动终端,在终端中输入如下指令,查看gcc版本。安装失败会提示指令为无效指令。安装失败有可能是没有勾选“add path to environment variable”
arm-none-eabi-gcc --version

成就:完成编译器安装后,我们可以将C程序编译成stm32f103rb能识别的二进制代码。
1.2安装make工具
安装gcc-arm-none-eabi后我们就可以编译C文件得到stm32可执行文件,那我们是不是开始编译我们的工程了呢?显然不是,此时我们会遇到如下问题:
1、当你的程序只有一个源文件的时候,直接使用gcc命令编译就行。软件工程包含的源文件越来越多采用gcc命令逐个手动去编译,很容易混乱而且工作量大,会让人抓狂。而且各个文件可能还得依赖不同的库,这样命令会变得很长,显然这是不可行的办法。
2、开发一个项目的时候,进行了一个简单的修改,比如就改了一个if条件,修改后都要重新编译一次,假设整个源码的工程里面的源文件的数量几百个或者上千个,完成所有文件的编译是需要大量时间的,编译半天都有可能,就修改了一个小bug而已,花费这么久的时间,明显工作效率会很低。

为了解决上述问题make工具诞生了。make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,通过调用makefile文件中用户指定的命令来进行编译和链接的。
makefile就是一个脚本文件,简单的说就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据 makefile中的命令进行编译和链接的。
下载make工具
在这里我不直接安装make工具,我们采样迂回的方式安装make工具。下载wingm
https://www.mingw-w64.org/downloads/#winlibscom


安装wingm
解压wingm,假设我们的路径是E:\tools\mingw64\bin (每个人的路径可能不一样)

将E:\tools\mingw64\bin路径(每个人的路径可能不一样)添加到系统环境变量PATH中,这样我们才可以在任意地方执行mingw64\bin中的指令,按照如下步骤将E:\tools\mingw64\bin添加到PATH中。

关键步骤到了:
将E:\tools\mingw64\bin\ming32-make.exe 重命名为 c:\MinGw\bin\make.exe
测试make工具
按下win+r按键输入cmd重新启动终端(一定要重新启动终端,终端不会实时更新环境变量),在终端中输入如下指令,查看make版本。安装失败会提示指令为无效指令。
make -v


至此我们的编译环境搭建完成,接下来我们就开始编译代码之旅!
2.编写和编译程序
2.1使用gcc-arm-none-eabi编译一个C文件
写一个main函数,保存为main.c文件
int main(void)
{while (1) {// 主循环代码,例如LED闪烁、串口通信等// ...}
}
在main.c文件目录下运行cmd ,执行如下指令,我们编译得到一个main.o文件。
arm-none-eabi-gcc -c main.c -o main.o

至此我们可以编译得到一个.o文件,这就是我们的第一步。
2.2写一个Makeflie编译C文件
正如前文说的当只有一个C文件时,可以手动使用gcc指令编译,如果有很多C文件时,手动使用gcc指令去完成编译已经变得不可能,此时就需要用到我们的make工具。接下来我们用make工具实现批量编译。
我们建立一个src文件夹,和一个Makefile文件,并在src文件夹中创建main.c和test.c两个C文件。

mian.c内容如下:
void _exit(int status) __attribute__((weak));
void _exit(int status)
{while (1);
}int main(void)
{while (1) {// 主循环代码,例如LED闪烁// ...}
}
test.c内容如下:
void delay(void)
{int i = 0 ;for(i = 0 ;i < 1000 ; i++){}
}
Makefile内容如下:
# 定义编译器
CC = arm-none-eabi-gcc# 目标
TARGET = my_test# 列出所有源文件
SRCS = $(wildcard src/*.c) # 将源文件转换为目标文件列表
OBJS = $(SRCS:.c=.o)# 默认目标
all: $(TARGET)# 生成文件
$(TARGET): $(OBJS)$(CC) $(OBJS) -o $(TARGET)
然后我们在Makefile文件所在的路径下运行cmd,并在终端中输入指令make,运行结果如下:

于是成功我们利用make工具实现批量编译C文件。
2.3编写一个启动文件和链接文件
目前我们已经可以利用make工具实现对文件的批量编译,那么是不是可以将编译得到的文件运行到stm32f103rb芯片上呢?
答案是否定的,要让stm32f103rb芯片运行我们编译得到的执行文件,还有以下3个步骤:
1、配置arm-none-eabi-gcc参考,让编译器输出stm32f103rb可执行格式的文件
2、编写一个启动文件,让代码能被正常引导
3、编写一个链接文件,让告诉编译器将生成的代码放置的合适位置
配置arm-none-eabi-gcc
当我们在gcc指令后增加一个参数-mcpu=cortex-m3,此时编译器输出的文件格式将是cortex-m3芯片能执行的格式,而stm32f103rb属于cortex-m3系列。
arm-none-eabi-gcc -c -mcpu=cortex-m3
编写一个启动文件
stm32芯片启动流程如下:
1、用代码段的第1个32位数初始化堆栈指针SP
2、用代码段的第2个32位数初始化程序指针PC(也就是程序跳转到第2个32位数的数值指向的地址)
为了完成正确的启动,我们需要写一个汇编格式的启动文件,汇编代码内容如下:
/**
*********************************************************************************************************
* (c) Copyright 2024-2032
* All Rights Reserved
* @By : liwei
*********************************************************************************************************
**/
.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb/******************************************************************************
复位启动函数Reset_Handler 执行跳转到main函数
******************************************************************************/ .section .text.Reset_Handler.weak Reset_Handler.type Reset_Handler, %function
Reset_Handler: bl mainbx lr
.size Reset_Handler, .-Reset_Handler
/******************************************************************************
向量表 第一个值为SP 第二个值为复位地址
******************************************************************************/
.global g_pfnVectors .section .isr_vector,"a",%progbits.type g_pfnVectors, %object.size g_pfnVectors, .-g_pfnVectorsg_pfnVectors:.word _estack.word Reset_Handler
由启动函数可知,我们定义了一个g_pfnVectors向量表,向量表中的 第一个值为SP初始化值, 第二个值为复位Reset_Handler函数地址。
在复位函数Reset_Handler中跳转到了mian函数。
编写一个链接文件
是不是增加一个启动文件,就能让stm32f103rb芯片运行我们编译得到的执行文件了呢?
答案是否定的!
问题:代码应该下载到stm32f103rb芯片的什么位置?
在编译过程中有一个重要的环节:链接

链接可以将多个目标代码整合成最终的可执行程序,在链接过程中有个重要的步骤就是给程序指定一个起始位置。因此我们编写一个简单的链接文件:
/* 定义flash 和 ram 区域 */MEMORY
{flash (rx) : ORIGIN = 0x08000000, LENGTH = 64Kram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}ENTRY(Reset_Handler)_heap_size = 0; /* required amount of heap */
_stack_size = 0; /* required amount of stack *//* The stack starts at the end of RAM and grows downwards. Full-descending*/
_estack = ORIGIN(ram) + LENGTH(ram);SECTIONS
{/* Reset and ISR vectors */.isr_vector :{__isr_vector_start__ = .;KEEP(*(.isr_vector)) /* without 'KEEP' the garbage collector discards this section */ASSERT(. != __isr_vector_start__, "The .isr_vector section is empty");} >flash/* Text section (code and read-only data) */.text :{. = ALIGN(4);_stext = .;*(.text*) /* code */*(.rodata*) /* read only data *//** NOTE: .glue_7 and .glue_7t sections are not needed because Cortex-M* only supports Thumb instructions, no ARM/Thumb interworking.*//* Static constructors and destructors */KEEP(*(.init))KEEP(*(.fini)). = ALIGN(4);_etext = .;} >flash/** Initialized data section. This section is programmed into FLASH (LMA* address) and copied to RAM (VMA address) in startup code.*/_sidata = .;.data : AT(_sidata) /* LMA address is _sidata (in FLASH) */{. = ALIGN(4);_sdata = .; /* data section VMA address */*(.data*). = ALIGN(4);_edata = .;} >ram/* Uninitialized data section (zeroed out by startup code) */.bss :{. = ALIGN(4);_sbss = .;*(.bss*)*(COMMON). = ALIGN(4);_ebss = .;} >ram/** Reserve memory for heap and stack. The linker will issue an error if* there is not enough memory.*/._heap :{. = ALIGN(4);. = . + _heap_size;. = ALIGN(4);} >ram._stack :{. = ALIGN(4);. = . + _stack_size;. = ALIGN(4);} >ram
}/* Nice to have */
__isr_vector_size__ = SIZEOF(.isr_vector);
__text_size__ = SIZEOF(.text);
__data_size__ = SIZEOF(.data);
__bss_size__ = SIZEOF(.bss);
从代码中可以发现,程序定义了flash和ram的起始位置和大小。
flash (rx) : ORIGIN = 0x08000000, LENGTH = 64K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
程序同时定了一个中断向量段,这个段在flash的开始位置。
/* Reset and ISR vectors */.isr_vector :{__isr_vector_start__ = .;KEEP(*(.isr_vector)) /* without 'KEEP' the garbage collector discards this section */ASSERT(. != __isr_vector_start__, "The .isr_vector section is empty");} >flash
程序还定义了.text 、.data、.bss 、._heap 、._stack段。
编写Makefile文件
编写一个makefil文件:
# toolchain
CC = arm-none-eabi-gcc
CP = arm-none-eabi-objcopy
AS = arm-none-eabi-gcc -x assembler-with-cpp# all the files will be generated with this name
PROJECT_NAME=stm32f10x_project# user specific
SRC += ./user/main.c
# startup
ASM_SRC += ./user/startup_stm32f10x_md.sOBJECTS = $(ASM_SRC:.s=.o) $(SRC:.c=.o)
# Define optimisation level here
MC_FLAGS = -mcpu=cortex-m3
AS_FLAGS = $(MC_FLAGS) -g -mthumb
CP_FLAGS = $(MC_FLAGS) -g -mthumb -Wall -fverbose-asm
LD_FLAGS = $(MC_FLAGS) -g -mthumb -Xlinker --gc-sections -T stm32_flash.ld # makefile rules
all: $(OBJECTS) $(PROJECT_NAME).elf $(PROJECT_NAME).hex $(PROJECT_NAME).binarm-none-eabi-size $(PROJECT_NAME).elf%.o: %.c$(CC) -c $(CP_FLAGS) -I . $(INC_DIR) $< -o $@%.o: %.s$(AS) -c $(AS_FLAGS) $< -o $@%.elf: $(OBJECTS)$(CC) $(OBJECTS) $(LD_FLAGS) -o $@%.hex: %.elf$(CP) -O ihex $< $@%.bin: %.elf$(CP) -O binary -S $< $@clean:del /Q $(PROJECT_NAME).elf $(PROJECT_NAME).hex $(PROJECT_NAME).bin
编写main文件
程序需要在main函数中实现对stm32f103rb芯片的控制,为了直观的显示结果,程序通过一个gpio控制一个led灯,main函数如下:
/**
*********************************************************************************************************
* (c) Copyright 2024-2032
* All Rights Reserved
* @By : liwei
*********************************************************************************************************
**/
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
// 定义STM32F103的寄存器基地址
#define STM32_RCC_BASE 0x40021000
#define STM32_GPIOB_BASE 0x40010C00 // 定义寄存器偏移
#define RCC_APB2ENR_OFFSET 0x18
#define GPIOB_CRL_OFFSET 0x00
#define GPIOB_CRH_OFFSET 0x04
#define GPIOB_ODR_OFFSET 0x0C // 定义寄存器地址
#define RCC_APB2ENR_REG (*(volatile uint32_t *)(STM32_RCC_BASE + RCC_APB2ENR_OFFSET))
#define GPIOB_CRL_REG (*(volatile uint32_t *)(STM32_GPIOB_BASE + GPIOB_CRH_OFFSET))
#define GPIOB_ODR_REG (*(volatile uint16_t *)(STM32_GPIOB_BASE + GPIOB_ODR_OFFSET))// 定义位掩码
#define RCC_APB2ENR_IOPBEN ((uint32_t)0x00000008)
#define GPIO_MODE_OUT_50MHZ ((uint32_t)0x0002)
#define GPIO_CNF_PP ((uint32_t)0x0000)
#define GPIO_PIN15 ((uint16_t)0x8000)
/***********************************************************************************************************
* @描述 : 延时函数
***********************************************************************************************************/
void delay(volatile uint32_t count)
{while (count--) {// 空循环}
}
/***********************************************************************************************************
* @描述 : 配置GPIOB引脚
***********************************************************************************************************/
void GPIOB_ConfigPin15AsOutput(void)
{// 使能GPIOB时钟RCC_APB2ENR_REG |= RCC_APB2ENR_IOPBEN;// 配置GPIOB引脚15的模式和配置uint32_t temp = GPIOB_CRL_REG;temp &= ~(0x0F000000); // 清除temp |= (GPIO_MODE_OUT_50MHZ << 28); // 设置MODE15为通用推挽输出模式,最大速度50MHztemp &= ~(0x00F00000); // 清除CNF15位(第20-23位)temp |= (GPIO_CNF_PP << 30); // 设置CNF15为推挽输出配置GPIOB_CRL_REG = temp;
}
/***********************************************************************************************************
* @描述 : main
***********************************************************************************************************/
int main(void)
{// 配置GPIOB引脚GPIOB_ConfigPin15AsOutput();// 主循环while (1) {// 切换GPIOB引脚15的状态GPIOB_ODR_REG = GPIO_PIN15; delay(1000000); // 延时// 切换GPIOB引脚15的状态GPIOB_ODR_REG &= ~GPIO_PIN15; delay(1000000); // 延时}
}
程序通过直接控制寄存器的方式控制GPIOB引脚15的状态,从而实现对led状态的控制。
在Makefile文件路径下运行cmd,在终端中执行make指令,我们将得到.hex的可执行文件,将执行文件下载到stm32f103rb中可以发现被控制的led灯闪烁。

至此,我们已经基本完成目标,使用gcc工具编译得到了一个stm32f103rb可执行文件,大功告成!

2.4完善启动文件
虽然我们实现了目标,但是实际上目前完成的工程只是一个简单的,不完善的工程,接下来我们继续完善启动文件,深入理解芯片启动的完整过程。
启动代码如下:
/**
*********************************************************************************************************
* (c) Copyright 2024-2032
* All Rights Reserved
* @By : liwei
*********************************************************************************************************
**/.syntax unified.cpu cortex-m3.fpu softvfp.thumb.global g_pfnVectors
.global Default_Handler/* start address for the initialization values of the . defined in linker script */
.word _sidata
.word _sdata
.word _edata
.word _sbss
.word _ebss.equ BootRAM, 0xF108F85F/******************************************************************************
复位启动函数Reset_Handler
******************************************************************************/ .section .text.Reset_Handler.weak Reset_Handler.type Reset_Handler, %function
Reset_Handler: /* 将初始化数据段器从FLASH复制到SRAM*/ movs r1, #0b LoopCopyDataInitCopyDataInit:ldr r3, =_sidataldr r3, [r3, r1]str r3, [r0, r1]adds r1, r1, #4LoopCopyDataInit:ldr r0, =_sdataldr r3, =_edataadds r2, r0, r1cmp r2, r3bcc CopyDataInitldr r2, =_sbssb LoopFillZerobss
/* 初始化 bss */
FillZerobss:movs r3, #0str r3, [r2], #4LoopFillZerobss:ldr r3, = _ebsscmp r2, r3bcc FillZerobss
/* 跳转到main函数*/bl mainbx lr
.size Reset_Handler, .-Reset_Handler/******************************************************************************
默认中断代替函数
******************************************************************************/ .section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:b Infinite_Loop.size Default_Handler, .-Default_Handler
/******************************************************************************
向量表
******************************************************************************/ .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 RTCAlarm_IRQHandler.word USBWakeUp_IRQHandler .word 0.word 0.word 0.word 0.word 0.word 0.word 0.word BootRAM
/*******************************************************************************
*
* 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 RTCAlarm_IRQHandler.thumb_set RTCAlarm_IRQHandler,Default_Handler.weak USBWakeUp_IRQHandler.thumb_set USBWakeUp_IRQHandler,Default_Handler
首先程序定义了一个完整了向量表,它不仅包含SP初始化值、PC初始化值、同时还包括stm32f103rb芯片所有的中断向量。
g_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
.
.
.
在复位函数Reset_Handler中,完成了对data 段和 bss段的初始化,最后跳转到mian函数。
/******************************************************************************
复位启动函数Reset_Handler
******************************************************************************/ .section .text.Reset_Handler.weak Reset_Handler.type Reset_Handler, %function
Reset_Handler: /* 将初始化数据段器从FLASH复制到SRAM*/ movs r1, #0b LoopCopyDataInitCopyDataInit:ldr r3, =_sidataldr r3, [r3, r1]str r3, [r0, r1]adds r1, r1, #4LoopCopyDataInit:ldr r0, =_sdataldr r3, =_edataadds r2, r0, r1cmp r2, r3bcc CopyDataInitldr r2, =_sbssb LoopFillZerobss
/* 初始化 bss */
FillZerobss:movs r3, #0str r3, [r2], #4LoopFillZerobss:ldr r3, = _ebsscmp r2, r3bcc FillZerobss
/* 跳转到main函数*/bl mainbx lr
.size Reset_Handler, .-Reset_Handler
问题:程序中的初始化静态变量的初始值是怎么来的?
如全局变量:int value = 1314;
value是保存在ram中的变量,芯片重启后ram中的值是随机的,程序是怎么让ram中的值为1314的?

利用分散加载技术,程序将存放在ROM中的data 段数据复制到RAM指定的位置,从而实现了data 段的数据初始化。Reset_Handler中的如下代码完成了数据加载功能:
/* 将初始化数据段器从FLASH复制到SRAM*/ movs r1, #0b LoopCopyDataInitCopyDataInit:ldr r3, =_sidataldr r3, [r3, r1]str r3, [r0, r1]adds r1, r1, #4LoopCopyDataInit:ldr r0, =_sdataldr r3, =_edataadds r2, r0, r1cmp r2, r3bcc CopyDataInitldr r2, =_sbssb LoopFillZerobss
在Makefile文件路径下运行cmd,在终端中执行make指令,我们将得到.hex的可执行文件,将执行文件下载到stm32f103rb中可以发现被控制的led灯闪烁。

2.5完善工程
我们还剩下最后一个问题:
使用st官方的库进行编程(不使直接用寄存器地址编程)
此时我们要添加st的官方库文件,修改Makefile文件,修改mian.c文件。
mian.c内容如下:
/**
*********************************************************************************************************
* (c) Copyright 2024-2032
* All Rights Reserved
* @By : liwei
*********************************************************************************************************
**/
#include "stm32f10x.h"
#include "stm32f10x_conf.h"/***********************************************************************************************************
* @描述 : 延时函数
***********************************************************************************************************/
void Delay(__IO uint32_t nCount)
{for(; nCount != 0; nCount--);
}
/***********************************************************************************************************
* @描述 : 设置使能
***********************************************************************************************************/
void RCC_Configuration(void)
{/* GPIOA, GPIOB clock enable */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
}
/***********************************************************************************************************
* @描述 : 配置GPIO
***********************************************************************************************************/
void GPIO_Configuration(void)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/***********************************************************************************************************
* @描述 : main函数
***********************************************************************************************************/
int main(void)
{RCC_Configuration();GPIO_Configuration();//LED闪烁while (1){GPIO_ResetBits(GPIOB, GPIO_Pin_15);Delay(300000);GPIO_SetBits(GPIOB, GPIO_Pin_15);Delay(300000); }
}
Makefile内容如下:
# toolchain
TOOLCHAIN = arm-none-eabi-
CC = $(TOOLCHAIN)gcc
CP = $(TOOLCHAIN)objcopy
AS = $(TOOLCHAIN)gcc -x assembler-with-cpp
# all the files will be generated with this name (main.elf, main.bin, main.hex, etc)
PROJECT_NAME=stm32f10x_project# define include dir
INCLUDE_DIRS =# define stm32f10x lib dir
STM32F10x_LIB_DIR = ./stm32f10x_lib# define user dir
USER_DIR = ./user# link file
LINK_SCRIPT = ./stm32_flash.ld# user specific
SRC =
ASM_SRC =
SRC += $(USER_DIR)/main.c
# user include
INCLUDE_DIRS = $(USER_DIR)# source director
STM32F1_STD_LIB = $(STM32F10x_LIB_DIR)/STM32F10x_StdPeriph_Driver
STM32F1_CORE_DIR = $(STM32F10x_LIB_DIR)/CMSIS/CM3/CoreSupport
STM32F1_DEVICE_DIR = $(STM32F10x_LIB_DIR)/CMSIS/CM3/DeviceSupport/ST/STM32F10x
STM32F1_SRC_DIR = $(STM32F1_STD_LIB)/src
STM32F1_INC_DIR = $(STM32F1_STD_LIB)/inc# startup
ASM_SRC += $(STM32F1_DEVICE_DIR)/startup/gcc_ride7/startup_stm32f10x_md.s# CMSIS
SRC += $(STM32F1_DEVICE_DIR)/system_stm32f10x.c
SRC += $(STM32F1_CORE_DIR)/core_cm3.c# use libraries, please add or remove when you use or remove it.
SRC += $(STM32F1_SRC_DIR)/stm32f10x_rcc.c
SRC += $(STM32F1_SRC_DIR)/stm32f10x_gpio.c
SRC += $(STM32F1_SRC_DIR)/stm32f10x_exti.c
SRC += $(STM32F1_SRC_DIR)/stm32f10x_usart.c
SRC += $(STM32F1_SRC_DIR)/misc.c# include directories
INCLUDE_DIRS += $(STM32F1_CORE_DIR)
INCLUDE_DIRS += $(STM32F1_DEVICE_DIR)
INCLUDE_DIRS += $(STM32F1_INC_DIR)
INCLUDE_DIRS += $(STM32F1_STD_LIB)
INC_DIR = $(patsubst %, -I%, $(INCLUDE_DIRS))
OBJECTS = $(ASM_SRC:.s=.o) $(SRC:.c=.o)# Define optimisation level here
MC_FLAGS = -mcpu=cortex-m3
AS_FLAGS = $(MC_FLAGS) -g -gdwarf-2 -mthumb -Wa,-amhls=$(<:.s=.lst)
CP_FLAGS = $(MC_FLAGS) -Os -g -gdwarf-2 -mthumb -fomit-frame-pointer -Wall -fverbose-asm -Wa,-ahlms=$(<:.c=.lst)
LD_FLAGS = $(MC_FLAGS) -g -gdwarf-2 -mthumb -nostartfiles -Xlinker --gc-sections -T$(LINK_SCRIPT) -Wl,-Map=$(PROJECT_NAME).map,--cref,--no-warn-mismatch# makefile rules
all: $(OBJECTS) $(PROJECT_NAME).elf $(PROJECT_NAME).hex $(PROJECT_NAME).bin$(TOOLCHAIN)size $(PROJECT_NAME).elf%.o: %.c$(CC) -c $(CP_FLAGS) -I . $(INC_DIR) $< -o $@%.o: %.s$(AS) -c $(AS_FLAGS) $< -o $@%.elf: $(OBJECTS)$(CC) $(OBJECTS) $(LD_FLAGS) -o $@%.hex: %.elf$(CP) -O ihex $< $@%.bin: %.elf$(CP) -O binary -S $< $@clean:del /Q $(PROJECT_NAME).elf $(PROJECT_NAME).hex $(PROJECT_NAME).bin
在Makefile文件路径下运行cmd,在终端中执行make指令,我们将得到.hex的可执行文件,将执行文件下载到stm32f103rb中可以发现被控制的led灯闪烁。

3.总结
为了实现如何不使用任何IDE集成软件编译得到stm32程序的执行文件,安装了GCC和make工具。
为了编译得到stm32f103rb执行文件,需要完成以下操作:
1、编写启动文件
2、编写链接文件
3、编写Makeflie文件
4、编写C语言源代码

希望获取源码的朋友可以在评论区留言
希望获取源码的朋友可以在评论区留言
希望获取源码的朋友可以在评论区留言
创作不易希望朋友们点赞,转发,评论,关注!
您的点赞,转发,评论,关注将是我持续更新的动力!
CSDN:https://blog.csdn.net/li_man_man_man
今日头条:https://www.toutiao.com/article/7149576260891443724
相关文章:
如何使用GCC手动编译stm32程序
如何不使用任何IDE(集成开发环境)编译stm32程序? 集成开发环境将编辑器、编译器、链接器、调试器等开发工具集成在一个统一的软件中,使得开发人员可以更加简单、高效地完成软件开发过程。如果我们不使用KEIL,IAR等集成开发环境,…...
在线绘制Nature Communication同款双色、四色火山图,突出感兴趣的基因
导读:火山图通常使用三种颜色分别表示显著上调,显著下调和不显著。通过为特定的数据点添加另一种颜色,可以创建双色或四色火山图,从而更直观地突出感兴趣的数据点。 《Nature Communication》文章“Molecular and functional land…...
C语言:C语言实现对MySQL数据库表增删改查功能
基础DOME可以用于学习借鉴; 具体代码 #include <stdio.h> #include <mysql.h> // mysql 文件,如果配置ok就可以直接包含这个文件//宏定义 连接MySQL必要参数 #define SERVER "localhost" //或 127.0.0.1 #define USER "roo…...
C++ 二叉搜索树(Binary Search Tree, BST)深度解析与全面指南:从基础概念到高级应用、算法优化及实战案例
🌟个人主页:落叶 🌟当前专栏: C专栏 目录 ⼆叉搜索树的概念 ⼆叉搜索树的性能分析 ⼆叉搜索树的插⼊ ⼆叉搜索树的查找 二叉搜索树中序遍历 ⼆叉搜索树的删除 cur的左节点为空的情况 cur的右节点为空的情况 左,右节点都不为…...
刷题日常(移动零,盛最多水的容器,三数之和,无重复字符的最长子串)
移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 俩种情况: 1.当nums[i]为0的时候 直接i 2.当nums[i]不为0的时候 此时 …...
深入了解决策树---机器学习中的经典算法
引言 决策树(Decision Tree)是一种重要的机器学习模型,以直观的分层决策方式和简单高效的特点成为分类和回归任务中广泛应用的工具。作为解释性和透明性强的算法,决策树不仅适用于小规模数据,也可作为复杂模型的基石&…...
Elasticsearch对于大数据量(上亿量级)的聚合如何实现?
大家好,我是锋哥。今天分享关于【Elasticsearch对于大数据量(上亿量级)的聚合如何实现?】面试题。希望对大家有帮助; Elasticsearch对于大数据量(上亿量级)的聚合如何实现? 1000道 …...
深度学习模型:循环神经网络(RNN)
一、引言 在深度学习的浩瀚海洋里,循环神经网络(RNN)宛如一颗独特的明珠,专门用于剖析序列数据,如文本、语音、时间序列等。无论是预测股票走势,还是理解自然语言,RNN 都发挥着举足轻重的作用。…...
前端---HTML(一)
HTML_网络的三大基石和html普通文本标签 1.我们要访问网络,需不需要知道,网络上的东西在哪? 为什么我们写,www.baidu.com就能找到百度了呢? 我一拼ping www.baidu.com 就拼到了ip地址: [119.75.218.70]…...
SQL 复杂查询
目录 复杂查询 一、目的和要求 二、实验内容 (1)查询出所有水果产品的类别及详情。 查询出编号为“00000001”的消费者用户的姓名及其所下订单。(分别采用子查询和连接方式实现) 查询出每个订单的消费者姓名及联系方式。 在…...
银河麒麟桌面系统——桌面鼠标变成x,窗口无关闭按钮的解决办法
银河麒麟桌面系统——桌面鼠标变成x,窗口无关闭按钮的解决办法 1、支持环境2、详细操作说明步骤1:用root账户登录电脑步骤2:导航到kylin-wm-chooser目录步骤3:编辑default.conf文件步骤4:重启电脑 3、结语 Ὁ…...
抓包之使用chrome的network面板
写在前面 本文看下工作中非常非常常用的chrome的network面板功能。 官方介绍:地址。 1:前置 1.1:打开 右键-》检查,或者F12。 1.2:组成部分 2:控制器常用功能 详细如下图: 接着我们挑选其…...
避坑ffmpeg直接获取视频fps不准确
最近在做视频相关的任务,调试代码发现一个非常坑的点,就是直接用ffmpeg获取fps是有很大误差的,如下: # GPT4o generated import ffmpegprobe ffmpeg.probe(video_path, v"error", select_streams"v:0", sho…...
大数据新视界 -- 大数据大厂之 Hive 函数库:丰富函数助力数据处理(上)(11/ 30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
深入解析 Django 中数据删除的最佳实践:以动态管理镜像版本为例
文章目录 引言场景与模型设计场景描述 删除操作详解1. 删除单个 Tag2. 批量删除 Tags3. 删除前确认4. 日志记录 高阶优化与问题分析1. 外键约束与误删保护2. 并发删除的冲突处理3. 使用软删除 结合 Django Admin 的实现总结与实践思考 引言 在现代应用开发中,服务和…...
【java】sdkman-java多环境切换工具
#java #env #sdk #lcshand 首先我们来复习一下,可参考我原来的文章: python多个版本的切换可用pyenv nodejs多个版本的切换可用nvm 同样,java多个版本的切换可用sdkman和jenv,我偏重于使用sdkman,因为有时候我也需要…...
11.25c++继承、多态
练习: 编写一个 武器类 class Weapon{int atk; }编写3个武器派生类:短剑,斧头,长剑 class knife{int spd; }class axe{int hp; }class sword{int def; }编写一个英雄类 class Hero{int atk;int def;int spd;int hp; public:所有的…...
STM32F103外部中断配置
一、外部中断 在上一节我们介绍了STM32f103的嵌套向量中断控制器,其中包括中断的使能、失能、中断优先级分组以及中断优先级配置等内容。 1.1 外部中断/事件控制器 在STM32f103支持的60个可屏蔽中断中,有一些比较特殊的中断: 中断编号13 EXTI…...
阿里电商大整合,驶向价值竞争新航道
阿里一出手就是王炸。11月21日,阿里公布了最新动作:将国内和海外电商业务整合,成立新的电商事业群。这是阿里首次将所有电商业务整合到一起,也对电商行业未来发展有着借鉴意义。阿里为何要这么干?未来又将给行业带来哪…...
等保测评在云计算方面的应用讲解
等保测评(信息安全等级保护测评)在云计算方面的应用主要聚焦于如何满足等级保护相关要求,并确保云计算平台及其上运行的业务系统的安全性。以下是主要内容的讲解: 1. 云计算中的等保测评概述 等保测评是在我国网络安全等级保护制…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
