#配置环境及烧录部分,请看之前的专栏文章: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 代码的状态。

    关键步骤摘要:

    1. 关看门狗 & 屏蔽中断: 防止启动过程中被干扰。

    2. 设置时钟: 配置 CLKDIVNMPLLCON,使系统运行在 400MHz (FCLK)。

      1. 注意: 必须先设置 CLKDIVN 和异步总线模式,最后设置 MPLL,否则可能死机。

    3. 初始化内存控制器: 配置 13 个寄存器以启用 SDRAM。

      1. 技巧: 使用位置无关代码 (PIC) 计算 SMRDATA 的物理地址,确保无论在 SRAM (0x0) 还是 SDRAM 都能正确读取配置数据。

    4. 代码搬运: 将代码从 0x0 (SRAM/NOR) 复制到 0x30000000 (SDRAM)。

    5. 跳转: 使用 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)

    1. 连接 Mini2440 的 USB A口、串口到电脑。

    2. 使用nor flash启动开发板,进入 Supervivi 菜单。

    3. 输入 a (Absolute User Application) 功能。

    4. 在powershell管理员中

      查看设备 ID (通常是 3-1 或 2-1):

      usbipd list

      将设备挂载进 WSL (如果听到叮咚声即成功):

      usbipd attach --wsl --busid <你的BUSID>
    5. WSL执行以下命令发送文件:

    6. ./run_docker.sh dnw ./bulid/mini2440.bin

    注意: 脚本已包含 --privileged 参数,确保容器能访问 USB 设备。

    1. 关闭板子,切换到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 进行调试。

    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐