引言:为什么选择ARM9架构进行嵌入式系统学习
ARM9架构作为嵌入式系统领域的经典处理器架构,至今仍然是学习嵌入式开发的理想选择。虽然ARM Cortex-M系列已经广泛应用,但ARM9提供了更接近硬件底层的学习体验,帮助开发者深入理解计算机体系结构、操作系统原理和驱动开发等核心概念。
ARM9处理器采用五级流水线设计,支持MMU(内存管理单元),主频通常在200MHz以上,具备出色的性能功耗比。这些特性使其成为学习嵌入式Linux开发、实时操作系统移植和硬件驱动开发的绝佳平台。更重要的是,ARM9相关的开发板和学习资源非常丰富,学习曲线相对平缓,非常适合初学者入门。
第一部分:ARM9基础知识体系构建
1.1 ARM9处理器架构核心概念
ARM9采用精简指令集(RISC)架构,具有以下关键特征:
流水线设计:ARM9采用经典的五级流水线(Fetch-Decode-Execute-Memory-Writeback),这种设计大大提高了指令执行效率。理解流水线的工作原理对于编写高效的嵌入式代码至关重要。
工作模式:ARM9支持多种工作模式,包括用户模式(User)、系统模式(System)、管理模式(Supervisor)、中止模式(Abort)、未定义模式(Undefined)、中断模式(IRQ)和快速中断模式(FIQ)。每种模式都有其特定的用途和权限级别。
寄存器组织:ARM9有37个32位寄存器,包括18个通用寄存器(R0-R17)和多个特殊功能寄存器(如程序计数器PC、链接寄存器LR、程序状态寄存器CPSR等)。
1.2 开发环境搭建
硬件环境准备
- 开发板选择:推荐S3C2440或S3C2410核心的开发板,如友善之臂mini2440、天嵌TE2440等
- 调试工具:JTAG仿真器(如J-Link、ULINK)或OpenOCD
- 串口工具:USB转TTL串口模块,用于调试信息输出
- 电源适配器:5V/2A直流电源
软件环境配置
# Ubuntu 20.04/22.04 环境下安装交叉编译工具链
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabi
sudo apt-get install binutils-arm-linux-gnueabi
sudo apt-get install gdb-arm-linux-gnueabi
# 安装其他必要工具
sudo apt-get install build-essential
sudo apt-get install u-boot-tools
sudo apt-get install device-tree-compiler
sudo apt-get install minicom # 串口调试工具
开发工具链验证
# 验证交叉编译器安装
arm-linux-gnueabi-gcc --version
# 测试编译一个简单程序
echo 'int main(){printf("Hello ARM9\\n");return 0;}' > hello.c
arm-linux-gnueabi-gcc -static hello.c -o hello
file hello # 应显示 ARM executable
# 查看编译出的二进制文件信息
arm-linux-gnueabi-objdump -h hello
第二部分:ARM9汇编语言编程基础
2.1 ARM9指令集详解
ARM9支持ARM和Thumb两种指令集状态。ARM指令集为32位,Thumb指令集为16位,后者可以节省存储空间。
数据传输指令
; MOV - 数据移动
MOV R0, #10 ; R0 = 10 (立即数赋值)
MOV R1, R0 ; R1 = R0 (寄存器间移动)
; LDR/STR - 内存访问
LDR R0, =0x40000000 ; 加载地址到R0
LDR R1, [R0] ; 从R0指向的地址读取数据到R1
STR R1, [R0, #4] ; 将R1的值写入R0+4的地址
; 批量数据传输
LDMIA R0!, {R1-R4} ; 从R0地址开始批量加载R1-R4,R0自动递增
STMIB R5!, {R1-R4} ; 批量存储到R5开始的地址,R5先递增后存储
算术运算指令
; 基本算术运算
ADD R0, R1, R2 ; R0 = R1 + R2
SUB R0, R1, R2 ; R0 = R1 - R2
RSB R0, R1, R2 ; R0 = R2 - R1 (反向减法)
; 乘法指令
MUL R0, R1, R2 ; R0 = R1 * R2 (32位结果)
MLA R0, R1, R2, R3 ; R0 = R1 * R2 + R3 (乘加)
; 带进位的运算
ADC R0, R1, R2 ; R0 = R1 + R2 + C (C是进位标志)
SBC R0, R1, R2 ; R0 = R1 - R2 - !C (借位)
逻辑运算指令
; 位操作
AND R0, R1, R2 ; R0 = R1 & R2 (按位与)
ORR R0, R1, R2 ; R0 = R1 | R2 (按位或)
EOR R0, R1, R2 ; R0 = R1 ^ R2 (按位异或)
BIC R0, R1, R2 ; R0 = R1 & (~R2) (位清除)
; 移位操作
LSL R0, R1, #5 ; R0 = R1 << 5 (逻辑左移)
LSR R0, R1, #5 ; R0 = R1 >> 5 (逻辑右移)
ASR R0, R1, #5 ; R0 = R1 >> 5 (算术右移,保持符号位)
ROR R0, R1, #5 ; R0 = R1循环右移5位
RRX R0, R1 ; R0 = R1带进位循环右移1位
分支指令
; 无条件分支
B label ; 跳转到label
BL function ; 跳转到function,并将返回地址存入LR
; 条件分支(基于条件码)
BEQ label ; 相等时跳转(Z=1)
BNE label ; 不等时跳转(Z=0)
BGT label ; 大于时跳转(N=V且Z=0)
BLT label ; 小于时跳转(N≠V)
BGE label ; 大于等于时跳转(N=V或Z=1)
BLE label ; 小于等于时跳转(N≠V或Z=1)
BCC label ; 无进位时跳转(C=0)
BCS label ; 有进位时跳转(C=1)
BVC label ; 无溢出时跳转(V=0)
BVS label ; 有溢出时跳转(V=1)
BHI label ; 高于时跳转(C=1且Z=0)
BLS label ; 低于或相等时跳转(C=0或Z=1)
软件中断指令
SWI 0x123456 ; 产生软件中断,用于系统调用
2.2 ARM9汇编程序结构
一个完整的ARM9汇编程序通常包含以下部分:
; 文件名: startup.s
; 功能: ARM9启动代码示例
.text ; 代码段声明
.arm ; 使用ARM指令集
.global _start ; 声明全局入口点
_start:
; 1. 初始化堆栈指针
LDR sp, =0x30001000 ; 设置栈顶地址(根据实际内存调整)
; 2. 清零BSS段
LDR r0, =__bss_start__
LDR r1, =__bss_end__
MOV r2, #0
clear_bss:
CMP r0, r1
STMLTIA r0!, {r2} ; 递增存储零
BLT clear_bss
; 3. 初始化数据段(从Flash复制到RAM)
LDR r0, =_data_load
LDR r1, =_data_start
LDR r2, =_data_end
copy_data:
CMP r1, r2
LDMLTIA r0!, {r3} ; 从加载地址读取
STMLTIA r1!, {r3} ; 写到运行地址
BLT copy_data
; 4. 调用C语言main函数
BL main
; 5. 死循环(main返回后)
halt:
B halt
.data
.align 4
_data_load: .word 0x00000000 ; Flash中的数据起始地址
_data_start: .word 0x30000000 ; RAM中的数据起始地址
_data_end: .word 0x30001000 ; RAM中的数据结束地址
__bss_start__: .word 0x30001000
__bss_end__: .word 0x30002000
2.3 ARM9汇编与C语言混合编程
在实际开发中,汇编和C语言混合编程非常常见:
C调用汇编函数
// main.c
#include <stdio.h>
// 声明汇编函数
extern int add_numbers(int a, int b);
int main() {
int result = add_numbers(10, 20);
printf("10 + 20 = %d\n", result);
return 0;
}
; add.s
.text
.global add_numbers
add_numbers:
; 参数通过R0和R1传递
ADD R0, R0, R1 ; R0 = R0 + R1
BX LR ; 返回到调用者
汇编调用C函数
; 调用C函数示例
.text
.global _start
.extern c_function ; 声明外部C函数
_start:
; 设置参数
MOV R0, #100 ; 第一个参数
MOV R1, #200 ; 第二个参数
; 调用C函数
BL c_function
; 继续执行...
B .
// C函数定义
int c_function(int a, int b) {
return a + b + 10;
}
第三部分:ARM9硬件接口编程
3.1 GPIO(通用输入输出)控制
GPIO是最基础的硬件接口,用于控制LED、按键等外设。
S3C2440 GPIO寄存器结构
// GPIO寄存器地址定义(S3C2440)
#define GPACON (*(volatile unsigned int *)0x56000000)
#define GPADAT (*(volatile unsigned int *)0x56000004)
#define GPBCON (*(volatile unsigned int *)0x56000010)
#define GPBDAT (*(volatile unsigned int *)0x56000014)
#define GPBUP (*(volatile unsigned int *)0x56000018)
// LED连接到GPB5, GPB6, GPB7, GPB8
#define LED1 (1 << 5)
#define LED2 (1 << 6)
#define LED3 (1 << 7)
#define LED4 (1 << 8)
GPIO控制LED示例
// led_control.c
#include "s3c2440_soc.h"
void delay_ms(unsigned int ms) {
volatile unsigned int i, j;
for (i = 0; i < ms; i++) {
for (j = 0; j < 1000; j++) {
__asm__ volatile("nop");
}
}
}
void led_init(void) {
// 配置GPB5-GPB8为输出模式
// GPBCON[15:14] = 01 (GPB5输出)
// GPBCON[17:16] = 01 (GPB6输出)
// GPBCON[19:18] = 01 (GPB7输出)
// GPBCON[21:20] = 11 (GPB8输出)
GPBCON &= ~((3 << 14) | (3 << 16) | (3 << 18) | (3 << 20));
GPBCON |= ((1 << 14) | (1 << 16) | (1 << 18) | (1 << 20));
// 禁用上拉电阻
GPBUP |= (LED1 | LED2 | LED3 | LED4);
// 初始状态:全部熄灭
GPBDAT |= (LED1 | LED2 | LED3 | LED4);
}
void led_on(int led_num) {
switch(led_num) {
case 1: GPBDAT &= ~LED1; break;
case 2: GPBDAT &= ~LED2; break;
case 3: GPBDAT &= ~LED3; break;
case 4: GPBDAT &= ~LED4; break;
}
}
void led_off(int led_num) {
switch(led_num) {
case 1: GPBDAT |= LED1; break;
case 2: GPBDAT |= LED2; break;
case 3: GPBDAT |= LED3; break;
case 4: GPBDAT |= LED4; break;
}
}
void led_toggle(int led_num) {
unsigned int mask = 0;
switch(led_num) {
case 1: mask = LED1; break;
case 2: mask = LED2; break;
case 3: mask = LED3; break;
case 9: mask = LED4; break;
}
if (GPBDAT & mask) {
GPBDAT &= ~mask; // 熄灭 -> 点亮
} else {
GPBDAT |= mask; // 点亮 -> 熄灭
}
}
int main(void) {
led_init();
while(1) {
// 流水灯效果
for(int i = 1; i <= 4; i++) {
led_on(i);
delay_ms(200);
led_off(i);
}
// 同时点亮所有LED
for(int i = 1; i <= 4; i++) {
led_on(i);
}
delay_ms(500);
// 同时熄灭所有LED
for(int i = 1; i <= 4; i++) {
led_off(i);
}
delay_ms(500);
}
return 0;
}
编译和运行
# 编译
arm-linux-gnueabi-gcc -c led_control.c -o led_control.o
arm-linux-gnueabi-gcc -c startup.s -o startup.o
arm-linux-gnueabi-gcc -T linker_script.ld -o led_control.elf startup.o led_control.o
# 生成二进制文件
arm-linux-gnueabi-objcopy -O binary led_control.elf led_control.bin
# 烧写到开发板(通过JTAG或U-Boot)
# 在U-Boot中:
# tftp 0x30000000 led_control.bin
# nand write 0x30000000 0x0 0x10000
3.2 UART串口通信
UART是嵌入式开发中最常用的调试接口。
UART寄存器配置
// UART寄存器定义(S3C2440)
#define ULCON0 (*(volatile unsigned int *)0x50000000)
#define UCON0 (*(volatile unsigned int *)0x50000004)
#define UFCON0 (*(volatile unsigned int *)0x50000008)
#define UMCON0 (*(volatile unsigned int *)0x5000000C)
#define UTRSTAT0 (*(volatile unsigned int *)0x50000010)
#define UERSTAT0 (*(volatile unsigned int *)0x50000014)
#define UFSTAT0 (*(volatile unsigned int *)0x50000018)
#define UTXH0 (*(volatile unsigned int *)0x50000020)
#define URXH0 (*(volatile unsigned int *)0x50000024)
#define UBRDIV0 (*(volatile unsigned int *)0x50000028)
#define UDIVSLOT0 (*(volatile unsigned int *)0x5000002C)
// 时钟频率
#define PCLK 50000000 // 假设PCLK=50MHz
#define BAUDRATE 115200
UART初始化和收发函数
// uart.c
#include "s3c2440_soc.h"
void uart0_init(void) {
// 1. 配置GPIO引脚为UART功能
// GPH2, GPH3 分别为 TXD0, RXD0
GPHCON &= ~((3 << 4) | (3 << 6));
GPHCON |= ((2 << 4) | (2 << 6)); // 设置为UART功能
// 2. 禁用上拉电阻
GPHUP |= (1 << 2) | (1 << 3);
// 3. 设置UART参数
// 8N1模式:8数据位,无校验,1停止位
ULCON0 = (0 << 6) | (0 << 3) | (0 << 2) | (3);
// 4. 设置工作模式
// 时钟选择:PCLK
// 发送模式:中断或查询
// 接收模式:中断或查询
UCON0 = (0 << 10) | (1 << 2) | (1 << 0);
// 5. 禁用FIFO
UFCON0 = 0;
// 6. 禁用流控
UMCON0 = 0;
// 7. 设置波特率
// 波特率 = PCLK / (16 * (UBRDIV + 1))
// UBRDIV = PCLK / (16 * BAUDRATE) - 1
unsigned int div = PCLK / (16 * BAUDRATE) - 1;
UBRDIV0 = div;
// 设置分频槽(用于更精确的波特率)
// 对于S3C2440,需要设置UDIVSLOT0
// 这里简化处理,实际应用中需要根据公式计算
UDIVSLOT0 = 0;
}
char uart0_getchar(void) {
// 等待接收缓冲区非空
while (!(UTRSTAT0 & (1 << 0)));
// 读取接收到的字符
return URXH0;
}
void uart0_putchar(char c) {
// 等待发送缓冲区为空
while (!(UTRSTAT0 & (1 << 2)));
// 发送字符
UTXH0 = c;
}
void uart0_puts(const char *str) {
while (*str) {
if (*str == '\n') {
uart0_putchar('\r');
}
uart0_putchar(*str++);
}
}
int uart0_printf(const char *format, ...) {
// 简单的printf实现(可扩展)
// 这里仅实现字符串输出
uart0_puts(format);
return 0;
}
UART测试程序
// uart_test.c
#include "s3c2440_soc.h"
extern void uart0_init(void);
extern char uart0_getchar(void);
extern void uart0_putchar(char c);
extern void uart0_puts(const char *str);
int main(void) {
char ch;
uart0_init();
uart0_puts("=== UART0 Test Program ===\n");
uart0_puts("Type characters, press 'q' to quit\n\n");
while(1) {
uart0_puts("Enter a character: ");
ch = uart0_getchar();
uart0_putchar('\n');
uart0_puts("You entered: ");
uart0_putchar(ch);
uart0_putchar('\n');
if (ch == 'q' || ch == 'Q') {
uart0_puts("Exiting...\n");
break;
}
}
return 0;
}
3.3 中断系统编程
ARM9的中断处理是嵌入式系统的核心技能。
中断控制器(VIC)配置
// 中断控制器寄存器(S3C2440)
#define SRCPND (*(volatile unsigned int *)0x4A000000)
#define INTMOD (*(volatile unsigned int *)0x4A000004)
#define INTMSK (*(volatile unsigned int *)0x4A000008)
#define INTPND (*(volatile unsigned int *)0x4A000010)
#define INTOFFSET (*(volatile unsigned int *)0x4A000014)
#define SUBSRCPND (*(volatile unsigned int *)0x4A000018)
#define INTSUBMSK (*(volatile unsigned int *)0x4A00001C)
// 中断号定义
#define INT_EINT0 0
#define INT_EINT1 1
#define INT_TIMER0 10
#define INT_UART0 28
中断服务程序框架
; 中断向量表
.text
.global _start
_start:
B reset_handler ; 复位
B undef_handler ; 未定义指令
B swi_handler ; 软件中断
B prefetch_handler ; 预取异常
B data_abort_handler ; 数据异常
B . ; 保留
B irq_handler ; IRQ中断
B fiq_handler ; FIQ中断
reset_handler:
; 初始化堆栈
LDR sp, =0x30001000
; 清除中断标志
LDR r0, =0x4A000008
LDR r1, =0xFFFFFFFF
STR r1, [r0] ; 屏蔽所有中断
; 初始化中断控制器
BL init_interrupts
; 跳转到main
BL main
B .
irq_handler:
; 保存上下文
SUB lr, lr, #4 ; 调整返回地址
STMFD sp!, {r0-r12, lr} ; 保存寄存器
; 调用C语言中断处理函数
BL irq_handler_c
; 恢复上下文
LDMFD sp!, {r0-r12, pc}^ ; 恢复并返回
fiq_handler:
; FIQ处理(快速中断)
B .
undef_handler:
; 未定义指令处理
B .
swi_handler:
; 软件中断处理
B .
prefetch_handler:
; 预取异常处理
B .
data_abort_handler:
; 数据异常处理
B .
C语言中断处理函数
// interrupt.c
#include "s3c2440_soc.h"
// 中断处理函数指针数组
void (*irq_handlers[32])(void) = {0};
void init_interrupts(void) {
// 屏蔽所有中断
INTMSK = 0xFFFFFFFF;
INTSUBMSK = 0x7FF;
// 清除所有中断标志
SRCPND = 0xFFFFFFFF;
INTPND = 0xFFFFFFFF;
SUBSRCPND = 0xFFFFFFFF;
// 设置IRQ模式栈指针
// 在IRQ模式下设置栈
__asm__ volatile(
"MOV r0, #0x12\n" // IRQ模式
"MSR cpsr, r0\n"
"LDR sp, =0x30002000\n" // IRQ栈顶
"MOV r0, #0x13\n" // 回到SVC模式
"MSR cpsr, r0\n"
);
}
void enable_irq(void) {
__asm__ volatile("CPSIE i"); // 使能IRQ中断
}
void disable_irq(void) {
__asm__ volatile("CPSID i"); // 禁用IRQ中断
}
void register_irq_handler(int irq_num, void (*handler)(void)) {
if (irq_num >= 0 && irq_num < 32) {
irq_handlers[irq_num] = handler;
}
}
void irq_handler_c(void) {
unsigned int irq_num = INTOFFSET;
if (irq_num < 32 && irq_handlers[irq_num]) {
irq_handlers[irq_num]();
}
// 清除中断标志
SRCPND = (1 << irq_num);
INTPND = (1 << irq_num);
}
// 外部中断示例
void eint0_init(void) {
// 配置GPF0为外部中断
GPFCON &= ~(3 << 0);
GPFCON |= (2 << 0); // 设置为EINT0
// 配置中断触发方式:下降沿触发
EXTINT0 &= ~(7 << 0);
EXTINT0 |= (2 << 0); // 双边沿触发
// 使能EINT0
INTMSK &= ~(1 << INT_EINT0);
// 注册中断处理函数
register_irq_handler(INT_EINT0, eint0_handler);
}
void eint0_handler(void) {
// 处理按键按下事件
// 清除EINT0中断标志
SRCPND = (1 << INT_EINT0);
INTPND = (1 << INT_EINT0);
// 执行按键处理逻辑
// 例如:切换LED状态
}
第四部分:嵌入式Linux在ARM9上的移植
4.1 Bootloader移植(U-Boot)
U-Boot是嵌入式系统中最常用的Bootloader。
U-Boot启动流程分析
Stage 1 (汇编代码):
1. CPU初始化(时钟、内存控制器)
2. 堆栈设置
3. BSS段清零
4. 跳转到Stage 2
Stage 2 (C代码):
1. 板级初始化
2. 内存映射
3. 设备初始化(串口、网卡、Flash)
4. 命令行接口
5. 内核加载和启动
U-Boot配置和编译
# 下载U-Boot源码
wget https://ftp.denx.de/pub/u-boot/u-boot-2023.10.tar.bz2
tar -xf u-boot-2023.10.tar.bz2
cd u-boot-2023.10
# 配置S3C2440开发板
make mini2440_config
# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
# 生成的文件:
# u-boot.bin - 可烧写的二进制文件
# u-boot.elf - 可调试的ELF文件
U-Boot命令使用示例
# 设置环境变量
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.1
setenv netmask 255.255.255.0
# 通过TFTP下载内核
tftp 0x30008000 uImage
# 烧写到NAND Flash
nand write 0x30008000 0x50000 0x500000
# 设置启动参数
setenv bootargs console=ttySAC0,115200 root=/dev/mtdblock2 rootfstype=jffs2
# 启动内核
bootm 0x30008000
4.2 Linux内核移植
内核配置
# 下载Linux内核
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.5.tar.xz
tar -xf linux-6.5.tar.xz
cd linux-6.5
# 配置ARM9默认配置
make ARCH=arm mini2440_defconfig
# 图形化配置
make ARCH=arm menuconfig
# 关键配置选项:
# System Type -> S3C2410 Machines -> S3C2440 Machines
# Device Drivers -> Network device support -> DM9000 support
# Device Drivers -> MTD support -> NAND Flash support
内核编译
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage
# 编译设备树(如果使用)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- dtbs
# 生成的文件:
# arch/arm/boot/zImage - 内核镜像
# arch/arm/boot/dts/s3c2440-mini2440.dtb - 设备树二进制
内核启动参数配置
// 在U-Boot中设置的启动参数示例
// console=ttySAC0,115200 root=/dev/mtdblock2 rootfstype=jffs2 init=/sbin/init
// 参数说明:
// console=ttySAC0,115200 - 串口0,波特率115200
// root=/dev/mtdblock2 - 根文件系统在MTD块设备2
// rootfstype=jffs2 - 根文件系统类型为JFFS2
// init=/sbin/init - 第一个用户进程
4.3 根文件系统构建
使用BusyBox构建基础系统
# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
# 设置静态链接
# Settings -> Build static binary (no shared libs)
# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
# 安装到根文件系统目录
mkdir -p ../rootfs
cp -r _install/* ../rootfs/
cd ../rootfs
# 创建必要的目录
mkdir -p dev etc lib proc sys tmp var/run
# 创建设备节点
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
sudo mknod dev/ttySAC0 c 204 64
# 创建inittab
cat > etc/inittab << EOF
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF
# 创建rcS脚本
mkdir -p etc/init.d
cat > etc/init.d/rcS << EOF
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Welcome to ARM9 Linux!"
/bin/sh
EOF
chmod +x etc/init.d/rcS
制作根文件系统镜像
# 制作JFFS2镜像(适用于NAND Flash)
mkfs.jffs2 -r rootfs -o rootfs.jffs2 -e 0x20000 --pad=0x800000
# 参数说明:
# -r rootfs: 源目录
# -o rootfs.jffs2: 输出文件
# -e 0x20000: 擦除块大小(128KB)
# --pad=0x800000: 填充到8MB
# 制作UBIFS镜像(更现代的NAND文件系统)
# 需要先安装mkfs.ubifs和ubinize工具
mkfs.ubifs -r rootfs -m 2048 -e 126976 -c 8192 -o rootfs.ubifs
# 创建UBI配置文件
cat > ubinize.cfg << EOF
[ubifs]
mode=ubi
image=rootfs.ubifs
vol_id=0
vol_size=100MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize
EOF
ubinize -o rootfs.ubi -m 2048 -p 128KiB ubinize.cfg
第五部分:嵌入式系统调试技巧
5.1 JTAG调试
OpenOCD配置
# 安装OpenOCD
sudo apt-get install openocd
# 创建OpenOCD配置文件(openocd.cfg)
cat > openocd.cfg << EOF
interface jlink
transport select jtag
adapter speed 1000
# S3C2440目标芯片
source [find target/s3c2440.cfg]
# 启动GDB服务
gdb_port 3333
telnet_port 4444
EOF
# 启动OpenOCD
openocd -f openocd.cfg
GDB调试
# 启动GDB
arm-linux-gnueabi-gdb your_program.elf
# 连接到OpenOCD
(gdb) target remote localhost:3333
# 常用GDB命令
(gdb) break main # 在main函数设置断点
(gdb) break *0x30008000 # 在地址0x30008000设置断点
(gdb) continue # 继续执行
(gdb) stepi # 单步执行(汇编级别)
(gdb) nexti # 单步执行(跳过函数调用)
(gdb) info registers # 查看所有寄存器
(gdb) print $r0 # 查看特定寄存器
(gdb) x/10xw 0x30000000 # 查看内存(10个字,十六进制)
(gdb) set $r0 = 100 # 修改寄存器
(gdb) monitor reset halt # 通过OpenOCD复位芯片
5.2 串口调试
调试信息输出
// debug.h
#ifndef DEBUG_H
#define DEBUG_H
#include "uart.h"
#define DEBUG_LEVEL 1
#if DEBUG_LEVEL >= 1
#define DBG(fmt, ...) uart0_printf("[DBG] " fmt "\n", ##__VA_ARGS__)
#else
#define DBG(fmt, ...)
#endif
#define INFO(fmt, ...) uart0_printf("[INFO] " fmt "\n", ##__VA_ARGS__)
#define ERROR(fmt, ...) uart0_printf("[ERROR] " fmt "\n", ##__VA_ARGS__)
// 断言宏
#define ASSERT(cond) \
if (!(cond)) { \
uart0_printf("ASSERT FAILED: %s:%d\n", __FILE__, __LINE__); \
while(1); \
}
#endif
调试示例
// 调试GPIO初始化
void led_init_debug(void) {
DBG("Initializing GPIO registers...");
unsigned int con_reg = GPBCON;
DBG("GPBCON before: 0x%08X", con_reg);
// 配置GPIO
GPBCON &= ~((3 << 14) | (3 << 16) | (3 << 18) | (3 << 20));
GPBCON |= ((1 << 14) | (1 << 16) | (1 << 18) | (1 << 20));
DBG("GPBCON after: 0x%08X", GPBCON);
// 验证配置
unsigned int expected = (1 << 14) | (1 << 16) | (1 << 18) | (1 << 20);
unsigned int actual = GPBCON & ((3 << 14) | (3 << 16) | (3 << 18) | (3 << 20));
if (actual == expected) {
INFO("GPIO configuration successful");
} else {
ERROR("GPIO configuration failed! Expected: 0x%08X, Actual: 0x%08X", expected, actual);
}
}
5.3 内存调试
内存泄漏检测
// 简单的内存调试工具
#include <stdlib.h>
#include <stdio.h>
#define DEBUG_MEMORY 1
#ifdef DEBUG_MEMORY
typedef struct mem_block {
void *ptr;
size_t size;
const char *file;
int line;
struct mem_block *next;
} mem_block_t;
static mem_block_t *mem_list = NULL;
static size_t total_allocated = 0;
void *debug_malloc(size_t size, const char *file, int line) {
void *ptr = malloc(size);
if (ptr) {
mem_block_t *block = malloc(sizeof(mem_block_t));
block->ptr = ptr;
block->size = size;
block->file = file;
block->line = line;
block->next = mem_list;
mem_list = block;
total_allocated += size;
printf("MALLOC: %p (%zu bytes) at %s:%d\n", ptr, size, file, line);
}
return ptr;
}
void debug_free(void *ptr, const char *file, int line) {
mem_block_t **curr = &mem_list;
while (*curr) {
if ((*curr)->ptr == ptr) {
mem_block_t *block = *curr;
*curr = block->next;
total_allocated -= block->size;
printf("FREE: %p at %s:%d\n", ptr, file, line);
free(block);
break;
}
curr = &(*curr)->next;
}
free(ptr);
}
void check_memory_leaks(void) {
if (mem_list) {
printf("\n=== MEMORY LEAK DETECTED ===\n");
mem_block_t *curr = mem_list;
while (curr) {
printf("Leak: %p (%zu bytes) at %s:%d\n",
curr->ptr, curr->size, curr->file, curr->line);
curr = curr->next;
}
printf("Total leaked: %zu bytes\n", total_allocated);
} else {
printf("No memory leaks detected.\n");
}
}
#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr, __FILE__, __LINE__)
#endif
第六部分:实战项目开发
6.1 项目1:温度监控系统
硬件设计
DS18B20温度传感器 -> GPIO(单总线协议)
OLED显示屏 -> I2C接口
蜂鸣器 -> GPIO
按键 -> GPIO外部中断
软件架构
// main.c - 温度监控系统
#include "s3c2440_soc.h"
#include "ds18b20.h"
#include "oled.h"
#include "buzzer.h"
#include "key.h"
#define TEMP_THRESHOLD_HIGH 30.0
#define TEMP_THRESHOLD_LOW 10.0
typedef enum {
STATE_NORMAL,
STATE_ALARM_HIGH,
STATE_ALARM_LOW
} system_state_t;
system_state_t current_state = STATE_NORMAL;
void system_init(void) {
uart0_init();
ds18b20_init();
oled_init();
buzzer_init();
key_init();
INFO("Temperature Monitor System Initialized");
}
void update_display(float temp) {
char buffer[32];
oled_clear();
oled_show_string(0, 0, "Temperature Monitor");
snprintf(buffer, sizeof(buffer), "Temp: %.1f C", temp);
oled_show_string(0, 2, buffer);
switch(current_state) {
case STATE_NORMAL:
oled_show_string(0, 4, "Status: Normal");
break;
case STATE_ALARM_HIGH:
oled_show_string(0, 4, "Status: HIGH!");
break;
case STATE_ALARM_LOW:
oled_show_string(0, 4, "Status: LOW!");
break;
}
}
void handle_temperature(float temp) {
if (temp > TEMP_THRESHOLD_HIGH) {
if (current_state != STATE_ALARM_HIGH) {
current_state = STATE_ALARM_HIGH;
buzzer_on();
INFO("Temperature too high: %.1f C", temp);
}
} else if (temp < TEMP_THRESHOLD_LOW) {
if (current_state != STATE_ALARM_LOW) {
current_state = STATE_ALARM_LOW;
buzzer_on();
INFO("Temperature too low: %.1f C", temp);
}
} else {
if (current_state != STATE_NORMAL) {
current_state = STATE_NORMAL;
buzzer_off();
INFO("Temperature normal: %.1f C", temp);
}
}
}
void key_event_handler(int key_id) {
switch(key_id) {
case KEY1:
// 切换显示模式
break;
case KEY2:
// 调整阈值
break;
case KEY3:
// 确认/静音
buzzer_off();
break;
}
}
int main(void) {
float temperature;
system_init();
while(1) {
// 读取温度
if (ds18b20_read_temp(&temperature)) {
update_display(temperature);
handle_temperature(temperature);
} else {
ERROR("Failed to read temperature");
}
// 检查按键
int key = key_scan();
if (key >= 0) {
key_event_handler(key);
}
// 延时1秒
delay_ms(1000);
}
return 0;
}
DS18B20驱动实现
// ds18b20.c - 单总线协议实现
#include "s3c2440_soc.h"
#define DS18B20_PIN (1 << 5) // 假设连接到GPF5
#define DS18B20_DIR GPFCON
#define DS18B20_DATA GPFDAT
// 单总线时序要求(微秒级延时)
void delay_us(unsigned int us) {
volatile unsigned int i;
for (i = 0; i < us * 10; i++) {
__asm__ volatile("nop");
}
}
// 设置引脚为输出模式
void ds18b20_set_output(void) {
DS18B20_DIR &= ~(3 << 10); // 清除GPF5配置位
DS18B20_DIR |= (1 << 10); // 设置为输出
}
// 设置引脚为输入模式
void ds18b20_set_input(void) {
DS18B20_DIR &= ~(3 << 10); // 清除GPF5配置位
// 默认为输入(00)
}
// 复位脉冲
int ds18b20_reset(void) {
ds18b20_set_output();
DS18B20_DATA &= ~DS18B20_PIN; // 拉低总线
delay_us(480); // 保持480us
ds18b20_set_input();
delay_us(60); // 等待60us
// 检测存在脉冲
if (DS18B20_DATA & DS18B20_PIN) {
return 0; // 无设备响应
}
delay_us(420); // 等待剩余时间
return 1; // 设备存在
}
// 写一位
void ds18b20_write_bit(int bit) {
ds18b20_set_output();
if (bit) {
// 写1:拉低1-15us,然后释放
DS18B20_DATA &= ~DS18B20_PIN;
delay_us(1);
DS18B20_DATA |= DS18B20_PIN;
delay_us(60);
} else {
// 写0:拉低60-120us
DS18B20_DATA &= ~DS18B20_PIN;
delay_us(60);
DS18B20_DATA |= DS18B20_PIN;
delay_us(1);
}
}
// 读一位
int ds18b20_read_bit(void) {
int bit;
ds18b20_set_output();
DS18B20_DATA &= ~DS18B20_PIN; // 拉低1us
delay_us(1);
ds18b20_set_input();
delay_us(15); // 等待15us
bit = (DS18B20_DATA & DS18B20_PIN) ? 1 : 0;
delay_us(45); // 等待剩余时间
return bit;
}
// 写字节
void ds18b20_write_byte(unsigned char byte) {
for (int i = 0; i < 8; i++) {
ds18b20_write_bit(byte & 0x01);
byte >>= 1;
}
}
// 读字节
unsigned char ds18b20_read_byte(void) {
unsigned char byte = 0;
for (int i = 0; i < 8; i++) {
if (ds18b20_read_bit()) {
byte |= (1 << i);
}
}
return byte;
}
// 读取温度
int ds18b20_read_temp(float *temp) {
unsigned char temp_l, temp_h;
int temp_raw;
if (!ds18b20_reset()) {
return 0; // 设备不存在
}
ds18b20_write_byte(0xCC); // 跳过ROM
ds18b20_write_byte(0x44); // 转换温度
delay_ms(750); // 等待转换完成(最大750ms)
if (!ds18b20_reset()) {
return 0;
}
ds18b20_write_byte(0xCC); // 跳过ROM
ds18b20_write_byte(0xBE); // 读暂存器
temp_l = ds18b20_read_byte();
temp_h = ds18b20_read_byte();
// 组合温度值
temp_raw = (temp_h << 8) | temp_l;
// 转换为摄氏度(0.0625°C/LSB)
*temp = temp_raw * 0.0625;
return 1;
}
6.2 项目2:简易数字示波器
硬件设计
ADC输入 -> ADC通道0(0x58000000)
触发信号 -> GPIO外部中断
显示输出 -> UART或LCD
ADC驱动
// adc.c - S3C2440 ADC驱动
#include "s3c2440_soc.h"
void adc_init(void) {
// 配置ADC引脚(GPF0作为模拟输入)
GPFCON &= ~(3 << 0); // 设置为模拟输入
// 设置ADC控制寄存器
// PRSCEN=1, PRSCVL=49 (分频系数50)
ADCCON = (1 << 14) | (49 << 6);
// 设置ADC时钟寄存器
// ADCCLK = PCLK / (49+1) / 2 = 50MHz / 50 / 2 = 500kHz
ADCDLY = 50000; // 启动延时
}
unsigned short adc_read(int channel) {
// 设置通道号和启动转换
ADCCON = (ADCCON & ~0x7) | (channel & 0x7) | (1 << 0);
// 等待转换完成
while (!(ADCCON & (1 << 15)));
// 读取转换结果(10位)
return ADCDAT0 & 0x3FF;
}
// 连续采样模式
void adc_start_dma_mode(int channel, unsigned short *buffer, int count) {
// 配置DMA(简化示例)
// 实际项目中需要配置DMA控制器
// 这里仅演示单次采样循环
for (int i = 0; i < count; i++) {
buffer[i] = adc_read(channel);
delay_us(100); // 10kHz采样率
}
}
示波器核心算法
// oscilloscope.c
#include "s3c2440_soc.h"
#include "adc.h"
#include "uart.h"
#define SAMPLE_COUNT 256
#define TRIGGER_LEVEL 512 // 50% of 1024
unsigned short adc_buffer[SAMPLE_COUNT];
// 简单的触发检测
int find_trigger_point(unsigned short *data, int len) {
for (int i = 1; i < len - 1; i++) {
// 上升沿触发
if (data[i-1] < TRIGGER_LEVEL && data[i] >= TRIGGER_LEVEL) {
return i;
}
}
return -1; // 未找到触发点
}
// 波形显示(通过UART发送ASCII艺术)
void display_waveform(unsigned short *data, int len, int trigger_pos) {
const char *levels = " .:-=+*#%@";
int max_level = 10;
uart0_puts("\n=== Waveform ===\n");
// 显示触发点
for (int i = 0; i < len; i++) {
int idx = (trigger_pos + i) % len;
int value = data[idx];
int level = (value * max_level) / 1024;
if (level < 0) level = 0;
if (level >= max_level) level = max_level - 1;
// 标记触发点
if (i == 0) {
uart0_putchar('>');
} else {
uart0_putchar(levels[level]);
}
// 每16个点换行
if ((i + 1) % 16 == 0) {
uart0_putchar('\n');
}
}
}
// 频率测量
float measure_frequency(unsigned short *data, int len, int sample_rate) {
int zero_crossings = 0;
for (int i = 1; i < len; i++) {
if ((data[i-1] < TRIGGER_LEVEL && data[i] >= TRIGGER_LEVEL) ||
(data[i-1] >= TRIGGER_LEVEL && data[i] < TRIGGER_LEVEL)) {
zero_crossings++;
}
}
// 频率 = (过零次数 / 2) / (采样点数 / 采样率)
return (zero_crossings / 2.0) * (sample_rate / (float)len);
}
int main(void) {
uart0_init();
adc_init();
uart0_puts("Simple Oscilloscope Started\n");
while(1) {
// 采集数据
adc_start_dma_mode(0, adc_buffer, SAMPLE_COUNT);
// 查找触发点
int trigger = find_trigger_point(adc_buffer, SAMPLE_COUNT);
if (trigger >= 0) {
// 显示波形
display_waveform(adc_buffer, SAMPLE_COUNT, trigger);
// 测量频率(假设采样率10kHz)
float freq = measure_frequency(adc_buffer, SAMPLE_COUNT, 10000);
uart0_printf("\nFrequency: %.2f Hz\n", freq);
// 计算峰峰值
unsigned short max_val = 0, min_val = 1023;
for (int i = 0; i < SAMPLE_COUNT; i++) {
if (adc_buffer[i] > max_val) max_val = adc_buffer[i];
if (adc_buffer[i] < min_val) min_val = adc_buffer[i];
}
float vpp = (max_val - min_val) * 3.3 / 1024.0;
uart0_printf("Vpp: %.2f V\n\n", vpp);
} else {
uart0_puts("No trigger found\n");
}
delay_ms(1000);
}
return 0;
}
第七部分:高级主题与优化技巧
7.1 性能优化
代码优化策略
// 优化前:效率低的LED控制
void delay_loop(unsigned int count) {
volatile unsigned int i;
for (i = 0; i < count; i++); // 空循环,浪费CPU周期
}
// 优化后:使用ARM9的协处理器
void delay_optimized(unsigned int ms) {
unsigned int cycles_per_ms = 200000; // 根据主频调整
__asm__ volatile(
"1:\n"
"SUBS %0, %0, #1\n" // 减1并设置标志
"BNE 1b\n" // 不为零则跳转
: "+r"(cycles_per_ms)
);
}
// 使用DMA进行数据传输(不占用CPU)
void dma_transfer(void *src, void *dst, unsigned int size) {
// 配置DMA通道
DMA_SRC = (unsigned int)src;
DMA_DST = (unsigned int)dst;
DMA_SIZE = size;
DMA_CON = (1 << 0); // 启动DMA
// 等待完成
while (DMA_CON & (1 << 0));
}
内存访问优化
// 不好的做法:频繁的内存访问
void process_data_bad(unsigned char *data, int len) {
for (int i = 0; i < len; i++) {
data[i] = data[i] * 2; // 每次都要访问内存
}
}
// 好的做法:使用寄存器缓存
void process_data_good(unsigned char *data, int len) {
int i;
unsigned char temp;
for (i = 0; i < len; i += 4) {
// 一次加载多个数据到寄存器
__asm__ volatile(
"LDMIA %0, {%1-%4}\n"
"LSL %1, %1, #1\n"
"LSL %2, %2, #1\n"
"LSL %3, %3, #1\n"
"LSL %4, %4, #1\n"
"STMIA %0, {%1-%4}\n"
: "+r"(data), "+r"(temp)
:
: "r2", "r3", "r4"
);
data += 4;
}
}
7.2 低功耗设计
时钟管理
// 降低CPU主频
void set_cpu_frequency(int mhz) {
// S3C2440时钟控制
// MPLL = (m + p) * Fin / (2 * p * 2^s)
// 简化配置:假设Fin=12MHz
int m, p, s;
if (mhz == 400) {
m = 100; p = 3; s = 1; // 400MHz
} else if (mhz == 200) {
m = 68; p = 1; s = 1; // 200MHz
} else {
m = 50; p = 1; s = 1; // 100MHz
}
// 设置MPLLCON
MPLLCON = (m << 12) | (p << 4) | s;
// 等待时钟稳定
delay_ms(10);
}
// 进入空闲模式
void enter_idle_mode(void) {
// 设置电源管理
PWRCFG &= ~(1 << 2); // 禁用睡眠模式
// 进入空闲模式(仅停止CPU,外设继续运行)
__asm__ volatile(
"MOV r0, #0\n"
"MCR p15, 0, r0, c7, c0, 4\n" // 等待中断
);
}
外设电源管理
// 关闭未使用外设的时钟
void power_down_unused_peripherals(void) {
// 关闭未使用的模块时钟
// CLKCON寄存器位定义:
// bit 0: PDCON (ADC)
// bit 1: UART0
// bit 2: UART1
// bit 3: UART2
// bit 4: PWM
// bit 5: USB
// bit 6: IIC
// bit 7: SPI
// bit 8: RTC
// bit 9: ADC
// bit 10: IIS
// bit 11: GPIO
// bit 12: UART0 (again)
// bit 13: NAND Flash Controller
unsigned int clkcon = 0;
// 只开启需要的模块
clkcon |= (1 << 1); // UART0
clkcon |= (1 << 11); // GPIO
clkcon |= (1 << 13); // NAND
CLKCON = clkcon;
}
7.3 安全与可靠性
看门狗定时器
// 看门狗配置
void watchdog_init(unsigned int timeout_ms) {
// 看门狗时钟 = PCLK / 128 / 2^prescaler
// 假设PCLK=50MHz,目标1秒超时
unsigned int prescaler = 255;
unsigned int count = (50000000 / (128 * (prescaler + 1))) * timeout_ms / 1000;
// 设置预分频器和除数
WTCON = (prescaler << 8) | (3 << 3) | (1 << 2) | (1 << 0);
// bit 0: 使能看门狗
// bit 2: 产生复位
// bit 3: 产生中断
// bit 8-15: 预分频器
WTDAT = count; // 设置超时值
WTCNT = count; // 当前计数值
// 重新加载计数器
WTCON |= (1 << 5); // 看门狗使能
}
// 喂狗
void watchdog_feed(void) {
WTCNT = WTDAT; // 重置计数器
}
错误处理与恢复
// 错误处理框架
typedef enum {
ERR_OK = 0,
ERR_TIMEOUT,
ERR_INVALID_PARAM,
ERR_HARDWARE_FAULT,
ERR_NO_MEMORY
} error_code_t;
// 错误日志记录
void log_error(error_code_t err, const char *func, int line) {
const char *err_str[] = {
"OK",
"Timeout",
"Invalid Parameter",
"Hardware Fault",
"No Memory"
};
uart0_printf("[ERROR] %s:%d: %s\n", func, line, err_str[err]);
}
// 带错误处理的函数
error_code_t read_sensor_safe(float *value) {
int retry = 3;
while (retry--) {
if (ds18b20_read_temp(value)) {
return ERR_OK;
}
delay_ms(100);
}
log_error(ERR_HARDWARE_FAULT, __func__, __LINE__);
return ERR_HARDWARE_FAULT;
}
// 系统恢复机制
void system_recovery(void) {
// 1. 保存关键数据
save_critical_data();
// 2. 重置硬件
hardware_reset();
// 3. 重新初始化
system_init();
// 4. 恢复数据
restore_critical_data();
INFO("System recovered");
}
第八部分:学习资源与进阶路径
8.1 推荐书籍与文档
经典教材:
- 《ARM体系结构与编程》杜春雷
- 《嵌入式Linux应用开发完全手册》韦东山
- 《深入理解计算机系统》Randal E. Bryant
官方文档:
- ARM Architecture Reference Manual
- S3C2440 User Manual
- U-Boot Documentation
- Linux Kernel Documentation
8.2 在线资源
开发板社区:
- 友善之臂论坛:www.arm9.net
- 天嵌科技论坛:www.tqmbbs.com
- OpenRISC社区
开源项目:
- Linux内核源码:www.kernel.org
- U-Boot源码:www.denx.de
- BusyBox:www.busybox.net
8.3 实践项目建议
初级项目:
- LED流水灯控制
- 串口回显程序
- 简单的按键检测
中级项目:
- 温湿度监控系统
- 简易计算器
- 文件系统操作
高级项目:
- 网络摄像头
- 智能家居控制器
- 实时数据采集系统
总结
ARM9作为嵌入式系统学习的经典平台,提供了从硬件底层到操作系统层面的完整学习路径。通过本指南的学习,你应该能够:
- 理解ARM9架构:掌握处理器核心概念、指令集和工作模式
- 搭建开发环境:配置交叉编译工具链和调试环境
- 编写汇编代码:理解底层硬件控制和启动流程
- 硬件接口编程:熟练使用GPIO、UART、中断等外设
- 系统移植:完成U-Boot、Linux内核和根文件系统的移植
- 调试技巧:掌握JTAG、串口和内存调试方法
- 项目开发:独立完成嵌入式系统项目
学习嵌入式系统是一个循序渐进的过程,建议按照以下步骤进行:
第一阶段(1-2个月):掌握基础概念,搭建环境,编写简单的汇编和C程序 第二阶段(2-3个月):深入学习硬件接口编程,完成基础外设驱动 第三阶段(3-4个月):学习系统移植,理解Bootloader和操作系统原理 第四阶段(持续):进行项目实践,积累实战经验
记住,实践是最好的老师。不要只停留在理论学习,要多动手编写代码、调试程序、解决问题。每个错误都是学习的机会,每次成功都会增强你的信心。
最后,保持对新技术的关注,嵌入式领域发展迅速,ARM Cortex-M、RISC-V等新架构不断涌现,但ARM9所代表的底层硬件理解和系统级开发思维是永恒的。
祝你在嵌入式系统开发的道路上取得成功!
