如何使用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等集成开发环境,…...
2023年MathorCup高校数学建模挑战赛—大数据竞赛B题电商零售商家需求预测及库存优化问题求解全过程文档及程序
2023年MathorCup高校数学建模挑战赛—大数据竞赛 B题 电商零售商家需求预测及库存优化问题 原题再现: 电商平台存在着上千个商家,他们会将商品货物放在电商配套的仓库,电商平台会对这些货物进行统一管理。通过科学的管理手段和智能决策&…...
不可分割的整体—系统思考的微妙法则
不可分割的整体——系统思考的微妙法则 作为企业领导者,我们经常需要做出决策,但有时候,我们会忽略一个事实:每个决策都不是孤立的,它背后都是一个复杂系统的一部分。 无论是市场动态、团队协作,还是产品…...
使用Grafana K6来测测你的系统负载能力
背景 近期我们有个号称会有很高很高并发的系统要上线,为了测试一下自己开发的系统的负载能力,准备了点海克斯科技,来看看抗不抗的住。 之前笔者写过用Apache JMeter进行压力测试的文章(传送门👉:https://…...
ENSP IPV6-over-IPV4
IPv6是网络层协议的第二代标准协议,一个IPv6地址同样可以分为网络前缀和主机ID两个部分。 可以将IPV4的网络看成IPV6的承载网,只有IPv4网络是连通的,则IPv6网络才有可能连通。所以配置的时候需要先配置IPv4网络的路由功能,再配IP…...
FLASH分区---FAT分区添加操作
1、板卡配置 注意:使用fat文件系统的时候,必须download进去一个fat系统的镜像 fat.img 0xee0000 注意:需要打开fat宏定义(涉及到底层,必须开,否则无法创建文件) 2、板卡.c 配置 修改分区大小、增…...
javax.xml.ws.soap.SOAPFaultException: ZONE_OFFSET
javax.xml.ws.soap.SOAPFaultException 表示 SOAP 调用过程中发生了错误,并且服务端返回了一个 SOAP Fault。 错误信息中提到的 ZONE_OFFSET 可能指的是时区偏移量。在日期和时间处理中,时区偏移量是指格林威治标准时间 (GMT) 的偏移量。如果服务期望特…...
针对解决conda环境BUG的个人笔记
1-conda学习&安装 安装视频: 零基础教程:基于Anaconda和PyCharm配置Pytorch环境_哔哩哔哩_bilibili 安装过程: MX250笔记本安装Pytorch、CUDA和cuDNN-CSDN博客 Win10MX250CUDA10.1cuDNNPytorch1.4安装测试全过程(吐血)_nvidia geforc…...
代理IP与百度在信息时代的交互
目录 一、代理IP的基本概念和工作原理 二、代理IP在百度搜索中的多重作用 解决网络延时问题,提高搜索速度 提高网络安全 隐藏用户的真实IP地址,保护个人隐私 突破访问限制,拓宽网络视野 三、代理IP在百度关键词排名优化中的应用 模拟…...
10、PyTorch autograd使用教程
文章目录 1. 相关思考2. 矩阵求导3. 两种方法求jacobian 1. 相关思考 2. 矩阵求导 假设我们有如下向量: y 1 3 x 1 5 [ w T ] 5 3 b 1 3 \begin{equation} y_{1\times3}x_{1\times5}[w^T]_{5\times3}b_{1\times3} \end{equation} y13x15[wT]53b13…...
Flink随笔 20241129 流数据处理:以生产线烤鸡为例理解 Flink
流数据(streaming data)就像是一条永不停歇的生产线,源源不断地向前推进,带来新的数据。而 Apache Flink 就是这条生产线的核心,它负责对数据进行处理、分类、聚合和存储。为了更好地理解 Flink 的流处理,我…...
Web day02 Js Vue Ajax
目录 1.javascript: 1.js的引入方式: 2.js变量 & 数据类型 & 输出语句: 模板字符串: 3.函数 & 自定义对象: 4. json 字符串 & DOM操作: 5. js事件监听: 6.js的模块化导入或者导出&a…...
Vue的生命周期
Vue.js 的生命周期是指一个 Vue 实例从创建到销毁的整个过程。在这个过程中,Vue 会提供一系列的钩子函数(也称为生命周期钩子),开发者可以在这些钩子中执行特定的操作。理解 Vue 的生命周期对于编写高效、可维护的 Vue 应用至关重…...
【LeetCode热题100】优先级队列
这盘博客记录了关于优先级队列的几道题,包括最后一块石头的重量、数据流中的第K大元素、前K个高频单词、数据流的中位数。 class Solution { public:int lastStoneWeight(vector<int>& stones) {priority_queue<int> heap;for(auto s : stones) hea…...
用go语言写一个小服务
文章目录 简介重新想到go 小服务main.go部署测试 结束语 简介 golang的优势 响应速度: Go > Java > Python 内存占用: Go < Java < Python 从java转go,然后go又转java,感觉就是go虽然在编译、内存占用都强于java&am…...
shell编程练习巩固
一、用shell写出一个简单的计算器。 其实用shell写个简单的计算器还是很简单,我们不用构建那么复杂的计算功能,只需要复现出简单的加减乘除取余即可。 既然是计算器就要明确一下思路: 用户可以输入一个数字根据数字选择加、减、乘、除、取…...
pytest+allure生成报告显示loading和404
pytestallure执行测试脚本后,通常会在电脑的磁盘上建立一个临时文件夹,里面存放allure测试报告,但是这个测试报告index.html文件单独去打开,却显示loading和404, 这个时候就要用一些办法来解决这个报告显示的问题了。 用命令产生…...
从数据孤岛到数据协同:企业如何构建安全的数据共享生态?
聚焦数据协作与隐私保护技术,探索企业如何在共享中保持安全性。 导读 在数字经济时代,数据已成为企业最宝贵的战略资源。然而,传统的"数据孤岛"模式正阻碍企业价值创新。本文将深度解析如何突破数据壁垒,构建安全高效的…...
数据采集中,除了IP池的IP被封,还有哪些常见问题?
在数据采集的过程中,代理IP池的使用无疑为我们打开了一扇通往信息宝库的大门。然而,除了IP被封禁这一常见问题外,还有许多其他问题可能影响数据采集的效果。本文将探讨在数据采集中,除了IP被封之外,还可能遇到的一些常…...
数据结构--数组
目录 1 定义 1.1 数组内存结构 1.2二维数组 2 练习 2.1 将数组内两个区间内有序元素合并 2.2 leetcode88. 合并两个有序数组 3 缓存与局部性原理 1 定义 1.1 数组内存结构 1 2 3 5 6 给数组添加元素时,应将原来添加位置的元素和之后的元素进行复制 System…...
基础入门-Web应用架构搭建域名源码站库分离MVC模型解析受限对应路径
知识点: 1、基础入门-Web应用-域名上的技术要点 2、基础入门-Web应用-源码上的技术要点 3、基础入门-Web应用-数据上的技术要点 4、基础入门-Web应用-解析上的技术要点 5、基础入门-Web应用-平台上的技术要点 一、演示案例-域名差异-主站&分站&端口站&…...
屏幕触控支持指纹
一、前端navigator.maxTouchPoints获取屏幕是否支持触控。 二、navigator.maxTouchPoints c接口修改。 1、third_party\blink\renderer\core\events\navigator_events.idl // https://w3c.github.io/pointerevents/#extensions-to-the-navigator-interface[ImplementedAsNavi…...
大米中的虫子检测-检测储藏的大米中是否有虫子 支持YOLO,VOC,COCO格式标注,4070张图片的数据集
大米中的虫子检测-检测储藏的大米中是否有虫子 支持YOLO,VOC,COCO格式标注,4070张图片的数据集 数据集分割 4070总图像数 训练组 87% 3551图片 有效集 9% 362图片 测试集 4% 157图片 预处理 自动定向…...
力扣第 74 题是 搜索二维矩阵
题目描述 给定一个 m x n 的矩阵 matrix 和一个目标值 target,请你编写一个函数来判断目标值 target 是否在矩阵中。 每行的元素按升序排列。每列的元素按升序排列。 示例 1 输入: matrix [[1, 4, 7, 11],[2, 5, 8, 12],[3, 6, 9, 16],[10, 13, 14…...
JavaScript实用工具lodash库
Lodash中文文档: Lodash 简介 | Lodash中文文档 | Lodash中文网 Lodash是一个功能强大、易于使用的JavaScript实用工具库,它提供了丰富的函数和工具,能够方便地处理集合、字符串、数值、函数等多种数据类型。通过使用Lodash,开发者可以大幅…...
MySQL之JDBC
我们在学习完了数据库的基本操作后,希望和我们的Java程序建立连接,那么我们今天就来一探究竟JDBC是如何让Java程序与数据库建立连接的 1. 什么是JDBC JDBC(Java Data Base Connectivity, Java数据库连接) 是Java程序和数据库之间…...
家校通小程序实战教程04教师管理
目录 1 创建数据源2 搭建管理后台3 搭建查询条件4 功能测试总结 我们上一篇介绍了如何将学生加入班级,学生加入之后就需要教师加入了。教师分为任课老师和班主任,班主任相当于一个班级的管理员,日常可以发布各种任务,发布接龙&…...
vitess使用记录:vtctldclient,设置分表规则
继续探索未完成的事情。 vitess使用记录系列已经写了好几篇了,记录了在测试过程中遇到的各种问题。《vitess使用:从部署到go客户端连接查询》、《vitess使用记录:vtctldclient》、《vitess使用:基于源码运行vtctldclient工具》整…...
Windows利用conda安装gpu版本Faiss + Ubuntu源码安装Faiss-gpu 记录(待更新~)
前言 由于在cpu上使用对向量检索算法时,发现面对数据量较大时,批量匹配耗时会显著增加,影响业务整体响应。便尝试使用GPU来实现检索计算,限于本人技术有限,写不出好算法。便取巧利用Faiss-gpu来检索(* ^ ▽ ^ *) 以下…...
react学习记录
目录结构react优秀代码之react目录结构简洁之道React 作为一个库,不会决定你如何组织项目的结构。这是件好事,因为这样 - 掘金【React】项目的目录结构全面指南_react项目结构-CSDN博客 生命周期【React 面经】生命周期详解:不同阶段与方法解…...
MaskRCNN训练自己的数据集
一. 数据标注 1. 安装labelme软件 conda install labelme2. 运行labelme # 命令行中直接输入 labelme3. 标注 二、训练数据处理 1. 在根目录下创建datasets/demo1文件夹,创建如下几个文件夹 2. 将标注好的.json文件放在json文件夹中 3. 原图放在pic文件夹中 4. …...
metawrap bin_refinement输入checkm数据库地址
这是运行metawrap bin_refinement -o bin_refinement -t 30 -A binning/metabat2_bins/ -B binning/maxbin2_bins/ -C binning/concoct_bins/ -c 50 -x 10 时遇到的报错(在命令行跑的时候遇到的) 参考metaGEM使用小记(解决各种问题)2024 2(三…...
Spring Web MVC其他扩展(详解下)
文章目录 Spring MVC其他扩展(下)异常处理异常处理机制声明式异常好处基于注解异常声明异常处理 拦截器拦截器概念拦截器使用拦截器作用位置图解拦截器案例拦截器工作原理源码 参数校验校验概述操作演示SpringMVC自定义参数验证ValueObject(VO) 文件上传…...
深度学习之 SegNet
可训练的图像分割引擎,包含一个encoder网络,一个对应的decoder网络,衔接像素级分类层,解码网络与VGG16的13层卷积层相同。解码网络是将低分辨率的编码特征图映射到全分辨率的特征图。解码网络使用最大池化层的池化索引进行非线性上…...
Taro React小程序开发框架 总结
目录 一、安装 二、目录结构 三、创建一个自定义页面 四、路由 1、API 2、传参 3、获取路由参数 4、设置TabBar 五、组件 六、API Taro非常好用的小程序框架,React开发者无缝衔接上。 一、安装 官方文档:Taro 文档 注意,项目创建…...
《Django 5 By Example》阅读笔记:p339-p358
《Django 5 By Example》学习第12天,p339-p358总结,总计20页。 一、技术总结 1.项目(购物网站) django-admin startproject myshop 虽然这里只是示例,但我觉得这种命名为 myxxx 的习惯非常不好,因为在实际应用中,是…...
CSS:Web美学的革新之旅
自HTML的诞生之日起,Web页面设计便踏上了一段不断进化的旅程。起初,HTML作为构建网页的骨架,仅承载着最基本的文本结构与少量显示属性。然而,随着互联网的蓬勃发展和用户对视觉体验需求的日益增长,HTML开始不堪重负&am…...
java全栈day10--后端Web基础(基础知识)之续集
一、Servlet执行流程 二、Http协议(相对Tomcat和servlet重要一点) 2.1Http-概叙 2.2Http-请求协议 2.2.3请求数据格式 2.2.3请求数据获取 先启动服务器 访问/hello Servlet 访问浏览器端Http协议数据 查看数据...
MySQL 与 MongoDB 存储差异分析
MySQL 与 MongoDB 存储差异分析:为什么随机生成数据的存储空间不同? 在实际应用中,我们常常需要选择合适的数据库系统来处理不同类型的数据。在这个过程中,数据库的 存储机制 和 性能优化 起着至关重要的作用。对于很多开发者来说…...
【ArcGIS Pro实操第10期】统计某个shp文件中不同区域内的站点数
统计某个shp文件中不同区域内的站点数 方法 1:使用“空间连接 (Spatial Join)”工具方法 2:使用“点计数 (Point Count)”工具方法 3:通过“选择 (Select by Location)”统计方法 4:通过“Python 脚本 (ArcPy)”实现参考 在 ArcGI…...
Django-Vue3-Admin - 现代化的前后端分离权限管理系统
项目介绍 Django-Vue3-Admin是一个基于RBAC(Role-Based Access Control)模型的综合性基础开发平台,专注于权限控制,支持列级别的细粒度权限管理。该项目采用前后端分离架构,技术栈包括: 后端: Django Django REST …...
【Java基础入门篇】二、控制语句和递归算法
Java基础入门篇 二、控制语句和递归算法 2.1 switch-case多分支选择语句 switch执行case语句块时,若没有遇到break,则运行下一个case直到遇到break,最后的default表示当没有case与之匹配时,默认执行的内容,代码示例如…...
PS的功能学习
背景差色较大,就魔棒 魔棒的连续就是倒水点的跨越问题 魔棒的容差的选择就有点看经验了,看颜色的统一程度选择 Ctrl D 取消当前所有的选区 至于快速选择工具,和对象选择工具也差不多,只不过控制范围变成了一块一块的&#x…...
yolov8的深度学习环境安装(cuda12.4、ubuntu22.04)
目录 一、先安装基础环境包 1.首先给Ubuntu安装Chrome浏览器(搜索引擎换成百度即可) 2、ubuntu 22.04中文输入法安装 3、安装 terminator 4、安装WPS for Linux 5、安装其它之前需要先安装anaconda 6、安装配置anaconda 7、安装完成anaconda后创建…...
《JavaEat:探索 Java 在美食世界的奇妙之旅》
在当今数字化的时代,编程语言的应用领域不断拓展,而 Java 作为一种广泛使用且功能强大的编程语言,其影响力早已超越了传统的软件开发范畴。当我们将目光聚焦在美食领域时,会惊喜地发现 Java 也能在其中发挥独特而重要的作用。本文…...
将excel文件中的信息读取后批量生成word文件
在日常办公过程中,可能需要把excel文件中的信息批量生成成百上千份word文档,便于打印、发邮件或存档等,比如根据excel中的合格人员招聘信息生成word合同文件,或是根据excel中的参会人员名单生成word参会通知等。 首先需要制作wor…...
Android 图形系统之三:SurfaceControl
在 Android 系统中,SurfaceControl 是一个关键的类,用于管理应用窗口和屏幕上的显示内容。它与 SurfaceFlinger 紧密交互,通过 BufferQueue 提供高效的图形缓冲区管理能力。SurfaceControl 是 Android 的显示架构中不可或缺的部分,…...
Nodemailer使用教程:在Node.js中发送电子邮件
目录 1. 简介 2. 安装 3. 基本配置 3.1 创建传输器 3.2 配置说明 4. 发送邮件 4.1 基本发送示例 4.2 发送验证码示例 5. 常见问题解决 5.1 "Greeting never received" 错误 5.2 安全建议 SMTP与邮件加密协议详解 1. SMTP简介 1.1 基本特点 2. 加密协…...
shell第二次作业
1. 使用case实现成绩优良差的判断 read -p "请输入你的成绩:" score if ! [[ "$score" ~ ^[0-9]$ ]];then echo "请输入数字" exit 1 fi if [ "$score" -lt 0 ] || [ "$score" -gt 100 ];then echo …...
MySQL Linux 离线安装
下载 进入官网,下载对应的需要MySQL版本,这里是历史版本。 官网 选择第一个MySQL Community Sever社区版,因为这个是免费的。 选择需要的对应版本: 安装 1.将下载好的安装包上传到服务器端 使用FinalShell 客户端连接服务器 …...