嵌入式学习笔记——大小端及跳转到绝对地址
大小端以及跳转到绝对地址 0x100000
- 嵌入式编程中的大小端详解
- 一、大端模式与小端模式
- 二、判断当前系统是大端还是小端
- 方法一:指针强制类型转换
- 方法二:使用联合体(union)
- 三、结构体位段和大小端的影响
- 四、大小端影响内存的 memcpy 拷贝效果
- 五、大小端转换函数
- 六、总结
- 跳转到绝对地址 0x100000 的原理与实现
- 一、为何需要跳转?
- 二、跳转原理
- 三、代码实现
- 四、简化写法
- 五、Flash 分区结构示意图
- 六、注意事项
- 七、小结
嵌入式编程中的大小端详解
在嵌入式编程中,理解大小端是非常重要的,它直接关系到数据在内存中的布局和跨平台通信时的数据解析正确性。
一、大端模式与小端模式
大端模式(Big Endian):高字节存在低地址,低字节存在高地址。
小端模式(Little Endian):低字节存在低地址,高字节存在高地址。
例如:
unsigned int temp = 0x12345678;
假设 temp
的地址是 0x20000010
,那么:
-
在 大端模式 中,内存排列为:
0x20000010
:0x120x20000011
:0x340x20000012
:0x560x20000013
:0x78
-
在 小端模式(STM32) 中,内存排列为:
0x20000010
:0x780x20000011
:0x560x20000012
:0x340x20000013
:0x12
二、判断当前系统是大端还是小端
方法一:指针强制类型转换
#include <stdio.h>
int main() {
int num = 1; // 定义整型变量 num,值为1
char *ptr = (char *)# // 强制转换为字符型指针,查看最低地址的字节内容
if (*ptr == 1) {
printf("小端模式\n");
} else {
printf("大端模式\n");
}
return 0;
}
方法二:使用联合体(union)
#include <stdio.h>
union endian_check {
int num;
char single_byte; // 访问低地址的单字节
};
int main() {
union endian_check check;
check.num = 1; // 赋值为1(0x00000001)
if (check.single_byte == 1) {
printf("小端模式\n");
} else {
printf("大端模式\n");
}
return 0;
}
三、结构体位段和大小端的影响
#include <stdio.h>
struct mybitfields {
unsigned short a : 4; // 4位位段
unsigned short b : 5; // 5位位段
unsigned short c : 7; // 7位位段
} test;
int main() {
int i;
test.a = 2; // 二进制 0010
test.b = 3; // 二进制 00011
test.c = 0; // 二进制 0000000
i = *((short *)&test); // 强制转换结构体地址为 short* 后解引用
printf("%d\n", i); // 输出为50,实际内存内容:0010 00011 0000000 => 0x32
return 0;
}
四、大小端影响内存的 memcpy 拷贝效果
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
unsigned int uiVal_1 = 0x12345678; // 原始整型变量
unsigned int uiVal_2 = 0; // 存储从字节数组拷贝后的数据
unsigned char aucVal[4] = {0x12, 0x34, 0x56, 0x78}; // 定义字节数组
unsigned short usVal_1 = 0;
unsigned short usVal_2 = 0;
memcpy(&uiVal_2, aucVal, sizeof(uiVal_2)); // 把数组内容复制到uiVal_2中
usVal_1 = (unsigned short)uiVal_1; // 截断低16位 => 0x5678
usVal_2 = (unsigned short)uiVal_2; // 拷贝后低16位 => 0x3412 (小端存储顺序)
printf("usVal_1: %x\n", usVal_1); // 输出截断值
printf("usVal_2: %x\n", usVal_2); // 输出截断值
return 0;
}
五、大小端转换函数
// 32位整数转换
int swapInt32(int intValue) {
int temp = 0;
temp = ((intValue & 0x000000FF) << 24) |
((intValue & 0x0000FF00) << 8) |
((intValue & 0x00FF0000) >> 8) |
((intValue & 0xFF000000) >> 24);
return temp;
}
// 16位短整型转换
unsigned short swapShort16(unsigned short shortValue) {
return ((shortValue & 0x00FF) << 8) | ((shortValue & 0xFF00) >> 8);
}
// 32位浮点数转换(使用联合体)
float swapFloat32(float floatValue) {
typedef union {
float unionFloat;
int unionInt;
} SWAP_UNION;
SWAP_UNION swapUnion;
swapUnion.unionFloat = floatValue;
swapUnion.unionInt = swapInt32(swapUnion.unionInt);
return swapUnion.unionFloat;
}
// 64位双精度浮点数转换(使用指针反转)
void swapDouble64(unsigned char *pIn, unsigned char *pOut) {
for (int i = 0; i < 8; i++) {
pOut[7 - i] = pIn[i]; // 将输入按字节反转存入输出
}
}
int main() {
int x = 0x12345678;
int y = swapInt32(x); // 调用函数转换大小端
printf("%x\r\n", y); // 输出结果应为 0x78563412
return 0;
}
六、总结
- STM32 采用小端模式。
- 小端:低位字节存在低地址;大端:高位字节存在低地址。
- 判断大小端可使用指针、联合体等方法。
- 位段的使用也会受到大小端的影响。
- 在多平台数据交换时,必须进行大小端转换,确保数据一致性。
跳转到绝对地址 0x100000 的原理与实现
在嵌入式开发中,程序往往被分布在 Flash 的不同区域。例如,在采用 Bootloader + 主程序(APP)结构的设计中,Bootloader 启动后会跳转到主程序所在的地址开始执行。这种“跳转”就是我们常说的“跳转到绝对地址执行”,下面我们详细介绍其原理与实现方法。
一、为何需要跳转?
常见应用场景包括:
- Bootloader 启动后跳转到主应用程序执行
- 多镜像升级系统(A/B 双系统)
- 不同 Flash 区域运行不同的功能模块
例如,如果主程序被烧录在 Flash 的地址 0x100000
(假设起始地址),那么 Bootloader 启动后就需要跳转到这个地址运行主程序。
二、跳转原理
Cortex-M 内核芯片(如 STM32)启动时,会自动读取启动地址前 8 字节:
- 第 1 个字(偏移 0):主堆栈指针初始值(MSP)
- 第 2 个字(偏移 4):程序入口地址(Reset_Handler)
因此,跳转前必须:
- 设置新的 MSP 值为
*(uint32_t*)0x100000
- 设置跳转地址为
*(uint32_t*)(0x100000 + 4)
并执行
三、代码实现
推荐使用 typedef
简化函数指针写法:
#include <stdint.h>
#define APP_ADDRESS 0x100000 // 目标程序地址
typedef void (*pFunction)(void); // 定义函数指针类型
void jump_to_app(void) {
__disable_irq(); // 关闭中断,避免干扰
uint32_t jump_address = *(volatile uint32_t*)(APP_ADDRESS + 4); // 程序入口地址
pFunction JumpToApplication = (pFunction)jump_address; // 转换成函数指针
__set_MSP(*(volatile uint32_t*)APP_ADDRESS); // 设置主堆栈指针(MSP)
JumpToApplication(); // 跳转执行目标程序
}
上述代码完成了从 Bootloader 跳转到主程序的过程。
四、简化写法
((void (*)())0x100000)(); // 将地址当作函数指针并执行
虽然简洁,但不推荐用于 STM32,因为没有设置 MSP主堆栈指针,容易导致系统异常。
五、Flash 分区结构示意图
+------------------------+
| 地址 0x08000000 | → Bootloader
+------------------------+
| 地址 0x08010000 | → APP 主程序(即 0x100000)
+------------------------+
| ...... |
注:部分 STM32 芯片 Flash 起始地址是 0x08000000
,此处 0x100000
视具体芯片配置而定。
六、注意事项
- 跳转地址处必须是有效程序,并具备正确的中断向量表
- 必须先设置 MSP,否则可能因栈指针错误导致 HardFault
- 跳转前应关闭中断,避免中断未关闭造成干扰
- 若使用 FreeRTOS 等 RTOS,需要考虑中断向量重定向问题
七、小结
项目 | 说明 |
---|---|
跳转地址 | 比如 0x100000 ,主程序存放起点 |
MSP 设置 | 必须从地址读取并设置 __set_MSP() |
入口地址 | 是 *(uint32_t*)(addr + 4) |
函数指针跳转 | 把地址强转为函数指针并调用 |