在昨天的学习中我们了解到了汇编指令可以帮助我们直接操作相关的寄存器来控制我们的LED灯,但是汇编指令是不适合我们复杂的C语言开发的,我们可以利用汇编代码设置好基本的运行环境,引导我们的C语言代码实现
对于固定地址的操作我们需要对一些地址进行变换,将这些地址进行强制类型转换,根据四个字节大小改变其中的数据,来实现对寄存器的操作

这样的代码是可以实现我们想要达到的功能的,但是ARM架构下的寄存器数量是及其庞大的,仅仅是实现一个小功能都需要我们对多个寄存器进行操作,那我们应该怎么办呢???
没关系,已经有服务商为我们编写好了这样的一些库,帮助我们去查找控制寄存器,那就是我们的STM32固态库
1.STM32固态库
1 分类:
- 标准库;封装寄存器以及对于寄存器的操作
- HAL库:封装一些宏,结构体,枚举,函数等
那么在今天的学习中,我们将使用我们的标准库来实现对应的操作;
首先对于源代码的编译我们先来展开了解
C语言的代码的编译分为四大部
- 预处理
- 编译
- 汇编
- 链接
ARM上程序的编译大体也是这个步骤,只不过我们需要用的文件和要生成的文件不太相同
我们这里利用到了GNU工具链对于我们的源代码进行编译,来看一下我们的具体实现过程吧
2,GNU工具链
1.arm-linux-gnueabihf-gcc -g -c led.S -o led.o
功能:
- 编译汇编启动文件或C语言文件,生成一个.o 文件
- -g 运行产生调适信息
- -c 编译源文件但不链接
- -o 指定编译产生的文件名
2.arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
功能:
- 将所有的.o 文件链接起来组成可执行的文件
在这里我们需要明确一个概念,0x87800000是程序到时候上电之后在ddr上的运行地址
存储地址:
链接后的可执行文件存储地位,存储位置可以任意选择(SD卡,EMMC,NAND)
运行地址:
代码运行时所处的地址,在链接时确定好运行地址,不同CPU的存储与运行地址都是不同的
代码运行时必须处于链接地址
链接流程执行图:

3.arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
objcopy 就像一个格式转换工具,将.lef 文件转换为 .bin文件
- -O 指定程序以什么格式输出,binary 表示以二进制形式输出
- -S 表示不复制源文件中的重定位信息与符号信息
4.arm-linux-gnueabihf-objdump -D led.elf > led.dis
反汇编指令
反汇编文件中可以看出代码已经链接到了以0x87800000为起始地址的区域
我们也可以通过一种链接脚本来指定程序的运行地址:
3.链接脚本
先来了解一下blf 文件 的布局:

Linux操作系统中的可执行文件与静态库都是这种格式;
不同的格子对应着不同的段,我们来对不同的段展开了解:
1. .bss段
- 用于存放未初始化或初始化为0的数据
- 在运行时会被自动清零
- 典型例子:
c
static int bss_var; // 静态变量(全局或局部)
int explicit_var = 0; // 显式初始化为0的全局变量
2. COMMON段
- 用于存放未初始化的非静态全局变量
- 链接时才确定最终大小和位置
- 允许多个目标文件定义同名符号
- 典型例子:
c
int common_var; // 未初始化的非静态全局变量
3. .data段
- 用于存放已初始化的全局变量和静态变量(非零值)
- 需要在程序文件中保存实际的初始值
- 典型例子:
c
int global_var = 100; // 初始化为非零值
int global_array[] = {1,2,3}; // 初始化为非零数组
4. .rodata段(只读数据段)
- 存放程序的只读数据
- 运行时受保护,不可修改
- 包含的数据类型:
- 字符串常量
- const修饰的全局变量
- 全局只读数组
- switch跳转表
- 浮点数常量
- 典型例子:
c
const int MAX_VALUE = 100; // const全局变量
char* str = "Hello World"; // 字符串常量
const int lookup[] = {1,2,3}; // 只读数组
最终我们的链接脚本就是这种格式的:

好了了解完了我们的链接脚本以及我们的编译流程,我们就可以正式尝试使用我们的标准库来实现LED灯的点亮了
1.搭建环境:
- 在刚开始运行的复位函数中我们需要设置好对应的工作模式与栈区

- 接下来我们需要对已经加载进运行地址的.bss段(存放未初始化的全局变量)进行清零,这里我们选择用汇编代码的循环实现

执行完了这些就可以放心的进入到我们的C语言函数中运行了
2.标准库的使用:
因为标准库对于要操作的相关寄存器都进行了定义,对于我们设置功能的函数也进行了定义,所有我们在这里可以直接进行调用:
- 初始化引脚功能:
led:
我们对于引脚的复用属性与输出方向进行设置
beep:

以上的区别仅仅是我们使用的引脚不同;
2.实现功能:
这里我们只需要操作GPIO3_IO01 的 DR寄存器,让对应的引脚输出高低电平就可以实现对基本外设的控制:
led:

beep:

这样就利用C语言代码点亮我们的led灯并且让我们的蜂鸣器工作了