1、Windows/Linux基础与工具
1.1 GCC 编译
一个 C/C++文件要经过
- 预处理(preprocessing)
- 编译(compilation)
- 汇编(assembly)
- 和连接(linking)
等 4 步才能变成可执行文件,如在日常交流中通常使用“编译” 统称这 4 个步骤,
假设有一个源文件 hello.c。
1.1.1 预处理 (Pre-processing)
-
做了什么: “大扫除”。处理所有以
#开头的指令。- 展开宏定义: 把
#define PI 3.14全部替换成3.14。 - 包含头文件: 把
#include <stdio.h>的内容直接“复制粘贴”到你的代码里。 - 处理条件编译: 比如
#ifdef DEBUG。 - 去掉注释: 让代码变得干干净净。
- 展开宏定义: 把
-
指令:
gcc -E hello.c -o hello.i -
产物:
.i文件(依然是文本,但体积变大了很多)。
1.1.2. 编译 (Compilation)
- 做了什么: “翻译官”。这是最核心的一步。GCC 把你写的 C 语言翻译成 汇编语言。这时候编译器会检查你的语法错误(比如少个分号)。
- 指令:
gcc -S hello.i -o hello.s - 产物:
.s文件(汇编代码,你能看懂一点点指令,如mov,push)。
1.1.3. 汇编 (Assembly)
- 做了什么: “降维打击”。把汇编代码翻译成机器能听懂的 二进制指令(机器码) 。
- 指令:
gcc -c hello.s -o hello.o - 产物:
.o文件(目标文件/Object file)。它是二进制的,你直接打开会看到一堆乱码。
1.1.4. 链接 (Linking)
- 做了什么: “组装车间”。你的程序可能调用了别人的库(比如
printf函数是在 C 标准库里的)。链接器把你的多个.o文件和系统提供的库文件(.so或.a)“粘”在一起,生成最终的可执行文件。 - 指令:
gcc hello.o -o hello - 产物: 可执行文件(Linux 下通常没有后缀,或者叫
a.out)。
1.2 gcc vs arm-linux-gcc 的本质区别
在 Windows下进行开发时,只需要单击几个按钮即可编译,集成开发环境(比如 Visual studio)已经将各种编译工具的使用封装好了。
Linux 下也有很优秀的集成开发工具,但是更多的时候是直接使用编译工具;即使使用集成 开发工具,也需要掌握一些编译选项。
- PC 上的编译工具链为 gcc、ld、objcopy、objdump 等,它们编译出来的程序在 x86 平台上运行。
- 要编译出能在 ARM 平台上运行的程序,必须使用交叉编译工具 arm-linux-gcc、
它们就像是两个翻译官。虽然他们都叫 GCC(GNU Compiler Collection),但他们的“母语”和“目标语言”不同。
我们可以用 “宿主机(Host)”和“目标机(Target)” 的概念来理解:
| 特性 | gcc (本地编译器) | arm-linux-gcc (交叉编译器) |
|---|---|---|
| 运行在哪里 | 本地电脑 (x86) | 本地电脑 (x86) |
| 产出的程序跑在哪里 | 本地电脑 (x86) | 嵌入式开发板 (ARM) |
| 链接的库 | 标准的 x86 版 Glibc 库 | 针对 ARM 指令集优化的库 |
1.3 Makefile
在 Linux 中使用 make 命令来编译程序,特别是大程序;而 make 命令所执行的动作依赖于 Makefile 文件。 Makefile 的存在只为了解决两件事:
- 自动化编译: 敲一个
make命令,全部搞定。 - 增量编译(核心价值): 它会检查哪个文件改动过,只重新编译改动过的部分,没动过的直接链接,极大地节省时间。
2、 GPIO 接口
GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚, 可以通过它们输出高低电平或者通过它们读入引脚的状态——是高电平还是低电平。
2.1 硬件核心:三个“控制面板”(寄存器)
芯片内部为每一组引脚都准备了三个 32 位的“控制面板”。通过 C 语言往这些地址写数据,就能改变引脚的物理特性。
| 寄存器名称 | 核心功能 | 理解视角 |
|---|---|---|
| GPxCON | 配置 (Configuration) | 决定引脚的身份。是当“输出”(发信号控制激光),还是“输入”(读传感器信号)? |
| GPxDAT | 数据 (Data) | 真正的开关。配置为输出时,写 1 引脚变高电平(3.3V),写 0 变低电平(0V)。 |
| GPxUP | 上拉 (Pull-up) | 决定引脚悬空时的状态。开启上拉能防止引脚因为电磁干扰而产生随机信号(这对读取稳定信号至关重要)。 |
2.2 怎样使用软件来访问硬件
单个引脚的操作无外乎 3 种:输出高低电平、检测引脚状态、中断。对某个引脚的操作 一般通过读、写寄存器来完成。 在 Linux C 编程中,看到的地址只是内存;但在嵌入式底层,地址就是硬件。
// 1. 定义地址:假设 0x56000050 是 GPFCON 寄存器的物理地址
#define GPFCON (*(volatile unsigned long *)0x56000050)
// 2. 操作硬件
GPFCON = 0x100; // 这行代码执行后,芯片某个引脚的物理电压可能瞬间改变
2.3. 位运算
-
|(或): 有 1 则 1。常用于 “置 1” (打开开关)。 -
&(与): 全 1 才 1。常用于 “清 0”或“检测” 。 -
~(取反): 1 变 0,0 变 1。常用于 “构造掩码” 。 -
<<(左移): 移动 1 的位置。 例
- 公式:
Register |= (1 << n)(将某一位“置 1”) - 公式:
Register &= ~(1 << n)(将某一位“清 0”)
3、内存管理单元 MMU
内存管理单元(Memory Management Unit)简称 MMU,它负责虚拟地址到物理地址的 映射,并提供硬件机制的内存访问权限检查。现代的多用户多进程操作系统通过 MMU 使得 各个用户进程都拥有自己独立的地址空间:地址映射功能使得各进程拥有“看起来”一样的 地址空间,而内存访问权限的检查可以保护每个进程所用的内存不会被其他进程破坏。
-
虚拟地址 (Virtual Address): 程序员看到的地址。在 32 位系统里,每个进程都以为自己拥有从
0x00000000到0xFFFFFFFF的完整 4GB 空间。 -
物理地址 (Physical Address): 内存条上实打实的电子存储单元地址。