当前位置: 首页 > news >正文

【单片机开发 - STM32(H7)】启动流程、方式、烧录方式详解

如侵权,联系删,个人总结学习用

参考资料:(最末尾有我的原生笔记,那个格式规范点)

安富莱

ARM汇编伪指令详解-CSDN博客

【STM32】STM32内存映射以及启动过程(超详细过程)-CSDN博客

前言:

  • 安富莱:

本章教程主要跟大家讲 STM32H7 的启动过程,这里的启动过程是指从 CPU 上电复位执行第 1 条指令开始(汇编文件)到进入 C 程序 main()函数入口之间的部分

启动过程相对来说还是比较重要的,理解了这个过程,对于以后分析程序还是有些帮助的,要不每次看到这个启动过程都会跳过,直接去看主程序了。还有就是以后打算学习 RTOS 的话,对于这个过程必须有个了解,因为移植的时候涉及到中断向量表。对初学者来说,看这个可能有些吃力,不过不要紧,随着自己做过一些简单的应用之后再来看这章,应该会有很多的帮助,由于我们的 V7 板子是基于 STM32H7XXX,所以我们这里主要针对 H7 系列的启动过程做一下分析,对于 F1, F4 系列也是大致相同的。

  • 我个人:

刚好对中断机制感兴趣,先复习一下启动流程

1. 启动文件

1.1. 不同编译器对应的启动文件

打开我们为本教程提供的工程文件,路径如下:

\Libraries\CMSIS\Device\ST\STM32H7xx\Source\Templates 在这个文件里面有 ST 官方为各个编译器提供的启动文件。

2. 启动文件分析

安富莱:鉴于 V7 开发板使用的是 STM32H743XI,下面我们详细的分析一下启动文件startup_stm32h743xx.s

预备知识

  • 分析前,先掌握一个小技能,遇到不认识的指令或者关键词可以检索。

  • 汇编指令
--AREA伪指令:指示汇编程序汇编新的代码节或数据节。程序是以程序段为单位组织代码的。段是相对独立的指令或代码序列,拥有特定的名称。段的种类有代码段、数据段和通用段代码段的内容为执行代码数据段存放代码运行时需要用到的数据通用段不包含用户代码和数据所有通用段共用一个空间AREA 伪指令用于定义一个代码段或数据段。 语法格式:AREA 段名 属性 1 ,属性 2 ,……其中,段名若以数字开头,则该段名需用 “ | ” 括起来,如 |1_test|属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:    — CODE 属性:用于定义代码段,默认为 READONLY — DATA 属性:用于定义数据段,默认为 READWRITE — READONLY 属性:指定本段为只读,代码段默认为 READONLY— READWRITE 属性:指定本段为可读可写,数据段的默认属性为 READWRITE — ALIGN 属性:使用方式为ALIGN表达式。在默认时, ELF (可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为 0 ~ 31 ,相应的对齐方式为表达式2次方。    — COMMON 属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元— NOINIT属性:  指定此数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或者将各个内存单元值初始化为0一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。使用示例: AREA Init , CODE , READONLY    该伪指令定义了一个代码段,段名为 Init ,属性为只读--SPACE和DCD伪指令:SPACE和DCD的功能类似:SPACE申请一片内存空间DCD申请一个字(32bit)的内存空间。SPACE和DCD的区别在于:SPACE申请空间但不赋初值DCD申请一个字的空间,并赋初值待补充

2.1. 启动文件由汇编编写,是系统上电复位后第一个执行的程序。主要做了以下工作:

下面先来看启动文件前面的介绍 (固件库版本: V1.2.0)

启动文件是后缀为.s 的汇编语言文本文件, 每行前面的分号表示此行是注释行

启动文件主要完成如下工作,即程序执行过程:

  1. 设置堆栈指针 SP = __initial_sp。(读中断向量表的第一个字,即 RAM 区的栈顶地址,将这个值加载到主栈指针(MSP))
  2. 设置 PC 指针 = Reset_Handler。(然后读中断向量表的第二个字,即复位处理函数地址,跳转到复位处理函数开始执行)
  3. 设置中断向量表。
  4. 配置系统时钟。
  • 配置外部 SRAM/SDRAM 用于程序变量等数据存储【这是可选的】
  1. 跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。

完整的启动流程图:
┌─────────────────────┐
│     上电/复位       │
└─────────┬───────────┘↓
┌─────────────────────┐
│   加载栈指针(MSP)   │ ← 从0x08000000读取(中断向量表的第一个字)
└─────────┬───────────┘↓
┌─────────────────────┐
│    Reset_Handler    │ ← 从0x08000004读取(中断向量表的第二个字)
└─────────┬───────────┘↓
┌─────────────────────┐
│     SystemInit      │ ← 系统时钟配置(使能FPU、使能HSI、设置向量表位置、配置系统时钟)
└─────────┬───────────┘↓
┌─────────────────────┐
│       __main        │ ← C库初始化
├─────────────────────┤
│    初始化.data段    │
│    清零.bss段       │
│    调用构造函数     │
└─────────┬───────────┘↓
┌─────────────────────┐
│        main         │ ← 用户代码开始
└─────────────────────┘为什么需要RAM区的栈顶地址的原因:【具体使用看堆栈机制】
- C函数调用需要栈来保存返回地址
- 局部变量需要栈空间
- 中断处理需要栈来保存上下文内存分布:
Flash (0x08000000):
┌──────────────────┐ 
│   Vector Table   │ 中断向量表
├──────────────────┤
│   .text          │ 代码段
├──────────────────┤
│   .rodata        │ 只读数据
├──────────────────┤
│   .data初始值    │ .data段的初始值
└──────────────────┘RAM (0x20000000):
┌──────────────────┐ 
│     Stack        │ 栈空间
├──────────────────┤
│     Heap         │ 堆空间
├──────────────────┤
│     .bss         │ 未初始化数据
├──────────────────┤
│     .data        │ 已初始化数据
└──────────────────┘

Cortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为使用主堆栈 MSP。

2.2. 复位序列

2.3. 代码分析

2.3.1. 【初始化栈】实现开辟栈(stack)空间,用于局部变量、函数调用、函数的参数等

; 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>
// #define Stack_Size      0x00001000
Stack_Size      EQU     0x00001000
// STACK:段名;NOINIT:不初始化;READWRITE:可读可写;ALIGN=3:2^3,即 8 字节对齐AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp		// 栈的结束地址,即栈顶地址,需要保存栈顶的地址

第 7 行:EQU 是表示宏定义的伪指令,类似于 C 语言中的#define。

伪指令的意思是指这个“指令”并不会生成二进制程序代码,也不会引起变量空间分配。

Stack_Size 表示宏名

0x00001000 表示栈大小,注意这里是以字节为单位。

第 9 行: 开辟一段数据空间可读可写,段名 STACK,按照 8 字节对齐。

AREA 伪指令表示下面将开始定义一个代码段或者数据段

此处是定义数据段。

ARER 后面的关键字表示这个段的属性。

STACK :表示这个段的名字,可以任意命名。

NOINIT:表示此数据段不需要填入初始数据

READWRITE:表示此段可读可写

ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于 0)。

第 10 行: SPACE 这行指令用于分配一定大小的内存空间,单位为字节

给 STACK 段分配指定字节的连续内存空间,这里指定大小等于 Stack_Size。

第 11 行: __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。【指针】(栈区域的最高地址)

__initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。

地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。

【注意此处栈顶指针还未分配具体地址】【在汇编程序中可以理解为放在哪指哪里的地址】

2.3.2. 【初始化堆】实现开辟堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数分配的变量空间是在堆上。

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
// #define Heap_Size       0x00000800
Heap_Size       EQU     0x00000800// HEAP:段名;NOINIT:不初始化;READWRITE:可读可写;ALIGN=3:2^3,即 8 字节对齐AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base			// 堆的起始地址
Heap_Mem        SPACE   Heap_Size
__heap_limit		// 堆的结束地址

这几行语句和上面第 1 部分代码类似。

分配一片连续的内存空间给名字叫 HEAP 的段,也就是分配堆空间。堆的大小为 0x00000800。

__heap_base 表示堆的开始地址

__heap_limit 表示堆的结束地址

【注意此处堆指针还未分配具体地址】

2.3.3. 【初始化中断向量表】开辟向量表空间

				PRESERVE8THUMB; Vector Table Mapped to Address 0 at ResetAREA    RESET, DATA, READONLY	// RESET:段名;DATA:包含数据,不包含指令;READONLY:只读EXPORT  __VectorsEXPORT  __Vectors_EndEXPORT  __Vectors_Size
/* 声明 __Vectors、__Vectors_End 和 __Vectors_Size 这三个标号具有全局属性,可供外部的文件调用 */

第 1 行: PRESERVE8 指定当前文件保持堆栈八字节对齐

第 2 行: THUMB 表示后面的指令是 THUMB 指令集 , CM7 采用的是 THUMB - 2 指令集。

第 6 行: AREA 定义一块数据段,只读,段名字是 RESET。

READONLY 表示只读,缺省就表示代码段了。

第 7-9 行: 3 行 EXPORT 语句将 3 个标号申明为可被外部引用(声明一个标号可被外部的文件使用,使标号具有全局属性)

如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。

主要提供给链接器用于连接库文件或其他文件。【C 文件也可以调用】

2.3.4. 【初始化中断向量表】实现建立中断向量表,中断向量表定位在代码段的最前面。具体的物理地址由链接器的配置参数(IROM1 的地址)决定。

【内存映射】如果程序在 Flash 运行,则中断向量表的起始地址是 0x08000000。

以 MDK 为例,就是如下配置选项:

// __Vectors:向量表起始地址
__Vectors       DCD     __initial_sp               ; 栈顶地址(0x0800 0000),这里只是存了RAM区的栈顶地址,方便单片机启动DCD     Reset_Handler              ; 复位程序地址(0x0800 0000+0x04*1)DCD     NMI_Handler                ; NMI Handler(0x0800 0000+0x04*2)DCD     HardFault_Handler          ; Hard Fault HandlerDCD     MemManage_Handler          ; MPU Fault HandlerDCD     BusFault_Handler           ; Bus Fault HandlerDCD     UsageFault_Handler         ; Usage Fault HandlerDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     SVC_Handler                ; SVCall HandlerDCD     DebugMon_Handler           ; Debug Monitor HandlerDCD     0                          ; ReservedDCD     PendSV_Handler             ; PendSV HandlerDCD     SysTick_Handler            ; SysTick Handler中间部分省略未写// 外部中断开始DCD     WWDG_IRQHandler            ; Window WatchdogDCD     PVD_IRQHandler             ; PVD through EXTI Line detectDCD     TAMPER_IRQHandler          ; TamperDCD     RTC_IRQHandler             ; RTC// 限于篇幅,中间代码省略DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5// 限于篇幅,中间代码省略DCD     0                                 ; Reserved                                    DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins 
// __Vectors_End:向量表结束地址          
__Vectors_End
// 获得向量表大小(字节)
__Vectors_Size  EQU  __Vectors_End - __Vectors

DCD 表示分配 1 个 4 字节的空间。DCD:分配一个或者多个以字(4 字节)为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中,DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。

每行 DCD 都会生成一个 4 字节的二进制代码。

向量表从 FLASH 的 0 地址(一般 0x0800 0000)开始放置,以 4 个字节为一个单位。

  • 地址 0(0x0800 0000)存放的是栈顶地址
  • 0x04 存放的是复位程序的地址
  • 以此类推。

使用:

  • 中断发生:

当异常(也即是中断事件)发生时, CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序。

从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。【中断向量表存放的实际上是中断服务程序的入口地址

2.3.5. 【复位中断程序初始化】实现初始化复位中断服务程序

				AREA    |.text|, CODE, READONLY	// .text:段名;DATA:包含机器指令;READONLY:只读; Reset handler
Reset_Handler   PROCEXPORT  Reset_Handler             [WEAK]IMPORT  SystemInitIMPORT  __mainLDR     R0, =SystemInitBLX     R0               LDR     R0, =__mainBX      R0ENDP

复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C库函数 _mian,最终调用 main 函数去到 C 的世界。

第 1 行: AREA 定义一块代码段,只读,段名字是 .text 。 READONLY 表示只读。

第 4 行: 利用 PROCENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。

第 5 行: WEAK 表示弱定义,声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。

这个声明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可。

第 6 行: IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。

但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。

  • 这里表示 SystemInit 和 __main 这两个函数均来自外部的文件

第 9 行: SystemInit 函数(标准库函数)在文件 system_stm32h7xx.c/system_stm32f10x.c 里面,主要实现:

void SystemInit(void)
{/* FPU settings */SCB->CPACR |= ((3UL << 20)|(3UL << 22));  // 使能FPU/* Reset the RCC clock configuration */RCC->CR |= RCC_CR_HSION;                  // 使能HSI/* Configure the Vector Table location */SCB->VTOR = FLASH_BASE;                   // 设置向量表位置/* Configure the System clock source */SystemClock_Config();                     // 配置系统时钟
}
  • 配置系统时钟、RCC 相关寄存器复位
  • 中断向量表位置设置

第 11 行: __main 标号表示 C/C++标准实时库函数里的一个初始化子程序__main 的入口地址

__main()
{// 1. 初始化.data段memcpy(__data_start__, __data_load__, __data_end__ - __data_start__);// 2. 初始化.bss段为0memset(__bss_start__, 0, __bss_end__ - __bss_start__);// 3. 调用构造函数(如果有C++代码)__call_init_functions();// 4. 调用main函数main();// 5. main返回后的处理exit(0);
}
  1. 该程序的一个主要作用是初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号)
  2. 初始化映像文件
  3. 最后跳转到 C 程序中的 main 函数

这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规,并且不能更改。

2.3.6. 【中断程序初始化】实现其他中断程序初始化

如果使能了中断,中断服务函数实现方法:

  1. 在启动文件中写
  2. 在 C 文件中写(因为是弱定义)
; Dummy Exception Handlers (infinite loops which can be modified)
/* 初始化默认中断程序(无限循环) */
NMI_Handler     PROCEXPORT  NMI_Handler                		  [WEAK]B       .ENDP
HardFault_Handler\PROCEXPORT HardFault_Handler	 			  [WEAK]B .ENDP// 限于篇幅,中间代码省略
/* 外部中断 */
Default_Handler PROCEXPORT  WWDG_IRQHandler                   [WEAK]                                       EXPORT  PVD_AVD_IRQHandler                [WEAK]                   EXPORT  TAMP_STAMP_IRQHandler             [WEAK]// 限于篇幅,中间代码省略
SAI4_IRQHandler
WAKEUP_PIN_IRQHandlerB       .ENDPALIGN

第 5 行: 死循环,用户可以在此实现自己的中断服务程序。

不过很少在这里实现中断服务程序,一般多是在其它的 C 文件里面重新写一个同样名字的中断服务程序, 因为这里是 WEEK 弱定义的。

如果没有在其它文件中写中断服务器程序,且使能了此中断,进入到这里后,会让程序卡在这个地方。

第 14 行: 缺省中断服务程序(开始)

第 23 行: 死循环, 如果用户使能中断服务程序,而没有在 C 文件里面写中断服务程序的话,都会进入到这里

比如在程序里面使能了串口 1 中断,而没有写中断服务程序 USART1_IRQHandle, 那么串口中断来了,会进入到这个死循环。

第 25 行: 缺省中断服务程序(结束) 。

  • B:跳转到一个标号。这里跳转到一个‘.’,即表示无限循环。
  • PROC:过程(子程序)的开始。
  • ENDP:过程(子程序)的结束。
  • ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。

2.3.7. 实现用户堆栈初始化(MICROLIB 配置)(两种内存管理模式)

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************IF      :DEF:__MICROLIB	; MicroLIB模式:更简单的内存管理EXPORT  __initial_spEXPORT  __heap_baseEXPORT  __heap_limitELSE			; 标准模式:更复杂的内存管理IMPORT  __use_two_region_memory	// 这个函数由用户自己实现EXPORT  __user_initial_stackheap__user_initial_stackheap; 设置堆栈的边界LDR     R0, =  Heap_Mem	; 堆的起始地址 -> R0LDR     R1, =(Stack_Mem + Stack_Size)	; 栈顶地址 -> R1LDR     R2, = (Heap_Mem +  Heap_Size)	; 堆的结束地址 -> R2LDR     R3, = Stack_Mem	; 栈的起始地址 -> R3BX      LR					; 返回ALIGNENDIFEND

第 4 行:简单的汇编语言实现 IF…….ELSE…………语句。如果定义了 MICROLIB,那么程序是不会执行 ELSE 分支的代码。 __MICROLIB 可能大家并不陌生,就在 MDK 的 Target Option 里面设置。

MicroLIB是ARM提供的一个精简版C运行时库,专门为资源受限的嵌入式系统设计。

主要优势:

    • 更小的代码体积
    • 更快的执行速度
    • 更简单的实现
    • 更少的RAM使用

标准库与MicroLIB对比:

|特性		|标准库	|MicroLIB	|
|---		|---	|---		|
|代码体积	|大		|小			|
|功能完整性	|完整	|精简		|
|执行速度	|较慢	|较快		|
|内存使用	|较多	|较少		|
|线程支持	|支持	|不支持		|

适用场景:

// 适合MicroLIB的场景	|	// 不适合MicroLIB的场景
- 单任务应用				|	- 需要完整C库功能
- 资源受限系统			|	- 多线程应用
- 实时性要求高			|	- 复杂的格式化输出
- 代码空间受限			|	- 需要文件系统// MicroLIB不支持或简化的功能
- printf()格式化受限
- scanf()功能受限
- 不支持浮点格式化
- 不支持多线程
- 文件操作受限
- locale支持受限

第 5 行: __user_initial_stackheap 将由__main 函数进行调用。

__user_initial_stackheap是ARM编译器提供的一个函数,用于初始化堆栈空间。

它在不使用MicroLIB时(:DEF:__MICROLIB未定义)被调用。

函数作用:

      • 初始化堆和栈的边界地址
      • 返回四个关键地址(通过R0-R3寄存器):
      • R0: 堆的起始地址
      • R1: 栈顶地址
      • R2: 堆的结束地址
      • R3: 栈的起始地址
高地址
┌──────────────────┐ 
│                  │ ← R1 (Stack_Mem + Stack_Size)
│     Stack        │    栈顶地址
│       ↓          │ 
├──────────────────┤ ← R3 (Stack_Mem)
│                  │    栈底地址
│                  │
│    Free RAM      │
│                  │
│                  │
├──────────────────┤ ← R2 (Heap_Mem + Heap_Size)
│       ↑          │    堆顶地址
│     Heap         │
├──────────────────┤ ← R0 (Heap_Mem)
│                  │    堆底地址
低地址
  • IF,ELSE,ENDIF:汇编的条件分支语句,跟C 语言的if ,else 类似。
  • END:文件结束


 

分析代码后,从中理解——单片机内存地址空间分配

__Vectors,__Vectors_End,__Vectors_Size__initial_sp__heap_base,__heap_limit
  • __Vectors,__Vectors_End,__Vectors_Size
定义:AREA    RESET, DATA, READONLYEXPORT  __VectorsEXPORT  __Vectors_EndEXPORT  __Vectors_Size
使用:
__Vectors       DCD     __initial_sp                      ; Top of StackDCD     Reset_Handler                     ; Reset Handler中间部分省略未写                                DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins __Vectors_End__Vectors_Size  EQU  __Vectors_End - __Vectors
  • __initial_sp
定义:
Stack_Size      EQU     0x00001000AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp内存布局:
┌──────────────────┐ __initial_sp (高地址)
│                  │
│    Stack Area    │ Stack_Size (4KB)
│        ↓         │ (向下增长)
└──────────────────┘ Stack_Mem (低地址)
  • __heap_base,__heap_limit
定义:
Heap_Size       EQU     0x00000800												;定义常量AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit内存布局:
┌──────────────────┐ __heap_limit (高地址)
│        ↑         │
│    Heap Area     │ Heap_Size (2KB)
│                  │ (向上增长)
└──────────────────┘ __heap_base (低地址)
  • 完整的内存布局
┌──────────────────┐ 0x08000000
│   Vector Table   │ __Vectors
│    (...vectors)  │
└──────────────────┘ __Vectors_End... 其他区域 ...┌──────────────────┐ __initial_sp
│    Stack Area    │ 
│       (4KB)      │
└──────────────────┘ Stack_Mem... 其他区域 ...┌──────────────────┐ __heap_limit
│    Heap Area     │
│       (2KB)      │
└──────────────────┘ __heap_base

分析代码后,从中理解——中断向量表

分析代码后,从中理解——堆栈机制

何时分配 __initial_sp、__heap_base、__heap_limit 的具体地址
在启动文件中定义的这些符号(__initial_sp、__heap_base、__heap_limit)
在汇编代码中只是占位符,它们的具体地址是在链接阶段由链接器根据链接脚本分配的。堆和栈的内存区域通常是在编译和链接阶段通过链接脚本固定分配的。
运行时动态调整堆和栈的大小或位置是非常困难的。
堆栈地址分布示意图
┌──────────────────┐ 0x20020000										- Stack Top/Start    : 0x20020000 (__initial_sp)
│                  │
│    Stack Area    │ Stack Size = 2KB (0x800)
│        ↓         │ (向下增长(从高地址向低地址))
├──────────────────┤ 0x2001F800										- Heap End     : 0x2001F800 (__heap_limit)
│        ↑         │
│    Heap Area     │ Heap Size = 2KB (0x800)
│                  │ (向上增长(从低地址向高地址))
├──────────────────┤ 0x2001F000										- Heap Start   : 0x2001F000 (__heap_base)
│                  │
│                  │
│   Other RAM      │ 应用程序数据(124kb)
│                  │
│                  │
├──────────────────┤ 0x20000000
│   Vector Table   │
└──────────────────┘Memory Details:
- Stack Top    : 0x20020000 (__initial_sp)
- Stack Bottom : 0x2001F800
- Heap Start   : 0x2001F000 (__heap_base)
- Heap End     : 0x2001F800 (__heap_limit)Notes:
→ Stack: 从高地址向低地址增长
→ Heap : 从低地址向高地址增长
→ 中间预留安全区,防止栈堆冲突

STM32 片内 RAM:

启动流程中的设置中断向量表(在 SystemInit 函数中):

// 在SystemInit中设置向量表位置
void SystemInit(void)
{/* Configure the Vector Table location */SCB->VTOR = FLASH_BASE;    // 设置向量表位置为Flash起始地址(0x08000000)// 或者重定位到RAM// SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;  // 例如:0x20000000
}
/* 链接脚本 (.ld文件) */
MEMORY
{FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 128K    /* Flash区域 */RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 64K     /* RAM区域 */
}SECTIONS
{.isr_vector :   /* 向量表段 */{. = ALIGN(4);KEEP(*(.isr_vector)). = ALIGN(4);} >FLASH
}
                AREA    RESET, DATA, READONLYEXPORT  __VectorsEXPORT  __Vectors_EndEXPORT  __Vectors_Size__Vectors       DCD     __initial_sp               ; 栈顶指针DCD     Reset_Handler              ; 复位DCD     NMI_Handler                ; NMIDCD     HardFault_Handler          ; Hard Fault...__Vectors_End__Vectors_Size  EQU  __Vectors_End - __Vectors    ; 计算向量表大小
// 1. 定义新的向量表数组
__attribute__((section(".ram_vector"))) 
uint32_t g_pfnVectors[] = {(uint32_t)&_estack,            // 栈顶指针(uint32_t)&Reset_Handler,      // 复位(uint32_t)&NMI_Handler,        // NMI(uint32_t)&HardFault_Handler,  // Hard Fault...
};// 2. 重定位函数
void NVIC_SetVectorTable(void)
{// 禁止所有中断__disable_irq();// 复制向量表到RAMuint32_t *src = (uint32_t *)&__Vectors;uint32_t *dst = (uint32_t *)SRAM_BASE;for(uint32_t i = 0; i < __Vectors_Size/4; i++){dst[i] = src[i];}// 设置VTOR指向新的向量表SCB->VTOR = SRAM_BASE;// 使能所有中断__enable_irq();
}
// 方法1:直接在启动文件中定义
void USART1_IRQHandler(void)
{// 中断处理代码
}// 方法2:动态修改向量表
void SetInterruptHandler(IRQn_Type IRQn, void (*handler)(void))
{uint32_t *vectors = (uint32_t *)SCB->VTOR;vectors[IRQn + 16] = (uint32_t)handler;
}

3. 程序启动方式&烧录方式

3.1. 启动方式

3.2. 烧录方式

3.2.1. ISP(串口烧录)——基于系统 BootLoader

3.2.2. ICP(SWD/JTAG接口烧录)——烧录算法

3.2.3. IAP——基于开发者的 BootLodader(分为有线/无线 OTA)

IAP 的原理与上面两种有较大区别,这种方式:(根据实际需要由开发者自行分配)

  • 单区 IAP:将主存储区又分成了两个区域
    • 0x0800 0000 起始处的这部分存储一个开发者自己设计的 Bootloader 程序
    • 另一部分存储真正需要运行的 APP 程序
  • 双区 IAP:将主存储区又分成了三个区域
    • 0x0800 0000 起始处的这部分,存储一个开发者自己设计的 Bootloader 程序
    • 一部分存储真正需要运行的 APP 程序
    • 一部分存储接收到的需要更新的 APP 程序

单片机的 Bootloader 程序,其主要作用就是给单片机升级。在单片机启动时,首先从 Bootloader 程序启动,一般情况不需要升级,就会立即从 Bootloader 程序跳转到存储区另一部分的 APP 程序开始运行。

假如 Bootloader 程序时,需要进行升级(比如APP程序运行时,接收到升级指令,可以在 flash 中的特定位置设置一个标志,然后触发重启,重启后进入 Bootloader 程序,Bootloader 程序根据标志位就能判断是否需要升级),则会通过某种方式(比如通过 WIFI 接收升级包,或借助另一块单片机接收升级包,Bootloader 再通过串口或 SPI 等方式从另一块单片机获取升级包数据)先将接收到的程序写入存储区中存储 APP 程序的那个位置,写入完成后再跳转到该位置,即实现了程序的升级。

 https://www.yuque.com/u41716106/ni1clp/ywm4rdb6tve7mg8x?singleDoc# 《【STM32(H7)】启动流程、方式、烧录方式详解》

相关文章:

【单片机开发 - STM32(H7)】启动流程、方式、烧录方式详解

如侵权&#xff0c;联系删&#xff0c;个人总结学习用 参考资料&#xff1a;&#xff08;最末尾有我的原生笔记&#xff0c;那个格式规范点&#xff09; 安富莱 ARM汇编伪指令详解-CSDN博客 【STM32】STM32内存映射以及启动过程&#xff08;超详细过程&#xff09;-CSDN博客…...

[手机Linux] ubuntu 错误解决

Ubuntu: 1,ttyname failed: Inappropriate ioctl for device 将 /root/.profile 文件中的 mesg n || true 改为如下内容。 vim /root/.profile tty -s && mesg n || true 2,Errors were encountered while processing: XXX XXXX sudo apt-get --purge remove xxx…...

springCloudGateway+nacos自定义负载均衡-通过IP隔离开发环境

先说一下想法&#xff0c;小公司开发项目&#xff0c;参考若依框架使用的spring-cloud-starter-gateway和spring-cloud-starter-alibaba-nacos, 用到了nacos的配置中心和注册中心&#xff0c;有多个模块&#xff08;每个模块都是一个服务&#xff09;。 想本地开发&#xff0c;…...

MyBatis-增删改查操作一些细节

目录 删除 新增 修改 查询 小结&#xff1a; 删除功能 需求&#xff1a;根据ID删除用户信息 SQL&#xff1a;delete from user where id 5; Mapper接口方法&#xff08;注意这里不是实现类&#xff09;&#xff1a; /*** 根据id删除*/ Delete("delete from user wher…...

windows 极速安装 Linux (Ubuntu)-- 无需虚拟机

1. 安装 WSL 和 Ubuntu 打开命令行&#xff0c;执行 WSL --install -d ubuntu若报错&#xff0c;则先执行 WSL --update2. 重启电脑 因安装了子系统&#xff0c;需重启电脑才生效 3. 配置 Ubuntu 的账号密码 打开 Ubuntu 的命令行 按提示&#xff0c;输入账号&#xff0c;密…...

【学习笔记】各种强化学习环境

0. 写在前面 0.1 强化学习综述/资料&#xff08;更新中&#xff09; 鹏程实验室&#xff1a; 中文报道&#xff1a;学术分享丨具身智能综述&#xff1a;鹏城实验室&#xff06;中大调研近400篇文献&#xff0c;英文原文&#xff1a;Aligning Cyber Space with Physical World…...

统计有序矩阵中的负数

统计有序矩阵中的负数 描述 给你一个 m * n 的矩阵 grid&#xff0c;矩阵中的元素无论是按行还是按列&#xff0c;都以非递增顺序排列。 请你统计并返回 grid 中 负数 的数目 示例 1&#xff1a; 输入&#xff1a;grid [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]…...

【已解决】git clone报错:Failed to connect to github.com port 443: Timed out

1.问题原因1 报错信息1&#xff1a; fatal: unable to access https://github.com/microsoft/xxx/: Failed to connect to github.com port 443: Timed out 报错信息2&#xff1a; fatal: unable to access https://github.com/xxx/xx/: OpenSSL SSL_read: Connection was …...

Android SystemUI——使用Dagger2加载组件(四)

SystemUI 是 Android 系统中的一个重要模块,负责绘制系统栏(如状态栏、导航栏)、锁屏、快捷设置等用户界面元素。由于其复杂性,良好的架构设计和依赖管理对于保持代码的可维护性和扩展性至关重要。这就是 Dagger2 在此发挥重要作用的地方。 一、Dagger2介绍 Dagger2 是一个…...

Lesson 109 A good idea

Lesson 109 A good idea 词汇 idea n. 主意&#xff0c;想法 复数&#xff1a;ideas 用法&#xff1a;口语&#xff1a;Good idea! 好主意&#xff01;       Big idea! 高见&#xff01;好主意&#xff01;       Great idea! 好主意       Bad idea! 坏主…...

网络安全-RSA非对称加密算法、数字签名

数字签名非常普遍&#xff1a; 了解数字签名前先了解一下SHA-1摘要&#xff0c;RSA非对称加密算法。然后再了解数字签名。 SHA-1 SHA-1&#xff08;secure hash Algorithm &#xff09;是一种 数据加密算法。该算法的思想是接收一段明文&#xff0c;然后以一种不可逆的方式将…...

自动化办公|xlwings简介

xlwings 是一个开源的 Python 库&#xff0c;旨在实现 Python 与 Microsoft Excel 的无缝集成。它允许用户使用 Python 脚本自动化 Excel 操作&#xff0c;读取和写入数据&#xff0c;执行宏&#xff0c;甚至调用 VBA 脚本。这使得数据分析、报告生成和其他与 Excel 相关的任务…...

C#使用OpenTK绘制3D可拖动旋转图形三棱锥

接上篇,绘制着色矩形 C#使用OpenTK绘制一个着色矩形-CSDN博客 上一篇安装OpenTK.GLControl后,这里可以直接拖动控件GLControl 我们会发现GLControl继承于UserControl //// 摘要:// OpenGL-aware WinForms control. The WinForms designer will always call the default//…...

【网络云SRE运维开发】2025第3周-每日【2025/01/14】小测-【第13章ospf路由协议】理论和实操

文章目录 选择题&#xff08;10道&#xff09;理论题&#xff08;5道&#xff09;实操题&#xff08;5道&#xff09; 【网络云SRE运维开发】2025第3周-每日【2025/01/14】小测-【第12章ospf路由协议】理论和实操 选择题&#xff08;10道&#xff09; 在OSPF协议中&#xff0c…...

计算机网络 (34)可靠传输的工作原理

前言 计算机网络可靠传输的工作原理主要依赖于一系列协议和机制&#xff0c;以确保数据在传输过程中能够准确无误地到达目的地。 一、基本概念 可靠传输指的是数据链路层的发送端发送什么&#xff0c;在接收端就收到什么&#xff0c;即保证数据的完整性、正确性和顺序性。由于网…...

提高互联网Web安全性:避免越权漏洞的技术方案

目录 一、越权漏洞概述 二、常见的越权漏洞类型 三、越权漏洞的影响 四、越权漏洞的技术解决方案 一、越权漏洞概述 越权&#xff08;Authorization Bypass&#xff09;类漏洞是指在系统中&#xff0c;攻击者通过绕过身份验证或访问控制&#xff0c;获取本不应访问的资源或…...

c语言 --- 字符串

创建字符串 1. 使用字符数组创建字符串 #include <stdio.h>int main() {char str[20] "Hello, world!";str[0] h; // 修改字符串的第一个字符printf("%s\n", str); // 输出&#xff1a;hello, world!return 0; }解释&#xff1a; 数组大小 20 表…...

Linux探秘坊-------1.系统核心的低语:基础指令的奥秘解析(3)

1.zip/unzip指令 语法&#xff1a; zip 压缩⽂件.zip ⽬录或⽂件 功能&#xff1a;将⽬录或⽂件压缩成zip格式 常⽤选项: -r&#xff1a;递归处理&#xff0c;将指定⽬录下的 所有⽂件和⼦⽬录⼀并处理 example: 1.事前准备 建立以下文件与目录&#xff1a; 2.压缩test 目…...

Java中网络编程的学习

目录 网络编程概述 网络模型 网络通信三要素: IP 端口号 通信协议 IP地址&#xff08;Internet Protocol Address&#xff09; 端口号 网络通信协议 TCP 三次握手 四次挥手 UDP TCP编程 客户端Socket的工作过程包含以下四个基本的步骤&#xff1a; 服务器程序…...

微服务的CAP定理与数据一致性抉择

分布式系统中的CAP定理&#xff0c;包括一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;和分区容错性&#xff08;Partition Tolerance&#xff09;三个核心要素。 微服务是分布式系统的一种表现形式&#xff0c;以及用户对于系统是分…...

正则表达式 - 简介

正则表达式 - 简介 正则表达式&#xff08;Regular Expression&#xff0c;简称Regex&#xff09;是一种用于处理字符串的强大工具&#xff0c;它允许我们按照特定的模式&#xff08;pattern&#xff09;来搜索、匹配、查找和替换文本。正则表达式广泛应用于各种编程语言和工具…...

MySQL:表的内外连接

目录 1.内连接 2.左外连接和右外连接 178. 分数排名 - 力扣&#xff08;LeetCode&#xff09; 1.内连接 内连接就是两张表做笛卡尔积&#xff0c;再加上一个筛选条件。 这两个sql语句是一样的。 2.左外连接和右外连接 左外连接就是左表必须是完全显示&#xff0c;即使筛选…...

65.在 Vue 3 中使用 OpenLayers 绘制带有箭头的线条

前言 在现代的前端开发中&#xff0c;地图已经成为许多项目的核心功能之一。OpenLayers 是一个强大的开源地图库&#xff0c;它提供了丰富的功能和高度的定制化支持。在本篇文章中&#xff0c;我将向大家展示如何在 Vue 3 中使用 OpenLayers 绘制带有箭头的线条。 我们将实现…...

关于编写测试用例的细枝末节

这里写目录标题 故障判别类-边界考虑示例1.0&#xff1a;若A&#xff1e;20.3且持续时间≥15ms时&#xff08;判故周期为1000Hz&#xff09;&#xff0c;输出B为1&#xff0c;否则输出B为0。 故障判别类-不可恢复测试示例1.1&#xff1a;若A&#xff1e;20.3且持续时间≥15ms时…...

【Vim Masterclass 笔记13】第 7 章:Vim 核心操作之——文本对象与宏操作 + S07L28:Vim 文本对象

文章目录 Section 7&#xff1a;Text Objects and MacrosS07L28 Text Objects1 文本对象的含义2 操作文本对象的基本语法3 操作光标所在的整个单词4 删除光标所在的整个句子5 操作光标所在的整个段落6 删除光标所在的中括号内的文本7 删除光标所在的小括号内的文本8 操作尖括号…...

(一)QSQLite3库简介

1、SQLite数据库 SQLite数据库&#xff0c;作为一个轻量级的关系型数据库管理系统&#xff0c;广泛应用于移动设备和桌面应用程序中。由于其简单易用、无需配置的特点&#xff0c;它为开发者提供了极大的便利。然而&#xff0c;正是由于其应用广泛&#xff0c;随着用户对于系统…...

新版 MacOS 无法从 /usr/local/lib 加载动态链接库的解决办法

自己编写的动态链接库在Unix规范下一般位于/usr/local/lib&#xff0c;在2023年及之前的MacOS版本中&#xff0c;直接将动态库安装到该位置即可在程序运行时加载&#xff0c;可是升级MacOS版本后&#xff0c;ld就报错。 错误现象 运行程序&#xff0c;报错 dyld[6376]: Libra…...

PanWeidb-使用BenchmarkSQL对磐维数据库进行压测

本文提供PanweiDb使用BenchmarkSQL进行性能测试的方法和测试数据报告。 BenchmarkSQL,一个JDBC基准测试工具,内嵌了TPC-C测试脚本,支持很多数据库,如PostgreSQL、Oracle和Mysql等。 TPC-C是专门针对联机交易处理系统(OLTP系统)的规范,一般情况下我们也把这类系统称为业…...

git在本地创建新分支并将该分支推送到远程仓库

1. 创建本地分支 首先&#xff0c;创建并切换到一个新的本地分支&#xff1a; git checkout -b new-branch-name2. 推送本地分支到远程仓库 将新的本地分支推送到远程仓库&#xff0c;并在远程创建一个对应的新分支&#xff1a; git push origin new-branch-name3. 设置本地…...

Axure9笔记

快速入门 原型图种类 1.线框图 2.高保真图 3.简易需求文档&#xff08;PRD&#xff09; tips 按住shift可以等比缩放 旋转&#xff1a;ctrl按角角 矢量图素材&#xff1a; iconfont-阿里巴巴矢量图标库 复制svg图-->将svg图换为形状 截屏&#xff1a; Windows&…...

33_操作Redis分片集群

1.Redis分片集群读写 我们使用的redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看。 [root@localhost ~]# redis-cli --cluster help 1.连接上7001节点后,尝试存储一组num=100和a=10的数据,语句如下所示。 [root@node1 cluster]# redis-cli -a 123456…...

llama.cpp 模型可视化工具 GGUF Visualizer

llama.cpp 模型可视化工具 GGUF Visualizer 1. GGUF Visualizer for VS Code (gguf-viz)1.1. Features1.2. Extension Settings References GGUF Visualizer https://marketplace.visualstudio.com/items?itemNameAgainstEntropy.gguf-viz 1. GGUF Visualizer for VS Code (g…...

MAC AndroidStudio模拟器无网络

先确认PC端是正常访问网络的&#xff1b; 模拟器端修改Wifi设置&#xff1a;设置 - 网络和互联网 - WALN设置 按照上图修改&#xff1b; IP设置&#xff1a;从DHCP修改为静态&#xff0c;IP地址&#xff1a;10.0.2.16 &#xff0c;网关&#xff1a;10.0.2.2 &#xff0c; DNS…...

如何添加合适的索引:MySql 数据库索引认知

写在前面 博文内容涉及 Mysql 数据库索引简单认知&#xff0c;包括SQL执行过程&#xff0c;数据库数据存储原理。如何通过索引加快数据查询原理简单介绍适合有一定SQL基础的开发运维小伙伴建立数据库索引认知&#xff0c;学会如何添加索引理解不足小伙伴帮忙指正 &#x1f603;…...

深度学习中的学习率调度器(scheduler)分析并作图查看各方法差异

文章目录 1. 指数衰减调度器&#xff08;Exponential Decay Scheduler&#xff09;工作原理适用场景实现示例 2. 余弦退火调度器&#xff08;Cosine Annealing Scheduler&#xff09;工作原理适用场景实现示例 3. 步长衰减调度器&#xff08;Step Decay Scheduler&#xff09;工…...

测试人员面试需要掌握的内容

测试人员面试需要掌握的内容 1、在公司的测试流程是什么&#xff1f; 产品经理确认本次版本的需求&#xff0c;召开需求评审会&#xff0c;进行估时排期&#xff0c;需求和时间都确定之后&#xff0c;UI出设计图&#xff0c;开发人员进行开发&#xff0c;测试人员编写测试用例…...

【C++】函数(下)

1、函数的常见样式 常见的函数样式有四种&#xff1a; &#xff08;1&#xff09;无参数无返回值 &#xff08;2&#xff09;有参数无返回值 &#xff08;3&#xff09;无参数有返回值 &#xff08;4&#xff09;有参数有返回值 &#xff08;1&#xff09;无参数无返回值 示例…...

dockerfile实现lnmp

dockerfile实现lnmp 自定义镜像实现整个架构 (基础镜像centos7) nginx cd /opt mkdir nginx mysql php vim Dockerfile docker network create --subnet172.111.0.0/16 mynetwork #创建自定义网段 docker run -itd --name nginx -p 80:80 --cpu-quota 20000 -m 512m -v /op…...

C语言进阶-2指针(一)

目录 1. 字符指针1.1 一般用法&#xff1a;字符指针指向单字符1.2 第二种用法&#xff0c;字符串首地址给指针变量1.3 习题,下面代码的输出结果是什么&#xff1f;为什么&#xff1f; 2. 指针数组2.1实例—— 字符指针数组2.2实例——整形指针数组2.3 例子&#xff0c;识别下下…...

JAVA:Spring Boot 集成 JWT 实现身份验证的技术指南

1、简述 在现代Web开发中&#xff0c;安全性尤为重要。为了确保用户的身份&#xff0c;JSON Web Token&#xff08;JWT&#xff09;作为一种轻量级且无状态的身份验证方案&#xff0c;广泛应用于微服务和分布式系统中。本篇博客将讲解如何在Spring Boot 中集成JWT实现身份验证…...

SpringBoot链接Kafka

一、SpringBoot生产者 &#xff08;1&#xff09;修改SpringBoot核心配置文件application.propeties, 添加生产者相关信息 # 连接 Kafka 集群 spring.kafka.bootstrap-servers192.168.134.47:9093# SASL_PLAINTEXT 和 SCRAM-SHA-512 认证配置 spring.kafka.properties.securi…...

《深度剖析算法优化:提升效率与精度的秘诀》

想象一下&#xff0c;你面前有一堆杂乱无章的数据&#xff0c;你需要从中找到特定的信息&#xff0c;或者按照一定的规则对这些数据进行排序。又或者&#xff0c;你要为一个物流公司规划最佳的配送路线&#xff0c;以降低成本和提高效率。这些问题看似复杂&#xff0c;但都可以…...

APP推荐:全新TV端来了,8K原画电视版

▌ 软件介绍 B站都不陌生吧&#xff0c;一个能追番、学习、娱乐的多元平台&#xff0c;之前也分享过几款第三方TV端&#xff0c;其中的BV最近更新了全新版本。 使用了全新的UI界面&#xff0c;由之前的顶部菜单栏改成了侧边布局&#xff0c;已解锁限制&…...

Spark vs Flink分布式数据处理框架的全面对比与应用场景解析

1. 引言 1.1 什么是分布式数据处理框架 随着数据量的快速增长&#xff0c;传统的单机处理方式已经无法满足现代数据处理需求。分布式数据处理框架应运而生&#xff0c;它通过将数据分片分布到多台服务器上并行处理&#xff0c;提高了任务的处理速度和效率。 分布式数据处理框…...

【Linux】正则表达式

正则表达式是一种可供Linux工具过滤文本的自定义模板&#xff0c;Linux工具&#xff08;如sed、gawk&#xff09;会在读取数据时使用正则表达式对数据进行模式匹配。 正则表达式使用元字符来描述数据流中的一个或多个字符。它是由正则表达式引擎实现的。正则表达式引擎是一种底…...

《银行保险机构数据安全管理办法》正式实施,分类分级、安全评估共筑安全防线

金融数据具有高价值和高敏感性&#xff0c;金融数据安全关乎国家安全和金融消费者权益密切相关。在当前数字化进程加速的背景下&#xff0c;数据合作频繁&#xff0c;安全风险也随之增加&#xff0c;给机构管理带来了新挑战。 为规范银行业保险业数据处理活动&#xff0c;保障数…...

excel仅复制可见单元格,仅复制筛选后内容

背景 我们经常需要将内容分给不同的人&#xff0c;做完后需要合并 遇到情况如下 那是因为直接选择了整列&#xff0c;当然不可以了。 下面提供几种方法&#xff0c;应该都可以 直接选中要复制区域然后复制&#xff0c;不要选中最上面的列alt;选中可见单元格正常复制&#xff…...

26_Redis RDB持久化

从这个模块开始带领大家来学习Redis分布式缓存的相关内容,主要学习目标见下: 数据丢失问题:实现Redis数据持久化(RDB和AOF)并发能力问题:搭建Redis主从集群,实现读写分离故障恢复问题:利用Redis哨兵模式,实现健康检测和自动恢复存储能力问题:搭建Redis分片集群,利用…...

Windows图形界面(GUI)-QT-C/C++ - QT控件创建管理初始化

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 控件创建 包含对应控件类型头文件 实例化控件类对象 控件设置 设置父控件 设置窗口标题 设置控件大小 设置控件坐标 设置文本颜色和背景颜色 控件排版 垂直布局 QVBoxLayout …...

Java+Maven+GDAL

下载已经编译好的压缩包&#xff0c;下载地址 解压 jar 包 release-1930-x64-dev.zip\release-1930-x64\bin\gdal\java 目录下 打成Maven依赖 mvn install:install-file -Dfilegdal-3.10.1.jar -DgroupIdorg.gdal -DartifactIdgdal -Dversion3.10.1 -Dpackagingjar -Dgener…...