嵌入式note4.1裸机开发
1.看原理图,需要用到的引脚
1.设置某一个引脚为输入、输出引脚(GPIO),要怎么做?
1)看原理图,D7为例子,这个led接到了GPIOE13,所以第一步 在芯片手册,搜GPIOE13。
得到,要使用E14这个引脚复用成通用输入输出功能(GPIOE13),应该选择候选功能0。(可以看到功能0对应GPIOE13)需要的信号名(GPIOE13)在功能0那格

2)候选功能寄存器GPIOxALTFN
搜候选功能寄存器GPIOxALTFN,发现GPIOx[13]使用GPIOxALTFN0进行配置,它的物理地址是0xC001E020

[27:26]=00


3)输入配置寄存器GPIOxOUTENB
搜输入输入配置寄存器GPIOxOUTENB,得到GPIOEOUTENB的物理地址是0xC001E004,设置[13]=1,输出。

4)搜数据寄存器GPIOxOUT,得到GPIOEOUT的物理地址是0xC001E000,设置[13]=0/1,输出高低电平。

5)(uboot默认是关闭看门狗寄存器,保险起见自己关一下)搜看门狗配置寄存器WTCON,得到其物理地址0xC0019000,设置[5]=0,关闭看门狗定时器。

代码
1.编写ARM 汇编代码(初始化代码)
#define WTCON 0xC0019000
#define GPIOxALTFN0 0xC001E020
#define GPIOEOUTENB 0xC001E004
#define GPIOEOUT 0xC001E000
//宏定义部分,定义寄存器的物理地址
.global _start
//声明_start为全局符号,是程序的入口地址(链接脚本指定的入口)
_start:
//程序执行的起点,嵌入式裸机程序必须从这里开始执行
//关闭看门狗
LDR R0,=WTCON
LDR R1,[R0] //R1得到0xC0019000下的内容/值
BIC R1,R1, #(1<<5) //第五位清零,关闭看门狗定时器
//BIC 目标寄存器, 源寄存器, 掩码
//1 << 5
//1 = 0000 0001
//1 << 5 = 0010 0000 (第5位变成1)
//BIC规则:
//掩码是 1 的位 → 结果变 0
//掩码是 0 的位 → 结果保持不变
// 1111 1111 (原 R1)
//& 1101 1111 (~00100000,掩码取反)
//= 1101 1111 (新 R1)
STR R1,[R0]
//STR数据存储指令
//R1:源寄存器(要写入的数据来自这里)
//[R0]:R0 里面存的是内存地址
// 把 R1 的值,写入到 R0 指向的内存地址中
//候选功能0
LDR R0,=GPIOxALTFN0
LDR R1,[R0] //R1得到0xC0019000下的内容/值
BIC R1,R1, #(3<<26) //[27:26]=00
//3即0000 0011
STR R1,[R0]
//输入输出使能寄存器
LDR R0,=GPIOEOUTENB
LDR R1,[R0] //R1得到0xC0019000下的内容/值
ORR R1,R1, #(1<<13) //[13]=1 输入出
//ORR = Bitwise OR(按位或)
//按位或运算规则:1 | 任何数 = 1,0 | 数 = 数本身
//第 13 位:无论原来是什么,强制变成 1
STR R1,[R0]
loop:
//loop: 是汇编里的「标签(Label)」
//作用 = 给这一行代码起个名字,方便跳转到这里执行
//数据寄存器
LDR R0,=GPIOEOUT
LDR R1,[R0] //R1得到0xC0019000下的内容/值
ORR R1,R1, #(1<<13) //[13]=1,输出高电平,led灭
STR R1,[R0]
MOV R0,#0xff00000
MOV R1,#0x50000
bl delay //bl跳转的同时保存返回地址
//数据寄存器
LDR R0,=GPIOEOUT
LDR R1,[R0] //R1得到0xC0019000下的内容/值
BIC R1,R1, #(1<<13) //[13]=0,输出低电平,led亮
STR R1,[R0]
MOV R0,#0xff00000
MOV R1,#0x50000
bl delay //bl跳转的同时保存返回地址
//bl = Branch with Link 中文:带链接的跳转
//跳过去执行,同时把 “回来的路” 存在 LR 里,保证函数执行完能正确返回到主程序
//b (Branch):跳转。让 PC 指针跳到目标地址。
//l (Link):链接。把 下一条指令的地址(返回地址)保存到 LR 寄存器 (R14) 中。
b loop //跳转
//执行到 B loop
//跳回 loop: 那一行
//一直反复,永远出不去
- 系统启动完,不能让程序跑飞
- 必须停在一个死循环里等待
- 所以裸机启动代码最后一定是:
loop:
B loop
| 指令 | 全称 | 作用 |
|---|---|---|
LDR |
Load Register | 从内存加载数据到寄存器(支持加载地址 / 内存值) |
STR |
Store Register | 将寄存器数据写入内存 |
BIC |
Bit Clear | 按位清除,将指定位清零 |
ORR |
Bitwise OR | 按位或运算,规则:1 | 任何数 = 1,0 | 数 = 数本身 |
LDR R0, =label |
伪指令 | 加载 label 对应的地址到寄存器(ARM 汇编常用技巧) |
| 指令 | 英文 | 方向 | 意思 |
|---|---|---|---|
| LDR R1, [R0] | Load | 内存 → 寄存器 | 读 |
| STR R1, [R0] | Store | 寄存器 → 内存 | 写 |
register:
-
名词 (n.):(计算机 / 嵌入式)寄存
-
动词 (v.):登记;注册;
“store” 最常见的意思是储存、存放,也可指商店、店铺
bl = Branch with Link 中文:带链接的跳转

2.裸机程序的makefile文件(跑在硬件上的makefile文件)
大小写m都可以,大写M和小写m同时存在优先执行小写的
ledSC.bin:LedSC.o delay.o #目标:依赖
#ledSC.bin:最终目标文件,是可以直接烧录到 ARM 芯片的纯二进制裸机程序
#ledSC.o delay.o:依赖文件,由汇编文件ledSC.S和 C 文件delay.c编译生成的目标文件
arm-linux-ld -Ttext 0x40000000 -o ledSC.elf $^
#arm-linux-ld(链接工具):ARM 架构的链接器,把多个.o目标文件合并成可执行文件
#-T:指定代码段,指定text代码段放在0x40000000的地址上,(指定裸机程序的链接地址)
#内存开始的地方是0x40000000
#-o 指定输出文件的名字,.elf 表示可执行链接文件
#$^:Makefile 自动变量,代表所有依赖文件(即ledSC.o delay.o)
arm-linux-objcopy -O binary ledSC.elf ledSC.bin
#arm-linux-objcopy:二进制格式转换工具
#-O binary:(是大写O)指定输出格式为纯二进制(binary),剥离 ELF 头、段信息、调试信息
#把可执行链接文件,转换成可以直接烧录到芯片的裸机二进制文件ledSC.bin
arm-linux-objdump -D ledSC.elf > ledSC_elf.dis
#arm-linux-objdump:反汇编工具
#-D:显示文件的所有汇编信息
#把可执行链接文件转换成汇编文件
%.o:%.S
#写明依赖关系,.O文件依赖.S文件
arm-linux-gcc -o $@ $< -c -nostdlib
#arm-linux-gcc:ARM 架构的交叉编译器
# -o:指定生成文件
# $@是自动变量,代表目标文件,这里指LedSC.o
# $<:自动变量,代表第一个依赖文件
#-c:只编译不连接,生成.o目标文件,一连接就生成可执行文件
# -nostdlib:不链接(使用)标准 C 库函数(裸机程序没有操作系统,没有文件的概念,不能用标准库)
%.o:%.c
arm-linux-gcc -o $@ $< -c -nostdlib
clean:
rm *.o *.elf *.bin *.dis -f
# clean:伪目标,用于清理编译生成的中间文件
3.执行编译,生成ledSC.bin
make clean # 清理所有编译产物
make # 执行编译,生成ledSC.bin
复制到共享目录下

| 命令 | 目标地址 | 说明 |
| ---------------------------- | ------------- | ------------------- |
| `loadb` | `0x4800_0000` | 默认地址(U-Boot 环境变量设置) |
| `loadb 0x40000000 ledSc.bin` | `0x4000_0000` | 显式指定 DDR 起始地址 |
-
0x4000_0000 是 DDR3 内存的物理起始地址
-
开发板通常配备 1GB DDR(范围:
0x4000_0000~0x7FFF_FFFF)
是否需要加载到 `0x40000000` 取决于程序本身的链接地址,而不是一个固定规则。
加载地址 = 链接地址(或符合重定位要求)
程序在哪里运行,取决于编译/链接时指定的地址,U-Boot 只是负责把数据放到正确的位置。
## 常见情况对比
| 程序类型 | 典型加载地址 | 原因 |
|---------|------------|------|
| **裸机程序** (如 `ledSc.bin`) | `0x40000000` 或链接指定地址 | 直接跑在 DDR,无操作系统搬运 |
| **Linux zImage/uImage** | `0x48000000` 或 `0x60000000` | 避免覆盖 U-Boot,内核自解压/重定位 |
| **设备树 dtb** | 内核附近或指定地址 | U-Boot 通过 `bootm` 传递给内核 |
| **U-Boot 自身** | `0x43C00000` 等 | 由 BL2 从 eMMC/SD 加载到特定地址 |
---
GEC6818 的具体例子
DDR 内存范围: 0x40000000 ~ 0x7FFFFFFF (1GB)
常用加载区域:
├─ 0x40000000 : 裸机程序、简单测试代码
├─ 0x48000000 : U-Boot 默认 loadaddr,Linux 内核常用
├─ 0x49000000 : 设备树 dtb
├─ 0x4A000000 : ramdisk、其他数据
└─ ... : 根据实际需要分配
```
---
## 怎么知道该用哪个地址?
### 1. 看链接脚本(裸机程序)
```ld
/* ledSc.lds */
SECTIONS {
. = 0x40000000; /* ← 链接基址 */
.text : { *(.text*) }
.data : { *(.data*) }
}
```
→ 必须加载到 `0x40000000`
### 2. 看 U-Boot 环境变量
```bash
GEC6818# printenv
loadaddr=0x48000000 # ← 默认加载地址
fdtaddr=0x49000000
```
→ 加载内核等可用默认值
### 3. 看编译时的指定
```bash
arm-linux-gnueabihf-ld -Ttext 0x41000000 ...
```
→ 按编译时指定的地址加载
---
## 总结
| 问题 | 答案 |
| 裸机程序为什么常用 `0x40000000`? | 因为链接脚本指定了 DDR 起始地址,简单直接 |
| 加载地址错误会怎样? | 程序跑飞、崩溃、或执行异常 |
**关键是:加载地址要与程序的链接地址匹配,或者程序自身具备重定位能力。**

最后:
go 0x40000000 # 跳转到该地址执行
完整编译流程(从源码到烧录)
ledSC.S → arm-linux-gcc → ledSC.o
delay.c → arm-linux-gcc → delay.o
ledSC.o + delay.o → arm-linux-ld → ledSC.elf
ledSC.elf → arm-linux-objcopy → ledSC.bin(可烧录)
ledSC.elf → arm-linux-objdump → ledSC_elf.dis(反汇编调试)
开发板网络设置和支持uboot命令

连接后输入reboot,在倒计时时不断按空格键进入下载模式,在没有进入系统的情况下运行裸机程序
默认的是不支持loadb通过串口传程序文件

我们重新烧录uboot使其支持网络和文件传输
接上OTG,输入fastroot

打开设备管理器,显示如下是已连接

运行。bat文件

查看网络


设置网关为虚拟机的ip(虚拟机为服务器),ip在同一网段并且不能和windows和ubanto冲突(133)Windows10 ubanto12


修改完保存

修改完后重启(reboot或复位按键)
重启后检查
检测环境变量设置的对不对



更多推荐



所有评论(0)