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

STM32 裸机编程 03

MCU 启动和向量表

当 STM32F429 MCU 启动时,它会从 flash 存储区最前面的位置读取一个叫作“向量表”的东西。“向量表”的概念所有 ARM MCU 都通用,它是一个包含 32 位中断处理程序地址的数组。对于所有 ARM MCU,向量表前 16 个地址由 ARM 保留,其余的作为外设中断处理程序入口,由 MCU 厂商定义。越简单的 MCU 中断处理程序入口越少,越复杂的 MCU 中断处理程序入口则会更多。

STM32F429 的向量表在数据手册表 62 中描述,我们可以看到它在 16 个 ARM 保留的标准中断处理程序入口外还有 91 个外设中断处理程序入口。

在向量表中,我们当前对前两个入口点比较感兴趣,它们在 MCU 启动过程中扮演了关键角色。这两个值是:初始堆栈指针和执行启动函数的地址(固件程序入口点)。

所以现在我们知道,我们必须确保固件中第 2 个 32 位值包含启动函数的地址,当 MCU 启动时,它会从 flash 读取这个地址,然后跳转到我们的启动函数。

最小固件

现在我们创建一个 main.c 文件,指定一个初始进入无限循环什么都不做的启动函数,并把包含 16 个标准入口和 91 个 STM32 入口的向量表放进去。用你常用的编辑器创建 main.c 文件,并写入下面的内容:

// Startup code
__attribute__((naked, noreturn)) void _reset(void) {for (;;) (void) 0;  // Infinite loop
}extern void _estack(void);  // Defined in link.ld// 16 standard and 91 STM32-specific handlers
__attribute__((section(".vectors"))) void (*tab[16 + 91])(void) = {_estack, _reset
};

对于 _reset() 函数,我们使用了 GCC 编译器特定的 naked 和 noreturn 属性,这意味着标准函数的进入和退出不会被编译器创建,这个函数永远不会返回。

void (*tab[16 + 91])(void) 这个表达式的意思是:定义一个 16+91 个指向没有返回也没有参数的函数的指针数组,每个这样的函数都是一个中断处理程序,这个指针数组就是向量表。

我们把 tab 向量表放到一个独立的叫作 .vectors 的区段,后面需要告诉链接器把这个区段放到固件最开始的地址,也就是 flash 存储区最开始的地方。前 2 个入口分别是:堆栈指针和固件入口,目前先把向量表其它值用 0 填充。

编译

我们来编译下代码,打开终端并执行:

$ arm-none-eabi-gcc -mcpu=cortex-m4 main.c -c

成功了!编译器生成了 main.o 文件,包含了最小固件,虽然这个固件程序什么都没做。这个 main.o 文件是 ELF 二进制格式的,包含了多个区段,我们来具体看一下:

$ arm-none-eabi-objdump -h main.o
...
Sections:
Idx Name          Size      VMA       LMA       File off  Algn0 .text         00000002  00000000  00000000  00000034  2**1CONTENTS, ALLOC, LOAD, READONLY, CODE1 .data         00000000  00000000  00000000  00000036  2**0CONTENTS, ALLOC, LOAD, DATA2 .bss          00000000  00000000  00000000  00000036  2**0ALLOC3 .vectors      000001ac  00000000  00000000  00000038  2**2CONTENTS, ALLOC, LOAD, RELOC, DATA4 .comment      0000004a  00000000  00000000  000001e4  2**0CONTENTS, READONLY5 .ARM.attributes 0000002e  00000000  00000000  0000022e  2**0CONTENTS, READONLY

注意现在所有区段的 VMA/LMA 地址都是 0,这表示 main.o 还不是一个完整的固件,因为它没有包含各个区段从哪个地址空间载入的信息。我们需要链接器从 main.o 生成一个完整的固件 firmware.elf

.text 区段包含固件代码,在上面的例子中,只有一个 _reset() 函数,2 个字节长,是跳转到自身地址的 jump 指令。.data 和 .bss(初始化为 0 的数据) 区段都是空的。我们的固件将被拷贝到偏移 0x8000000 的 flash 区,但是数据区段应该被放到 RAM 里,因此 _reset() 函数应该把 .data 区段拷贝到 RAM,并把整个 .bss 区段写入 0。现在 .data 和 .bss 区段是空的,我们修改下 _reset() 函数让它处理好这些。

为了做到这一点,我们必须知道堆栈从哪开始,也需要知道 .data 和 .bss 区段从哪开始。这些可以通过“链接脚本”指定,链接脚本是一个带有链接器指令的文件,这个文件里存有各个区段的地址空间以及对应的符号。

链接脚本

创建一个链接脚本文件 link.ld,然后把一下内容拷进去:

ENTRY(_reset);
MEMORY {flash(rx)  : ORIGIN = 0x08000000, LENGTH = 2048ksram(rwx) : ORIGIN = 0x20000000, LENGTH = 192k  /* remaining 64k in a separate address space */
}
_estack     = ORIGIN(sram) + LENGTH(sram);    /* stack points to end of SRAM */SECTIONS {.vectors  : { KEEP(*(.vectors)) }   > flash.text     : { *(.text*) }           > flash.rodata   : { *(.rodata*) }         > flash.data : {_sdata = .;   /* .data section start */*(.first_data)*(.data SORT(.data.*))_edata = .;  /* .data section end */} > sram AT > flash_sidata = LOADADDR(.data);.bss : {_sbss = .;              /* .bss section start */*(.bss SORT(.bss.*) COMMON)_ebss = .;              /* .bss section end */} > sram. = ALIGN(8);_end = .;     /* for cmsis_gcc.h  */
}

下面分段解释下:

ENTRY(_reset);

这行是告诉链接器在生成的 ELF 文件头中 "entry point" 属性的值。没错,这跟向量表重复了,这个的目的是为像 Ozone 这样的调试器设置固件起始的断点。调试器是不知道向量表的,所以只能依赖 ELF 文件头。

MEMORY {flash(rx)  : ORIGIN = 0x08000000, LENGTH = 2048ksram(rwx) : ORIGIN = 0x20000000, LENGTH = 192k  /* remaining 64k in a separate address space */
}

这是告诉链接器有 2 个存储区空间,以及它们的起始地址和大小。

_estack     = ORIGIN(sram) + LENGTH(sram);    /* stack points to end of SRAM */

这行告诉链接器创建一个 _estack 符号,它的值是 RAM 区的最后,这也是初始化堆栈指针的值。

  .vectors  : { KEEP(*(.vectors)) }   > flash.text     : { *(.text*) }           > flash.rodata   : { *(.rodata*) }         > flash

这是告诉链接器把向量表放在 flash 区最前,然后是 .text 区段(固件代码),再然后是只读数据 .rodata

  .data : {_sdata = .;   /* .data section start */*(.first_data)*(.data SORT(.data.*))_edata = .;  /* .data section end */} > sram AT > flash_sidata = LOADADDR(.data);

这是 .data 区段,告诉链接器创建 _sdata 和 _edata 两个符号,我们将在 _reset() 函数中使用它们将数据拷贝到 RAM。

  .bss : {_sbss = .;              /* .bss section start */*(.bss SORT(.bss.*) COMMON)_ebss = .;              /* .bss section end */} > sram

.bss 区段也是一样。

启动代码

现在我们来更新下 _reset 函数,把 .data 区段拷贝到 RAM,然后把 .bss 区段初始化为 0,再然后调用 main() 函数,在 main() 函数有返回的情况下进入无限循环:

int main(void) {return 0; // Do nothing so far
}// Startup code
__attribute__((naked, noreturn)) void _reset(void) {// memset .bss to zero, and copy .data section to RAM regionextern long _sbss, _ebss, _sdata, _edata, _sidata;for (long *src = &_sbss; src < &_ebss; src++) *src = 0;for (long *src = &_sdata, *dst = &_sidata; src < &_edata;) *src++ = *dst++;main();             // Call main()for (;;) (void) 0;  // Infinite loop in the case if main() returns
}

下面的框图演示了 _reset() 如何初始化 .data 和 .bss

初始化

firmware.bin 文件由 3 部分组成:.vectors(中断向量表)、.text(代码)、.data(数据)。这些部分根据链接脚本被分配到不同的存储空间:.vectors 在 flash 的最前面,.text 紧随其后,.data 则在那之后很远的地方。.text 中的地址在 flash 区,.data 在 RAM 区。例如,一个函数的地址是 0x8000100,则它位于 flash 中。而如果代码要访问 .data 中的变量,比如位于 0x20000200,那里将什么也没有,因为在启动时 firmware.bin 中 .data 还在 flash 里!这就是为什么必须要在启动代码中将 .data 区段拷贝到 RAM。

现在我们可以生成完整的 firmware.elf 固件了:

$ arm-none-eabi-gcc -T link.ld -nostdlib main.o -o firmware.elf

再次检验 firmware.elf 中的区段:

$ arm-none-eabi-objdump -h firmware.elf
...
Sections:
Idx Name          Size      VMA       LMA       File off  Algn0 .vectors      000001ac  08000000  08000000  00010000  2**2CONTENTS, ALLOC, LOAD, DATA1 .text         00000058  080001ac  080001ac  000101ac  2**2CONTENTS, ALLOC, LOAD, READONLY, CODE
...

可以看到,.vectors 区段在 flash 的起始地址 0x8000000,.text 紧随其后。我们在代码中没有创建任何变量,所以没有 .data 区段。

烧写固件

现在可以把这个固件烧写到板子上了!

先把 firmware.elf 中各个区段抽取到一个连续二进制文件中:

$ arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

然后使用 st-link 工具将 firmware.bin 烧入板子,连接好板子,然后执行:

$ st-flash --reset write firmware.bin 0x8000000

这样就把固件烧写到板子上了。

相关文章:

STM32 裸机编程 03

MCU 启动和向量表 当 STM32F429 MCU 启动时&#xff0c;它会从 flash 存储区最前面的位置读取一个叫作“向量表”的东西。“向量表”的概念所有 ARM MCU 都通用&#xff0c;它是一个包含 32 位中断处理程序地址的数组。对于所有 ARM MCU&#xff0c;向量表前 16 个地址由 ARM …...

Python ‘list‘ object is not callable错误

我尝试着解决“TypeError: ‘list’ object is not callable”这个错误。在Python编程中&#xff0c;我有时会遇到这个错误。这个错误通常是由于我错误地尝试像函数一样调用一个列表对象。为了解决这个问题&#xff0c;我需要找出错误发生的具体位置&#xff0c;然后进行修正。…...

原生php 实现redis登录五次被禁,隔天再登陆

<?php /*** Created by PhpStorm.* User: finejade* Date: 2023-10-18* Time: 11:08*/ session_start();include_once(header.php); include_once(connect.php); include_once(common.php); include_once(redis.php); try {// 常量 用户错误次数记录define("USER_LOGI…...

24. Kernel 4.19环境下,Cilium网络仍然需要使用iptables

在设计这套容器集群服务时,我从原来的k3s架构中分离出一个问题,那就是容器网络插件应该选择哪个。因为我设计的目标是给服务器领域使用的容器引擎,所以我就不需要考虑太多边缘IOT设备的情况,直接拉满技能找了cilium。cilium借助内核ebpf技术的出现,让我看到了网络性能更好…...

java中的容器(集合),HashMap底层原理,ArrayList、LinkedList、Vector区别,hashMap加载因子0.75原因

一、java中的容器 集合主要分为Collection和Map两大接口&#xff1b;Collection集合的子接口有List、Set&#xff1b;List集合的实现类有ArrayList底层是数组、LinkedList底层是双向非循环列表、Vector&#xff1b;Set集合的实现类有HashSet、TreeSet&#xff1b;Map集合的实现…...

Linux Server 终止后立即重启报错 bind error: Address already in use

先启动Server&#xff0c;再启动Client&#xff0c;然后使用CtrlC关闭Server&#xff0c;马上再运行Server&#xff0c;会得到以下结果&#xff1a; bind error: Address already in use这是因为&#xff0c;虽然Server的应用程序终止了&#xff0c;但TCP协议层的连接并没有完全…...

【Python 千题 —— 基础篇】分解数据

题目描述 题目描述 编写一个程序&#xff0c;输入一个类似 “233,234,235” 格式的字符串&#xff0c;然后提取字符串中的数字&#xff0c;将这些数字存储在列表中&#xff0c;并输出该列表。在这里&#xff0c;我们使用 eval 函数来解析字符串中的数字。 输入描述 输入一个…...

【C++】C++11新特性之右值引用与移动语义

文章目录 一、左值与左值引用二、右值与右值引用三、 左值引用与右值引用比较四、右值引用使用场景和意义1.左值引用的短板2.移动构造和移动赋值3.STL中右值引用的使用 五、万能引用与完美转发1.万能引用2.完美转发 一、左值与左值引用 在C11之前&#xff0c;我们把数据分为常…...

家庭燃气表微信抄表识别系统

1.背景需求 目前家里燃气度数的读数上报&#xff0c;每个月在社区微信群里面将手机拍摄的燃气表读数截图&#xff08;加住址信息水印&#xff09;&#xff0c;发到群里给抄表员。 2.总体设计 设计目标 功能一&#xff1a;手机上随时可以远程采集读数图片&#xff08;自动加住…...

EF执行迁移时提示provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的

ef在执行时提示provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的。 只需要在数据库链接字符串后增加EncryptTrue;TrustServerCertificateTrue;即可 再次执行...

视频标注的两个主要方法

视频标注技术 单一图像法 在自动化工具面世之前&#xff0c;视频标注效率不高。各公司使用单一图像法提取视频中的所有帧&#xff0c;然后使用标准图像标注技术将它们作为图像来标注。在30fps的视频中&#xff0c;每分钟有1800帧。这个过程没有利用视频标注的优势&#xff0c;…...

学成在线第一天-项目介绍、项目的搭建、开发流程以及相关面试题

目录 一、项目介绍 二、项目搭建 三、开发流程 四、相关面试题 五、总结 一、项目介绍 背景 业务 技术 背景&#xff1a;首先是整个这个行业的背景 然后基于这个行业的背景引出当前项目的背景 业务&#xff1a;功能模块 功能业务流程 技术&#xff1a;整体架构&am…...

《数据结构与算法之美》读书笔记1

Java的学习 方法参数多态&#xff08;向上和向下转型&#xff09; 向上转型&#xff1a; class Text{public static void main(String[] args) {Animals people1 new NiuMa();people1.eat1();//调用继承后公共部分的方法&#xff0c;没重写调用没重写的&#xff0c;重写了调…...

接口测试经验合集

一 、接口测试常见问题 前景提要&#xff1a;由于本人测试小白&#xff0c;可能所遇问题都较为基础&#xff0c;测试小白可以参考 1.1 postman会报 connect ECONNREFUSED jemeter会报 org.apache.http.conn.HttpHostConnectException: Connect tofailed: Connection refus…...

Leetcode—2331.计算布尔二叉树的值【简单】

2023每日刷题&#xff08;六&#xff09; Leetcode—2331.计算布尔二叉树的值 递归实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool evaluateTree(struct TreeNod…...

Java面试(基础篇)——解构Java常见的基础面试题 结合Java源码分析

fail-safe 和fail-fast机制 Fail-fast&#xff1a;快速失败 Fail-fast &#xff1a; 表示快速失败&#xff0c;在集合遍历过程中&#xff0c;一旦发现容器中的数据被修改了&#xff0c;会立刻抛出ConcurrentModificationException 异常&#xff0c;从而导致遍历失败 package …...

Ubuntu 17.10的超震撼声音权限

从GNOME GUADEC 2017开发者大会归来之后&#xff0c;Canonical的Didier Roche就开始了一个日更博客系列&#xff0c;主要讲述即将带来的Ubuntu 17.10&#xff08;Artful Aardvark&#xff09;发行版将如何从Unity到GNOME Shell的转变。有趣的是&#xff0c;Ubuntu Unity桌面环境…...

图像信号处理板设计原理图:2-基于6U VPX的双TMS320C6678+Xilinx FPGA K7 XC7K420T的图像信号处理板

综合图像处理硬件平台包括图像信号处理板2块&#xff0c;视频处理板1块&#xff0c;主控板1块&#xff0c;电源板1块&#xff0c;VPX背板1块。 一、板卡概述 图像信号处理板包括2片TI 多核DSP处理器-TMS320C6678&#xff0c;1片Xilinx FPGA XC7K420T-1FFG1156&#xff0c;1片X…...

【数组】移除元素(暴力遍历×双指针√)

一、力扣题目链接 27.移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 你不需要考虑数组中超出新长度后面的元素。 二、思路 要知道数组的元素在内存地址中是连续的&#xff0c;不…...

【笔试题】华为研发工程师编程题

1.汽水瓶 某商店规定&#xff1a;三个空汽水瓶可以换一瓶汽水&#xff0c;允许向老板借空汽水瓶&#xff08;但是必须要归还&#xff09;。 小张手上有n个空汽水瓶&#xff0c;她想知道自己最多可以喝到多少瓶汽水。 数据范围&#xff1a;输入的正整数满足 1≤n≤100 1≤n≤…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...

多元隐函数 偏导公式

我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式&#xff0c;给定一个隐函数关系&#xff1a; F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 &#x1f9e0; 目标&#xff1a; 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z​、 …...