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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/110824.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VSCode搭建ESP32 ESP-IDF开发环境-Windows

陈拓 2023/10/09-2023/10/14 1. 安装Windows系统下的ESP32 ESP-IDF开发环境 见《Windows系统安装ESP32 ESP-IDF开发环境》 Windows系统安装ESP32 ESP-IDF开发环境-CSDN博客Windows系统安装ESP32 ESP-IDF开发环境。https://blog.csdn.net/chentuo2000/article/details/1339225…

迅为RK3588开发板使用RKNN-Toolkit-lite2运行测试程序

1 首先也需要部署运行环境&#xff0c;将库文件放入 RK3588 开发板上&#xff0c;我们将网盘资料“iTOP-3588 开发 板 \02_ 【 iTOP-RK3588 开 发 板 】 开 发 资 料 \12_NPU 使 用 配 套 资 料 \05_Linux_librknn_api\librknn_api\aarch64”路径下的文件通过U盘拷贝到开发板的…

AI AIgents时代-(五.)Autogen

由微软开发的 Autogen 是一个新的 Agents 项目&#xff0c;刚一上线就登上GitHub热榜&#xff0c;狂揽11k星✨✨✨ 项目地址&#xff1a;https://github.com/microsoft/autogen Autogen 允许你根据需要创建任意数量的Agents&#xff0c;并让它们协同工作以执行任务。它的独特之…

stable-diffusion-webui sdxl模型代码分析

采样器这块基本都是用的k-diffusion&#xff0c;模型用的是stability的原生项目generative-models中的sgm&#xff0c;这点和fooocus不同&#xff0c;fooocus底层依赖comfyui中的models&#xff0c;comfy是用load_state_dict的方式解析的&#xff0c;用的load_checkpoint_guess…

实时精准 自我防护 | 开源网安RASP平台能力获客户认可!

近日&#xff0c;开源网安收到了一封来自华润数科的感谢信&#xff0c;表达了对开源网安团队在网络安全工作中给予大力支持的衷心感谢。开源网安十分注重客户的需求和信任&#xff0c;客户的满意和认可是开源网安最大的追求。 在助力华润数科网络安全工作开展过程中&#xff0c…

nextjs-一个基于React的全栈框架

一、nextjs基本介绍 Next.js是一个基于React的轻量级框架&#xff0c;用于构建React应用程序。它在React的基础上提供了一些增强功能&#xff0c;包括服务器渲染&#xff08;SSR&#xff09;、静态生成&#xff08;SSG&#xff09;、路由等。Next.js的目标是简化React应用程序…

十八、字符串(1)

本章概要 字符串的不可变 的重载与 StringBuilder 意外递归字符串操作 字符串操作毫无疑问是计算机程序设计中最常见的行为之一。 在 Java 大展拳脚的 Web 系统中更是如此。在本章中&#xff0c;我们将深入学习在 Java 语言中应用最广泛的 String 类&#xff0c;并研究与之相关…

pymysql连接Mariadb/Mysql出现错误(配置正确情况下)解决办法

场景&#xff1a;在kali中使用python中pymysql对Mariadb进行连接&#xff0c;在整个过程中配置全部正确&#xff0c;但是就是无法进行连接&#xff0c;提示结果如下&#xff1a; Access denied for user rootlocalhost解决办法&#xff1a;进入数据库中&#xff0c;将默认密码…

实验室烧杯可以用超声波清洗机吗

实验室烧杯可以用超声波清洗机吗&#xff1f;答案是可以的&#xff01;超声波清洗机不仅可以清洗实验烧杯&#xff0c;还可以用于清洗实验室中的试管、培养皿、移液管、载玻片、容量瓶、锥形瓶等各类实验器皿。在实验中&#xff0c;如果烧杯清洁不到位&#xff0c;会使得实验数…

分布式存储 vs. 全闪集中式存储:金融数据仓库场景下的性能对比

作者&#xff1a;深耕行业的 SmartX 金融团队 张德敏 近年来随着金融行业的高速发展&#xff0c;经营决策者及监管机构对信息时效性的要求越来越高&#xff0c;科技部门面临诸多挑战。例如&#xff0c;不少金融机构使用数仓业务系统&#xff0c;为公司高层提供日常经营报表&am…

CSV文件新增数据知多少

CSV文件编辑数据时 一、如果数据的长度不超过11位&#xff0c;直接正常输入 二、如果数据的长度大于等于11位&#xff0c;新增数据时&#xff0c;将列格式改为文本&#xff0c;并且输入数字&#xff0c;数字使用文本格式保存 如果数字长度超过11位&#xff0c;则展示为E科学…

seq“ 和 “time“ 字段

在RTP&#xff08;Real-time Transport Protocol&#xff09;报文中&#xff0c;"seq" 和 "time" 字段分别表示以下内容&#xff1a; 1. **Seq&#xff08;Sequence Number&#xff09;字段**&#xff1a;Seq字段是RTP头部中的一个16位字段&#xff0c;用…

​iOS上架App Store的全攻略

第一步&#xff1a;申请开发者账号 在开始将应用上架到App Store之前&#xff0c;你需要申请一个开发者账号。 1.1 打开苹果开发者中心网站&#xff1a;Apple Developer 1.2 使用Apple ID和密码登录&#xff08;如果没有账号则需要注册&#xff09;&#xff0c;要确保使用与公…

瑞芯微RKNN开发·yolov5

官方预训练模型转换 下载yolov5-v6.0分支源码解压到本地&#xff0c;并配置基础运行环境。下载官方预训练模型 yolov5n.ptyolov5s.ptyolov5m.pt… 进入yolov5-6.0目录下&#xff0c;新建文件夹weights&#xff0c;并将步骤2中下载的权重文件放进去。修改models/yolo.py文件 …

提高编程效率-Vscode实用指南

您是否知道全球73%的开发人员依赖同一个代码编辑器&#xff1f; 是的&#xff0c;2023 年 Stack Overflow 开发者调查结果已出炉&#xff0c;Visual Studio Code 迄今为止再次排名第一最常用的开发环境。 “Visual Studio Code 仍然是所有开发人员的首选 IDE&#xff0c;与专业…

eBay类目限制要多久?eBay促销活动有哪些?-站斧浏览器

eBay类目限制要多久&#xff1f; 1、eBay对不同类目的商品有不同的限制和要求。一些类目可能对新卖家有一定的限制&#xff0c;限制他们在该类目下销售商品的数量或需要满足某些条件才能进行销售。 2、对于新卖家的限制通常是在一定时间内&#xff0c;比如30天或90天&#xf…

(转)STR 内核做了什么

参考这篇文章&#xff1a; Linux电源管理(6)_Generic PM之Suspend功能 写的很清晰

微信小程序一键获取位置

需求 有个表单需要一键获取对应位置 并显示出来效果如下&#xff1a; 点击一键获取获取对应位置 显示在 picker 默认选中 前端 代码如下: <view class"box_7 {{ showChange1? change-style: }}"><view class"box_11"><view class"…

Stable Diffusion WebUI报错RuntimeError: Torch is not able to use GPU解决办法

新手在安装玩Stable Diffusion WebUI之后会遇到各种问题&#xff0c; 接下来会慢慢和你讲解如何解决这些问题。 在我们打开Stable Diffusion WebUI时会报错如下&#xff1a; RuntimeError: Torch is not able to use GPU&#xff1b;add --skip-torch-cuda-test to COMMANDL…

SQL题目记录

1.商品推荐题目 1.思路&#xff1a; 通过取差集 得出要推荐的商品差集的选取&#xff1a;except直接取差集 或者a left join b on where b null 2.知识点 1.except selectfriendship_info.user1_id as user_id,sku_id fromfriendship_infojoin favor_info on friendship_in…