Mini2440 流水灯实验(wsl+docker)
本文详细介绍了在Mini2440开发板上实现LED裸机开发的完整流程。项目使用Docker封装交叉编译环境,包含启动代码(startup.S)完成硬件初始化、主程序(main.c)控制LED流水灯效果。关键技术点包括:1) 通过启动代码完成看门狗关闭、时钟设置、内存控制器初始化等硬件配置;2) 实现代码从SRAM/NOR到SDRAM的搬运;3) 使用Makefile自动化构建系统;4) 通过dnw
#配置环境及烧录部分,请看之前的专栏文章:Windows + WSL2 (Ubuntu) -Mini2440 开发环境操作手册
本文详细记录了在 Mini2440 开发板上进行 LED 裸机开发的完整流程,把代码在nor flash模式启动并烧录,之后用nand flash启动实现持久化。这也是基于mini2440上的RTOS的先导内容,本篇包括基于 Docker 的环境搭建、代码编写、编译以及烧录步骤。
1. 项目简介
本项目实现了一个简单的裸机程序,具备以下功能:
-
启动代码 (
startup.S): 完成硬件初始化(看门狗、中断屏蔽、时钟设置、内存控制器初始化、堆栈设置),并将代码从 SRAM/NOR 搬运到 SDRAM (0x30000000) 运行。 -
主程序 (
main.c): 控制 GPB5-8 引脚,实现 LED 流水灯效果。 -
构建系统 (
Makefile): 自动化编译、链接、生成二进制文件及反汇编分析文件。
2. 环境搭建 (Docker)
为了避免繁琐的交叉编译工具链配置,本项目使用 Docker 封装了完整的编译环境。
2.1 文件结构
按照文件新建目录,使用命令mkdir新建文件夹,nano新建文件,当然你直接在当前目录输入code .打开vscode编辑也更方便。
mini2440_led/
├── Dockerfile # Docker 镜像定义
├── run_docker.sh # 启动脚本 (宿主机使用)
├── Makefile # 构建脚本
├── link.ld # 链接脚本
├── dnw # USB 下载工具 (Linux版)
├── inc/ # 头文件目录
│ ├── 2440addr.h
│ ├── 2440addr.inc
│ └── memcfg.inc
└── src/ # 源码目录
├── startup.S # 启动汇编
└── main.c # C 主程序
2.2 dnw下载器
/* dnw2 linux main file. This depends on libusb.
*
* Author: Fox <hulifox008@163.com>
* License: GPL
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <usb.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define QQ2440_SECBULK_IDVENDOR 0x5345
#define QQ2440_SECBULK_IDPRODUCT 0x1234
struct usb_dev_handle * open_port()
{
struct usb_bus *busses, *bus;
usb_init();
usb_find_busses();
usb_find_devices();
busses = usb_get_busses();
for(bus=busses;bus;bus=bus->next)
{
struct usb_device *dev;
for(dev=bus->devices;dev;dev=dev->next)
{
if( QQ2440_SECBULK_IDVENDOR==dev->descriptor.idVendor
&& QQ2440_SECBULK_IDPRODUCT==dev->descriptor.idProduct)
{
printf("Target usb device found!\n");
struct usb_dev_handle *hdev = usb_open(dev);
if(!hdev)
{
perror("Cannot open device");
}
else
{
if(0!=usb_claim_interface(hdev, 0))
{
perror("Cannot claim interface");
usb_close(hdev);
hdev = NULL;
}
}
return hdev;
}
}
}
printf("Target usb device not found!\n");
return NULL;
}
void usage()
{
printf("Usage: dnw2 <file>\n\n");
}
unsigned char* prepare_write_buf(char *filename, unsigned int *len)
{
unsigned char *write_buf = NULL;
struct stat fs;
int fd = open(filename, O_RDONLY);
if(-1==fd)
{
perror("Cannot open file");
return NULL;
}
if(-1==fstat(fd, &fs))
{
perror("Cannot get file size");
goto error;
}
write_buf = (unsigned char*)malloc(fs.st_size+10);
if(NULL==write_buf)
{
perror("malloc failed");
goto error;
}
if(fs.st_size != read(fd, write_buf+8, fs.st_size))
{
perror("Reading file failed");
goto error;
}
printf("Filename : %s\n", filename);
printf("Filesize : %ld bytes\n", (long)fs.st_size);
*((uint32_t*)write_buf) = 0x30000000; //download address
*((uint32_t*)write_buf+1) = fs.st_size + 10; //download size;
*len = fs.st_size + 10;
close(fd);
return write_buf;
error:
if(fd!=-1) close(fd);
if(NULL!=write_buf) free(write_buf);
fs.st_size = 0;
return NULL;
}
int main(int argc, char *argv[])
{
if(2!=argc)
{
usage();
return 1;
}
struct usb_dev_handle *hdev = open_port();
if(!hdev)
{
return 1;
}
unsigned int len = 0;
unsigned char* write_buf = prepare_write_buf(argv[1], &len);
if(NULL==write_buf) return 1;
unsigned int remain = len;
unsigned int towrite;
printf("Writing data ...\n");
while(remain)
{
towrite = remain>512 ? 512 : remain;
if(towrite != usb_bulk_write(hdev, 0x03, write_buf+(len-remain), towrite, 3000))
{
perror("usb_bulk_write failed");
break;
}
remain-=towrite;
printf("\r%d%%\t %u bytes ", (len-remain)*100/len, len-remain);
fflush(stdout);
}
if(0==remain) printf("Done!\n");
return 0;
}
编译dnw
gcc dnw.c -o dnw -lusb
放入环境变量就不用每次把dnw文件到处复制了
sudo cp ~/mini2440_led/dnw /usr/local/bin/
2.3Docker 环境配置
Dockerfile:
# 使用 Ubuntu 22.04 作为基础镜像
FROM ubuntu:22.04
# 避免安装过程中的交互式提示
ENV DEBIAN_FRONTEND=noninteractive
# 更换为阿里云镜像源以解决连接失败问题
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
# 1. 更新软件源
# 2. 安装 make (构建工具)
# 3. 安装 gcc-arm-none-eabi (ARM 裸机交叉编译工具链)
# 4. 安装 libusb-0.1-4 (dnw 依赖)
# 5. 清理缓存以减小镜像体积
RUN apt-get update && apt-get install -y \
make \
gcc-arm-none-eabi \
binutils-arm-none-eabi \
libusb-0.1-4 \
&& rm -rf /var/lib/apt/lists/*
# 复制 dnw 工具到容器中
COPY dnw /usr/local/bin/dnw
RUN chmod +x /usr/local/bin/dnw
# 设置容器内的工作目录
WORKDIR /app
# 默认命令:当运行容器且未指定命令时,默认执行 make
CMD ["make"]
启动脚本 (run_docker.sh):
#!/bin/bash
# 镜像名称
IMAGE_NAME="mini2440-env"
# 检查镜像是否存在,不存在则构建
if [[ "$(docker images -q $IMAGE_NAME 2> /dev/null)" == "" ]]; then
echo "镜像 $IMAGE_NAME 不存在,正在构建..."
docker build -t $IMAGE_NAME .
fi
# 运行容器
# --rm: 退出后删除容器
# --privileged: 赋予特权 (用于 dnw 访问 USB)
# -v "$PWD":/app: 挂载当前目录
# "$@": 传递给脚本的所有参数 (例如 make, make clean, dnw xxx)
docker run --rm --privileged -v "$PWD":/app $IMAGE_NAME "$@"
3. 核心代码说明
3.1 链接脚本 (link.ld)
指定程序的链接地址为 0x30000000 (SDRAM),这是因为我们在启动代码中会将程序搬运到这里执行。
ENTRY(_start)
SECTIONS
{
/*
* 设置链接地址
* 如果是在 SDRAM 中调试运行,0x30000000
* 如果是烧录到 NOR Flash 运行,0x00000000
*/
. = 0x30000000;
.text : {
*startup.o (.text) /* 确保 startup 代码在最前面 */
*(.text)
*(.rodata)
}
. = ALIGN(4);
_etext = .;
.data : {
_data = .;
*(.data)
}
. = ALIGN(4);
_bss_start_ = .;
.bss : {
*(.bss)
*(COMMON)
}
_bss_end_ = .;
}
3.2 启动代码 (src/startup.S)
这是程序最关键的部分,负责将系统从上电状态配置到可以运行 C 代码的状态。
关键步骤摘要:
-
关看门狗 & 屏蔽中断: 防止启动过程中被干扰。
-
设置时钟: 配置
CLKDIVN和MPLLCON,使系统运行在 400MHz (FCLK)。-
注意: 必须先设置
CLKDIVN和异步总线模式,最后设置MPLL,否则可能死机。
-
-
初始化内存控制器: 配置 13 个寄存器以启用 SDRAM。
-
技巧: 使用位置无关代码 (PIC) 计算
SMRDATA的物理地址,确保无论在 SRAM (0x0) 还是 SDRAM 都能正确读取配置数据。
-
-
代码搬运: 将代码从
0x0(SRAM/NOR) 复制到0x30000000(SDRAM)。 -
跳转: 使用
ldr pc, =on_sdram跳转到 SDRAM 中的代码继续执行。
// startup_gnu.S
// 适配 GNU Assembler (GAS) / GCC 工具链 (WSL2/Linux 环境)
// 全局变量以及头文件
.equ BUSWIDTH, 32
.include "../inc/2440addr.inc"
.include "../inc/memcfg.inc"
// 外部符号声明
.extern my_MainInterrupt
.extern my_MainLoop
.extern main
// 需要在链接脚本 (.ld) 中定义这些符号
.extern _start // 代码段起始(ROM)
.extern _etext // 代码段结束
.extern _data // 数据段起始
.extern _bss_start_ // BSS段起始 (RAM,data与bss起始紧挨着,所以不用定义)
.extern _bss_end_ // BSS段结束
.global _start // 链接器默认入口
.global my_entry // 保持原名
// 处理器7种模式定义
.equ USERMODE, 0x10
.equ FIQMODE, 0x11
.equ IRQMODE, 0x12
.equ SVCMODE, 0x13
.equ ABORTMODE, 0x17
.equ UNDEFMODE, 0x1b
.equ MODEMASK, 0x1f
// 异常向量表
.section .text
.code 32 // ARM 模式 (16是thumb模式,即ARMV4T的T,完全不需要考虑T,只用ARMV4即可)
.align 2 // 4字节对齐
_start: // 标准入口标签
my_entry:
b reset // 0x00
b undef // 0x04
b swi // 0x08
b pabort // 0x0C
b dabort // 0x10
b . // 0x14 保留
b irq // 0x18
b fiq // 0x1C
// 异常处理函数
//undef LED1亮
undef:
ldr r0, =GPBDAT
ldr r1, [r0]
ldr r2, =(0x0f<<5)
orr r1, r1, r2
ldr r2, =(0x01<<5)
bic r1, r1, r2
str r1, [r0]
b .
//swi LED2亮
swi:
ldr r0, =GPBDAT
ldr r1, [r0]
ldr r2, =(0x0f<<5)
orr r1, r1, r2
ldr r2, =(0x02<<5)
bic r1, r1, r2
str r1, [r0]
b .
//pabort LED1 2亮
pabort:
ldr r0, =GPBDAT
ldr r1, [r0]
ldr r2, =(0x0f<<5)
orr r1, r1, r2
ldr r2, =(0x03<<5)
bic r1, r1, r2
str r1, [r0]
b .
//dabort LED 3亮
dabort:
ldr r0, =GPBDAT
ldr r1, [r0]
ldr r2, =(0x0f<<5)
orr r1, r1, r2
ldr r2, =(0x04<<5)
bic r1, r1, r2
str r1, [r0]
b .
//irq LED 1 3亮
irq:
sub lr, lr, #4
stmfd sp!, {r0-r12, lr}
mrs r0, spsr
stmfd sp!, {r0}
// LED 指示 (可选)
ldr r0, =GPBDAT
ldr r1, [r0]
ldr r2, =(0x05<<5)
bic r1, r1, r2
str r1, [r0]
ldr r0, =INTOFFSET
ldr r0, [r0]
ldr r1, =HandlerEINT0 // 需定义该地址常量
add r1, r1, r0, lsl #2
ldr r1, [r1]
mov lr, pc
mov pc, r1
ldmfd sp!, {r0}
msr spsr_cxsf, r0
ldmfd sp!, {r0-r12, lr}
movs pc, lr
//fiq LED2 3亮
fiq:
ldr r0, =GPBDAT
ldr r1, [r0]
ldr r2, =(0x0f<<5)
orr r1, r1, r2
ldr r2, =(0x06<<5)
bic r1, r1, r2
str r1, [r0]
b .
// 复位处理 (Reset Handler)
reset:
// 1. 关闭看门狗
ldr r0, =WTCON
mov r1, #0x0
str r1, [r0]
// 2. 屏蔽中断
ldr r0, =INTMSK
ldr r1, =0xffffffff
str r1, [r0]
ldr r0, =INTSUBMSK
ldr r1, =0x7fff
str r1, [r0]
// 3. 设置时钟
.equ U_MDIV, 56
.equ U_PDIV, 2
.equ U_SDIV, 2
.equ M_MDIV, 127
.equ M_PDIV, 2
.equ M_SDIV, 1
.equ CLKDIVN_VAL, 5 //直接根据数据手册查表
ldr r0, =LOCKTIME
ldr r1, =0xffffffff
str r1, [r0]
// 3.1 设置分频系数 CLKDIVN
// 必须在 MPLLCON 之前设置,防止频率过高
ldr r0, =CLKDIVN
ldr r1, =CLKDIVN_VAL
str r1, [r0]
// 3.2 设置异步总线模式 (Async Bus Mode)
// 当 HDIVN != 0 时必须设置
.if CLKDIVN_VAL > 1
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000
mcr p15,0,r0,c1,c0,0
.else
mrc p15,0,r0,c1,c0,0
bic r0,r0,#0xc0000000
mcr p15,0,r0,c1,c0,0
.endif
// 3.3 设置 UPLLCON
ldr r0, =UPLLCON
ldr r1, =((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)
str r1, [r0]
// 7个nop
nop
nop
nop
nop
nop
nop
nop
// 3.4 设置 MPLLCON
ldr r0, =MPLLCON
ldr r1, =((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV)
str r1, [r0]
// 4. 初始化内存控制器
ldr r0, =SMRDATA
ldr r1, =_start
sub r0, r0, r1 // r0 = SMRDATA 相对 _start 的偏移
mov r2, pc // 获取当前 PC
ldr r3, =0x30000000 // 判断阈值
cmp r2, r3
blo 1f // 若 PC < 0x30000000,说明在 0x0 运行,偏移即为物理地址
add r0, r0, r3 // 若 PC >= 0x30000000,说明在 SDRAM 运行,需加上基址
1:
ldr r1, =BWSCON
add r2, r0, #52 // SMRDATA 数据长度 13*4 = 52 字节
0: // 局部标签
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b // backward跳转到标签 0
// 5. 初始化堆栈
.equ STACK_BASE_ADDRESS, 0x33ff8000
.equ UserStack, (STACK_BASE_ADDRESS-0x3800)
.equ SVCStack, (STACK_BASE_ADDRESS-0x2800)
.equ UndefStack, (STACK_BASE_ADDRESS-0x2400)
.equ AbortStack, (STACK_BASE_ADDRESS-0x2000)
.equ IRQStack, (STACK_BASE_ADDRESS-0x1000)
.equ FIQStack, (STACK_BASE_ADDRESS-0x0)
//5.1 undef
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r0, r0, #UNDEFMODE
msr cpsr_c, r0
ldr sp, =UndefStack
//5.2 Abort
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r0, r0, #ABORTMODE
msr cpsr_c, r0
ldr sp, =AbortStack
//5.3 irq
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r0, r0, #IRQMODE
msr cpsr_c, r0
ldr sp, =IRQStack
//5.4 fiq
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r0, r0, #FIQMODE
msr cpsr_c, r0
ldr sp, =FIQStack
//5.5 svc
mrs r0, cpsr
bic r0, r0, #MODEMASK
orr r0, r0, #SVCMODE
msr cpsr_c, r0
ldr sp, =SVCStack
// 6. 代码搬运 (SRAM/NOR -> SDRAM)
// 无论从 NAND 还是 NOR 启动,0x00000000 处都有我们的代码
// (NAND启动时是SRAM,NOR启动时是NOR Flash)
// 将其复制到0x30000000 处
mov r0, #0 // 源地址 0x00000000
ldr r1, =_start // 目标地址 0x30000000 (由linker.ld定义)
ldr r2, =_bss_start_ // 结束地址 (BSS段是空的)
sub r2, r2, r1 // 计算代码大小 (r2 = _bss_start_ - _start)
copy_loop:
ldr r3, [r0], #4
str r3, [r1], #4
subs r2, r2, #4
bgt copy_loop // 循环直到复制完
// 跳转到 SDRAM 中的代码继续执行
ldr pc, =on_sdram
on_sdram:
// 7. 清零 BSS
mov r0, #0
ldr r2, BaseOfZero
ldr r3, EndOfBSS
0:
cmp r2, r3
strcc r0, [r2], #4
bcc 0b
// 8.跳转到 main
bl main
b .
//reset结束================================================================
.align 2
.ltorg
// 内存配置数据表 SMRDATA
SMRDATA:
.long (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.long ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.long ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.long ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.long ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.long ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.long ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.long ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.long ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.long ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Tsrc<<18)+(Tchr<<16)+REFCNT)
.long 0x32
.long 0x30
.long 0x30
// 链接器符号 (指针)
BaseOfROM: .word _start
TopOfROM: .word _etext
BaseOfBSS: .word _data
BaseOfZero: .word _bss_start_
EndOfBSS: .word _bss_end_
// ISR 地址定义 (RAM)
//异常处理程序地址(对应异常向量表),16进制也可以用,10进制计算便于检查
.equ ISR_ENTRIES_STARTADDRESS, 0x33ffff00
.equ HandleReset, ISR_ENTRIES_STARTADDRESS + 0
.equ HandleUndef, ISR_ENTRIES_STARTADDRESS + 4
.equ HandleSWI, ISR_ENTRIES_STARTADDRESS + 8
.equ HandlePabort, ISR_ENTRIES_STARTADDRESS + 12
.equ HandleDabort, ISR_ENTRIES_STARTADDRESS + 16
.equ HandleReserved, ISR_ENTRIES_STARTADDRESS + 20
.equ HandleIRQ, ISR_ENTRIES_STARTADDRESS + 24
.equ HandleFIQ, ISR_ENTRIES_STARTADDRESS + 28
//S3C2440有32个中断源,每个中断源对应一个处理函数
//具体的IRQ中断源处理函数地址表
.equ HandlerEINT0, ISR_ENTRIES_STARTADDRESS + 32
.equ HandlerEINT1, ISR_ENTRIES_STARTADDRESS + 36
.equ HandlerEINT2, ISR_ENTRIES_STARTADDRESS + 40
.equ HandlerEINT3, ISR_ENTRIES_STARTADDRESS + 44
.equ HandlerEINT4_7, ISR_ENTRIES_STARTADDRESS + 48
.equ HandlerEINT8_23, ISR_ENTRIES_STARTADDRESS + 52
.equ HandlerINT_CAM, ISR_ENTRIES_STARTADDRESS + 56
.equ HandlernBATT_FLT, ISR_ENTRIES_STARTADDRESS + 60
.equ HandlerINT_TICK, ISR_ENTRIES_STARTADDRESS + 64
.equ HandlerINT_WDT_AC97, ISR_ENTRIES_STARTADDRESS + 68
.equ HandlerINT_TIMER0, ISR_ENTRIES_STARTADDRESS + 72
.equ HandlerINT_TIMER1, ISR_ENTRIES_STARTADDRESS + 76
.equ HandlerINT_TIMER2, ISR_ENTRIES_STARTADDRESS + 80
.equ HandlerINT_TIMER3, ISR_ENTRIES_STARTADDRESS + 84
.equ HandlerINT_TIMER4, ISR_ENTRIES_STARTADDRESS + 88
.equ HandlerINT_UART2, ISR_ENTRIES_STARTADDRESS + 92
.equ HandlerINT_LCD, ISR_ENTRIES_STARTADDRESS + 96
.equ HandlerINT_DMA0, ISR_ENTRIES_STARTADDRESS + 100
.equ HandlerINT_DMA1, ISR_ENTRIES_STARTADDRESS + 104
.equ HandlerINT_DMA2, ISR_ENTRIES_STARTADDRESS + 108
.equ HandlerINT_DMA3, ISR_ENTRIES_STARTADDRESS + 112
.equ HandlerINT_SDI, ISR_ENTRIES_STARTADDRESS + 116
.equ HandlerINT_SPI0, ISR_ENTRIES_STARTADDRESS + 120
.equ HandlerINT_UART1, ISR_ENTRIES_STARTADDRESS + 124
.equ HandlerINT_NFCON, ISR_ENTRIES_STARTADDRESS + 128
.equ HandlerINT_USBD, ISR_ENTRIES_STARTADDRESS + 132
.equ HandlerINT_USBH, ISR_ENTRIES_STARTADDRESS + 136
.equ HandlerINT_IIC, ISR_ENTRIES_STARTADDRESS + 140
.equ HandlerINT_UART0, ISR_ENTRIES_STARTADDRESS + 144
.equ HandlerINT_SPI1, ISR_ENTRIES_STARTADDRESS + 148
.equ HandlerINT_RTC, ISR_ENTRIES_STARTADDRESS + 152
.equ HandlerINT_ADC, ISR_ENTRIES_STARTADDRESS + 156
3.3 主程序 (src/main.c)
简单的流水灯逻辑。
#include "2440addr.h"
void delay(volatile int count) {
while (count--);
}
void EINT0_Handler(void) {
rSRCPND |= (1<<0); // EINT0 对应位 0
rINTPND |= (1<<0);
}
int main(void)
{
// 1. 初始化 LED 1-4端口 (GPB5, GPB6, GPB7, GPB8)
rGPBCON &= ~(0xFF << 10);
rGPBCON |= (0x55 << 10);
pISR_EINT0 = (unsigned)EINT0_Handler;
while(1) {
// 全亮
//rGPBDAT &= ~(0xF << 5);
//delay(0xF0000);
// 全灭
rGPBDAT |= (0xF << 5);
// 流水灯
int i;
for(i = 5; i <= 8; i++) {
rGPBDAT &= ~(1 << i); // 点亮 LED i
delay(0xF0000);
rGPBDAT |= (1 << i); // 熄灭 LED i
}
}
return 0;
}
3.4 关键头文件 (inc文件夹)
这些文件定义了寄存器地址和内存配置,是编译通过的基础。
inc/2440addr.inc (汇编用寄存器定义):
// ====================================================================
// File Name : 2440addr_gnu.inc
// Function : S3C2440 Define Address Register (Assembly for GNU GAS)
// Adapted from 2440addr.inc
// ====================================================================
// GBLL BIG_ENDIAN__
// BIG_ENDIAN__ SETL {FALSE}
// In GAS, define BIG_ENDIAN__ via command line or .equ if needed.
// For now, we assume Little Endian as per original default.
// =================
// Memory control
// =================
.equ BWSCON, 0x48000000 // Bus width & wait status
.equ BANKCON0, 0x48000004 // Boot ROM control
.equ BANKCON1, 0x48000008 // BANK1 control
.equ BANKCON2, 0x4800000c // BANK2 control
.equ BANKCON3, 0x48000010 // BANK3 control
.equ BANKCON4, 0x48000014 // BANK4 control
.equ BANKCON5, 0x48000018 // BANK5 control
.equ BANKCON6, 0x4800001c // BANK6 control
.equ BANKCON7, 0x48000020 // BANK7 control
.equ REFRESH, 0x48000024 // DRAM/SDRAM refresh
.equ BANKSIZE, 0x48000028 // Flexible Bank Size
.equ MRSRB6, 0x4800002c // Mode register set for SDRAM Bank6
.equ MRSRB7, 0x48000030 // Mode register set for SDRAM Bank7
// ==========================
// CLOCK & POWER MANAGEMENT
// ==========================
.equ LOCKTIME, 0x4c000000 // PLL lock time counter
.equ MPLLCON, 0x4c000004 // MPLL Control
.equ UPLLCON, 0x4c000008 // UPLL Control
.equ CLKCON, 0x4c00000c // Clock generator control
.equ CLKSLOW, 0x4c000010 // Slow clock control
.equ CLKDIVN, 0x4c000014 // Clock divider control
// =================
// INTERRUPT
// =================
.equ SRCPND, 0x4a000000 // Interrupt request status
.equ INTMOD, 0x4a000004 // Interrupt mode control
.equ INTMSK, 0x4a000008 // Interrupt mask control
.equ PRIORITY, 0x4a00000c // IRQ priority control
.equ INTPND, 0x4a000010 // Interrupt request status
.equ INTOFFSET, 0x4a000014 // Interruot request source offset
.equ SUSSRCPND, 0x4a000018 // Sub source pending
.equ INTSUBMSK, 0x4a00001c // Interrupt sub mask
// =================
// I/O PORT for LED
// =================
.equ GPFCON, 0x56000050 // Port F control
.equ GPFDAT, 0x56000054 // Port F data
.equ GPFUP, 0x56000058 // Pull-up control F
.equ GPBCON, 0x56000010 // Port B control
.equ GPBDAT, 0x56000014 // Port B data
// Miscellaneous register
.equ MISCCR, 0x56000080 // Miscellaneous control
.equ DCKCON, 0x56000084 // DCLK0/1 control
.equ EXTINT0, 0x56000088 // External interrupt control register 0
.equ EXTINT1, 0x5600008c // External interrupt control register 1
.equ EXTINT2, 0x56000090 // External interrupt control register 2
.equ EINTFLT0, 0x56000094 // Reserved
.equ EINTFLT1, 0x56000098 // Reserved
.equ EINTFLT2, 0x5600009c // External interrupt filter control register 2
.equ EINTFLT3, 0x560000a0 // External interrupt filter control register 3
.equ EINTMASK, 0x560000a4 // External interrupt mask
.equ EINTPEND, 0x560000a8 // External interrupt pending
.equ GSTATUS0, 0x560000ac // External pin status
.equ GSTATUS1, 0x560000b0 // Chip ID(0x32440000)
.equ GSTATUS2, 0x560000b4 // Reset type
.equ GSTATUS3, 0x560000b8 // Saved data0(32-bit) before entering POWER_OFF mode
.equ GSTATUS4, 0x560000bc // Saved data1(32-bit) before entering POWER_OFF mode
// Added for 2440
.equ MSLCON, 0x560000cc // Memory sleep control register
// =================
// WATCH DOG TIMER
// =================
.equ WTCON, 0x53000000 // Watch-dog timer mode
.equ WTDAT, 0x53000004 // Watch-dog timer data
.equ WTCNT, 0x53000008 // Eatch-dog timer count
// =================
// Nand Flash
// =================
.equ NFCONF, 0x4E000000 // NAND Flash configuration
.equ NFCONT, 0x4E000004 // NAND Flash control
.equ NFCMD, 0x4E000008 // NAND Flash command
.equ NFADDR, 0x4E00000C // NAND Flash address
.equ NFDATA, 0x4E000010 // NAND Flash data
.equ NFDATA8, 0x4E000010 // NAND Flash data
.equ NFMECCD0, 0x4E000014 // NAND Flash ECC for Main Area
.equ NFMECCD1, 0x4E000018
.equ NFSECCD, 0x4E00001C // NAND Flash ECC for Spare Area
.equ NFSTAT, 0x4E000020 // NAND Flash operation status
.equ NFESTAT0, 0x4E000024
.equ NFESTAT1, 0x4E000028
.equ NFMECC0, 0x4E00002C
.equ NFMECC1, 0x4E000030
.equ NFSECC, 0x4E000034
.equ NFSBLK, 0x4E000038 // NAND Flash Start block address
.equ NFEBLK, 0x4E00003C // NAND Flash End block address
inc/memcfg.inc (内存控制器配置值):
//
// NAME : memcfg_gnu.inc
// DESC : Memory bank configuration file (GNU GAS)
// Adapted from Memcfg.inc
//
// Memory Area
// GCS6 32bit(64MB) SDRAM(0x3000_0000-0x33ff_ffff)
// BWSCON
.equ DW8, (0x0)
.equ DW16, (0x1)
.equ DW32, (0x2)
.equ WAIT, (0x1<<2)
.equ UBLB, (0x1<<3)
.ifndef BUSWIDTH
.error "BUSWIDTH must be defined before including this file"
.endif
.if BUSWIDTH == 16
.equ B1_BWSCON, (DW16)
.equ B2_BWSCON, (DW16)
.equ B3_BWSCON, (DW16)
.equ B4_BWSCON, (DW16+WAIT)
.equ B5_BWSCON, (DW16)
.equ B6_BWSCON, (DW16)
.equ B7_BWSCON, (DW16)
.else
// BUSWIDTH=32 ; 2440 EV board.
.equ B1_BWSCON, (DW16) // AMD flash(AM29LV800B), 16-bit, for nCS1
.equ B2_BWSCON, (DW16) // PCMCIA(PD6710), 16-bit
.equ B3_BWSCON, (DW16) // Ethernet(CS8900), 16-bit
.equ B4_BWSCON, (DW32) // Intel Strata(28F128), 32-bit, for nCS4
.equ B5_BWSCON, (DW16) // A400/A410 Ext, 16-bit
.equ B6_BWSCON, (DW32) // SDRAM(K4S561632C) 32MBx2, 32-bit
.equ B7_BWSCON, (DW32) // N.C.
.endif
// BANK0CON
.equ B0_Tacs, 0x3 // 0clk
.equ B0_Tcos, 0x3 // 0clk
.equ B0_Tacc, 0x7 // 14clk
.equ B0_Tcoh, 0x3 // 0clk
.equ B0_Tah, 0x3 // 0clk
.equ B0_Tacp, 0x1
.equ B0_PMC, 0x0 // normal
// BANK1CON
.equ B1_Tacs, 1 // 0clk
.equ B1_Tcos, 1 // 0clk
.equ B1_Tacc, 6 // 14clk
.equ B1_Tcoh, 1 // 0clk
.equ B1_Tah, 1 // 0clk
.equ B1_Tacp, 0x0
.equ B1_PMC, 0x0 // normal
// Bank 2 parameter
.equ B2_Tacs, 1 // 0clk
.equ B2_Tcos, 1 // 0clk
.equ B2_Tacc, 6 // 14clk
.equ B2_Tcoh, 1 // 0clk
.equ B2_Tah, 1 // 0clk
.equ B2_Tacp, 0x0
.equ B2_PMC, 0x0 // normal
// Bank 3 parameter
.equ B3_Tacs, 0x1 // 0clk
.equ B3_Tcos, 0x1 // 0clk
.equ B3_Tacc, 0x6 // 14clk
.equ B3_Tcoh, 0x1 // 0clk
.equ B3_Tah, 0x1 // 0clk
.equ B3_Tacp, 0x0
.equ B3_PMC, 0x0 // normal
// Bank 4 parameter
.equ B4_Tacs, 0x1 // 0clk
.equ B4_Tcos, 0x1 // 0clk
.equ B4_Tacc, 0x6 // 14clk
.equ B4_Tcoh, 0x1 // 0clk
.equ B4_Tah, 0x1 // 0clk
.equ B4_Tacp, 0x0
.equ B4_PMC, 0x0 // normal
// Bank 5 parameter
.equ B5_Tacs, 0x1 // 0clk
.equ B5_Tcos, 0x1 // 0clk
.equ B5_Tacc, 0x6 // 14clk
.equ B5_Tcoh, 0x1 // 0clk
.equ B5_Tah, 0x1 // 0clk
.equ B5_Tacp, 0x0
.equ B5_PMC, 0x0 // normal
.if 1 // When 100MHz HCLK is used.
// Bank 6 parameter
.equ B6_MT, 0x3 // SDRAM
.equ B6_Trcd, 0x1 // 3clk
.equ B6_SCAN, 0x1 // 9bit
// Bank 7 parameter
.equ B7_MT, 0x3 // SDRAM
.equ B7_Trcd, 0x1 // 3clk
.equ B7_SCAN, 0x1 // 9bit
// REFRESH parameter
.equ REFEN, 0x1 // Refresh enable
.equ TREFMD, 0x0 // CBR(CAS before RAS)/Auto refresh
.equ Trp, 0x1 // 3clk
.equ Tsrc, 0x1 // 5clk Trc= Trp(3)+Tsrc(5) = 8clock
.equ Tchr, 0x2 // 3clk
.equ REFCNT, 1268 // HCLK=105Mhz, (2048+1-7.81*100);75M->1463
.else
// Bank 6 parameter
.equ B6_MT, 0x3 // SDRAM
.equ B6_Trcd, 0x2 // 4clk
.equ B6_SCAN, 0x1 // 9bit
// Bank 7 parameter
.equ B7_MT, 0x3 // SDRAM
.equ B7_Trcd, 0x2 // 4clk
.equ B7_SCAN, 0x1 // 9bit
// REFRESH parameter
.equ REFEN, 0x1 // Refresh enable
.equ TREFMD, 0x0 // CBR(CAS before RAS)/Auto refresh
.equ Trp, 0x2 // 4clk
.equ Tsrc, 0x2 // 6clk Trc= Trp(4)+Tsrc(6) = 10clock
.equ Tchr, 0x2 // 3clk
.equ REFCNT, 1012 // HCLK=135Mhz, (2048+1-7.8*133 = 1012)
.endif
inc/2440addr.h (C语言用寄存器定义):
#ifndef __2440ADDR_H__
#define __2440ADDR_H__
// ====================================================================
// S3C2440 寄存器定义
// ====================================================================
// --- GPIO 寄存器 (Port B 用于 LED) ---
#define rGPBCON (*(volatile unsigned *)0x56000010) // Port B Control
#define rGPBDAT (*(volatile unsigned *)0x56000014) // Port B Data
#define rGPBUP (*(volatile unsigned *)0x56000018) // Port B Pull-up
// --- 中断控制器 ---
#define rSRCPND (*(volatile unsigned *)0x4a000000) // Interrupt Request Status
#define rINTMOD (*(volatile unsigned *)0x4a000004) // Interrupt Mode Control
#define rINTMSK (*(volatile unsigned *)0x4a000008) // Interrupt Mask Control
#define rINTPND (*(volatile unsigned *)0x4a000010) // Interrupt Request Status
#define rINTOFFSET (*(volatile unsigned *)0x4a000014) // Interrupt Request Source Offset
// --- 看门狗 ---
#define rWTCON (*(volatile unsigned *)0x53000000)
// ====================================================================
// 中断向量表地址 (对应 startup_gnu.S 中的定义)
// ====================================================================
// 这里的地址必须与 startup_gnu.S 中的 ISR_ENTRIES_STARTADDRESS 保持一致
#define _ISR_STARTADDRESS 0x33ffff00
// 异常向量处理函数指针
#define pISR_RESET (*(unsigned *)(_ISR_STARTADDRESS+0x0))
#define pISR_UNDEF (*(unsigned *)(_ISR_STARTADDRESS+0x4))
#define pISR_SWI (*(unsigned *)(_ISR_STARTADDRESS+0x8))
#define pISR_PABORT (*(unsigned *)(_ISR_STARTADDRESS+0xC))
#define pISR_DABORT (*(unsigned *)(_ISR_STARTADDRESS+0x10))
#define pISR_RESERVED (*(unsigned *)(_ISR_STARTADDRESS+0x14))
#define pISR_IRQ (*(unsigned *)(_ISR_STARTADDRESS+0x18))
#define pISR_FIQ (*(unsigned *)(_ISR_STARTADDRESS+0x1C))
// 具体中断源处理函数指针 (IRQ)
#define pISR_EINT0 (*(unsigned *)(_ISR_STARTADDRESS+0x20))
#define pISR_EINT1 (*(unsigned *)(_ISR_STARTADDRESS+0x24))
#define pISR_EINT2 (*(unsigned *)(_ISR_STARTADDRESS+0x28))
#define pISR_EINT3 (*(unsigned *)(_ISR_STARTADDRESS+0x2C))
#define pISR_EINT4_7 (*(unsigned *)(_ISR_STARTADDRESS+0x30))
#define pISR_EINT8_23 (*(unsigned *)(_ISR_STARTADDRESS+0x34))
// ... 其他中断源可按需添加 ...
#define pISR_TIMER0 (*(unsigned *)(_ISR_STARTADDRESS+0x48)) // +72 (0x48)
#endif
3.5 Makefile
支持生成 .bin (烧录用), .elf (调试用), .dis (反汇编), .txt (详细符号表), .s (C转汇编)。
# =====================================================================
# Makefile for mini2440 (GNU Toolchain)
# =====================================================================
# 交叉编译器
CROSS_COMPILE ?= arm-none-eabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
AS = $(CROSS_COMPILE)as
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
READELF = $(CROSS_COMPILE)readelf
# 目标文件名
TARGET = mini2440
BUILD_DIR = build
# 编译选项
# -mcpu=arm920t : 指定处理器架构
# -g : 生成调试信息
# -Wall : 开启所有警告
# -O0 : 不优化,越大优化程度越高,代码出错概率越高
# -nostdlib : 不链接标准库(隐含-nostartfiles,即不使用win环境下标准的启动文件crt0.o),裸机开发必需
# -Iinc : 头文件搜索路径
INCLUDES = -Iinc
CFLAGS = -mcpu=arm920t -g -Wall -O0 -nostdlib $(INCLUDES)
ASFLAGS = -mcpu=arm920t -g $(INCLUDES)
LDFLAGS = -T link.ld
# 源文件列表
# 自动查找 src 目录下的 .S 文件和 .c 文件
SRCS_ASM = $(wildcard src/*.S)
SRCS_C = $(wildcard src/*.c)
# 如果有多个源码目录,可以这样添加:
# SRCS_C += $(wildcard drivers/*.c)
# SRCS_C += $(wildcard lib/*.c)
# 生成文件到build目录
OBJS = $(addprefix $(BUILD_DIR)/, $(SRCS_ASM:.S=.o) $(SRCS_C:.c=.o))
.PHONY: all clean dis
all: $(BUILD_DIR)/$(TARGET).bin $(BUILD_DIR)/$(TARGET).dis $(BUILD_DIR)/$(TARGET).txt $(OBJS_S)
# 1. 链接生成 ELF 文件
$(BUILD_DIR)/$(TARGET).elf: $(OBJS)
@mkdir -p $(dir $@)
$(LD) $(LDFLAGS) -o $@ $^
@echo
@echo ">>> ELF Header Info:"
$(READELF) -h $@
@echo
# 2. 生成bin
$(BUILD_DIR)/$(TARGET).bin: $(BUILD_DIR)/$(TARGET).elf
@mkdir -p $(dir $@)
$(OBJCOPY) -O binary -S $< $@
# 3. 生成dis
$(BUILD_DIR)/$(TARGET).dis: $(BUILD_DIR)/$(TARGET).elf
@mkdir -p $(dir $@)
$(OBJDUMP) -D $< > $@
# 4. 生成详细的符号表和源码混合反汇编
$(BUILD_DIR)/$(TARGET).txt: $(BUILD_DIR)/$(TARGET).elf
@mkdir -p $(dir $@)
$(OBJDUMP) -x -S $< > $@
# 5. 编译汇编
$(BUILD_DIR)/%.o: %.S
@mkdir -p $(dir $@)
$(CC) $(ASFLAGS) -c -o $@ $<
# 6. 编译 C
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c -o $@ $<
# 清理规则
clean:
rm -rf $(BUILD_DIR)
4. 编译与运行
所有操作均通过 run_docker.sh 脚本完成,无需在宿主机安装工具链。
4.1 编译项目
默认执行 make,生成所有目标文件。
./run_docker.sh
输出: build/mini2440.bin (烧录文件), build/mini2440.txt (分析文件) 等。
4.2 清理项目
./run_docker.sh make clean
4.3 烧录运行 (使用 dnw)
-
连接 Mini2440 的 USB A口、串口到电脑。
-
使用nor flash启动开发板,进入 Supervivi 菜单。
-
输入
a(Absolute User Application) 功能。 -
在powershell管理员中
查看设备 ID (通常是 3-1 或 2-1):
usbipd list将设备挂载进 WSL (如果听到叮咚声即成功):
usbipd attach --wsl --busid <你的BUSID> -
WSL执行以下命令发送文件:
-
./run_docker.sh dnw ./bulid/mini2440.bin
注意: 脚本已包含 --privileged 参数,确保容器能访问 USB 设备。
-
关闭板子,切换到nand flash再重启就可以看到流水灯了,大功告成!
5. 常见问题
-
Q: 为什么链接地址是 0x30000000?
-
A: 因为 Mini2440 的 SDRAM 挂载在 0x30000000。虽然 NAND 启动时代码最初在 0x0 (SRAM),但为了运行大于 4KB 的程序,我们需要将其搬运到容量更大的 SDRAM 中运行。
-
-
Q: 为什么 dnw 提示找不到设备?
-
A: 请确保宿主机已识别 USB 设备 (使用
lsusb查看)。如果宿主机识别但容器不识别,请检查run_docker.sh中是否包含--privileged。
-
-
Q: 烧录后程序不运行?
-
A: 检查
link.ld中的地址是否正确,以及startup.S中的内存初始化和代码搬运逻辑是否执行成功。可以通过点亮 LED 进行调试。
-
更多推荐

所有评论(0)