欢迎光临
我们一直在努力

ARM裸机开发入门:从环境搭建到外设驱动

ARM 裸机开发学习知识体系

第一阶段:环境搭建与工具链认知
  • 交叉编译工具链安装与配置
    • 安装 gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf。
    • 配置环境变量(修改 .bashrc 文件)。
    • 验证安装(arm-linux-gnueabihf-gcc -v)。
  • 代码传输与烧录
    • 在 Ubuntu 上配置 FTP 服务 (vsftpd)。
    • 使用 Windows 下的 FileZilla Client 工具上传代码到 Ubuntu 开发机。
    • 使用 imxdownload 工具将生成的 .bin 文件烧录到 SD 卡 (/dev/sdb)。
  • 开发板启动方式
    • 了解 i.MX6ULL-Mini 开发板的多种启动模式(SD卡、eMMC、NAND Flash、USB)。
    • 通过拨码开关选择从 SD 卡启动。
  • GNU 工具链详解
    • 编译器 (arm-linux-gnueabihf-gcc): 将源代码(.S, .c)编译成目标文件(.o)。
    • 链接器 (arm-linux-gnueabihf-ld): 将一个或多个目标文件链接成可执行文件(.elf),并根据链接脚本分配地址。
    • 格式转换器 (arm-linux-gnueabihf-objcopy): 将 .elf 文件转换为纯二进制格式(.bin),用于烧录。
    • 反汇编器 (arm-linux-gnueabihf-objdump): 将 .elf 文件反汇编成人类可读的汇编代码,用于调试和分析。
  • Makefile 自动化构建
    • 理解 Makefile 的三要素:目标、依赖、规则。
    • 编写规则来自动化执行编译、链接、格式转换、反汇编等步骤。
    • 使用变量(如 CC, LD)简化和优化 Makefile。
    • 定义伪目标(如 clean, load)来执行清理和烧录操作。
  • 第二阶段:ARM 汇编语言基础
  • 汇编程序结构与伪操作
    • AREA: 定义代码段或数据段。
    • CODE32 / THUMB: 指定指令集(32位 ARM 或 16位 Thumb)。
    • ENTRY: 定义程序入口点。
    • END: 标记汇编源文件结束。
    • .global _start: 声明全局符号 _start,作为程序入口。
  • 核心数据处理指令
    • MOV: 数据传送指令。理解立即数(12位立即数的判断标准)、寄存器寻址、移位操作(LSL, LSR, ASR, ROR, RRX)。
    • ADD/SUB: 加法/减法指令。
    • MVN: 按位取反传送指令。
    • BIC: 位清零指令(AND NOT)。
    • ORR: 逻辑或指令(用于置位)。
  • 内存访问指令 (Load/Store)
    • LDR: 从内存加载数据到寄存器。掌握多种寻址方式(立即数偏移、寄存器偏移、前/后索引等)。
    • STR: 将寄存器数据存储到内存。
  • 程序状态寄存器 (CPSR) 与条件执行
    • CPSR 标志位:
      • N (Negative): 结果为负。
      • Z (Zero): 结果为零。
      • C (Carry): 无符号运算产生进位或借位。
      • V (oVerflow): 有符号运算溢出。
    • CMP: 比较指令(实质是做减法但不保存结果,只更新 CPSR 标志位)。
    • 利用条件码(如 EQ, NE, GT, LT 等)实现条件执行。
  • 程序流控制指令
    • B: 无条件或条件分支跳转。
    • BL: 带返回的分支跳转(将下一条指令地址存入 LR)。
    • BX: 间接跳转,并可切换指令集状态(ARM/Thumb)。
  • ARM 处理器工作模式
    • 7 种基本模式: User, FIQ, IRQ, Supervisor (SVC), Abort, Undef, System。
    • Cortex-A 特有模式: Monitor (安全扩展)。
    • 模式切换: 通过修改 CPSR 寄存器的 M[4:0] 位域。
      • MRS: 读取 CPSR 到通用寄存器。
      • MSR: 将通用寄存器的值写入 CPSR。
    • CPS 指令: 快速开关中断和切换模式(如 cpsid i, cpsie i, cps #mode)。
  • 异常向量表
    • 处理器复位后从固定地址(通常是 0x0)开始执行。
    • 异常向量表包含各种异常(复位、未定义指令、SWI、预取中止、数据中止、IRQ、FIQ)的入口地址。
    • 软中断 (SWI): 用于在用户模式下请求内核服务(系统调用)。
  • 第三阶段:函数调用与栈管理
  • 函数调用机制
    • PC 与 LR: BL 指令将返回地址存入 LR (R14),函数通过 BX LR 或 MOV PC, LR 返回。
    • 寄存器传参: ARM AAPCS 规约规定,前 4 个参数通过 R0-R3 传递,返回值通过 R0 返回。
  • 栈 (Stack) 的概念与类型
    • 满栈 (Full Stack): 栈指针指向最后一个有效数据项。
    • 空栈 (Empty Stack): 栈指针指向下一个要放入数据的位置。
    • 增栈 (Ascending): 栈向高地址增长。
    • 减栈 (Descending): 栈向低地址增长。
    • ARM 采用: 满减栈 (Full Descending Stack)。
  • 栈指针初始化
    • 使用 LDR SP, =0x40001000 初始化栈顶指针(SP, R13)。
  • 保护与恢复现场 (压栈/弹栈)
    • STMFD (Store Multiple Full Descending): STMFD SP!, {寄存器列表}。将寄存器列表中的值压入栈,并更新 SP。
    • LDMFD (Load Multiple Full Descending): LDMFD SP!, {寄存器列表}。从栈中弹出值到寄存器列表,并更新 SP。
    • 主调函数职责: 在调用子函数前后,负责保存和恢复自己需要使用的寄存器(包括 LR)。
  • 栈对齐问题
    • PRES8 伪指令: 确保栈指针在函数调用时保持 8 字节对齐,以满足某些 ABI 要求,避免链接错误。
  • 混合编程 (C 与汇编互调)
    • 汇编调用 C 函数:
    • 在汇编中使用 IMPORT c_func_name 声明。
    • 按规约准备参数(R0-R3)。
    • 使用 BL c_func_name 调用。
    • 从 R0 获取返回值。
    • C 调用汇编函数:
    • 在汇编中使用 EXPORT asm_func_name 导出函数。
    • 在 C 中使用 extern 声明该函数原型。
    • 像调用普通 C 函数一样调用它。
  • 第四阶段:启动代码与 C 语言运行环境搭建
  • 启动代码的核心任务
    • 初始化异常向量表。
    • 为各处理器模式(SVC, IRQ, FIQ, ABT, UND)设置独立的栈指针。
    • 初始化 .bss 和 COMMON 段(清零)。
    • (可选)关闭看门狗、初始化时钟、DDR 等。
    • 设置处理器为 User 或 SVC 模式。
    • 跳转到 C 语言的 main() 函数。
  • 链接脚本 (Linker Script)
    • 作用: 为链接器提供内存布局蓝图,精确控制各段(.text, .rodata, .data, .bss)在内存中的位置。
    • 关键语法:
      • SECTIONS { … }: 定义输出段。
      • . = 0x87800000;: 设置当前链接地址(Location Counter)。
      • *(.text): 收集所有输入文件的 .text 段。
      • *(.rodata*): 使用通配符收集所有以 .rodata 开头的段(如 .rodata.str1.1)。
      • __bss_start = .; … __bss_end = .;: 定义符号,标记 .bss 段的起始和结束地址,供启动代码用于清零。
  • 内存段 (Memory Segments) 详解
    • .text: 代码段,存放程序的机器指令。
    • .rodata: 只读数据段。存放字符串常量、const 全局变量、跳转表等。运行时不可写。
    • .data: 已初始化数据段。存放初始化为非零值的全局/静态变量。程序镜像中包含其初始值。
    • .bss: 未初始化数据段。存放初始化为0或未显式初始化的静态/全局变量。程序镜像中不占空间,运行时由启动代码清零。
    • COMMON: 未初始化的非静态全局变量。在链接时才确定大小和位置,最终通常与 .bss 合并。
  • 第五阶段:i.MX6ULL 裸机外设驱动开发
  • 硬件抽象与寄存器访问
    • volatile 关键字: 告诉编译器不要优化对该变量的访问,因为其值可能被硬件异步修改。
    • 直接地址映射: #define GPIO1_DR *((volatile unsigned int *)0x0209C000)。
    • 结构体映射: 将外设寄存器组定义为结构体,提高代码可读性和可维护性。c

      编辑

      struct GPIO_t {
      unsigned int DR;
      unsigned int GDIR;
      // … other registers
      };
      #define GPIO1 (*((struct GPIO_t *)0x0209C000))

  • SDK (Software Development Kit) 的使用
    • 主要利用 NXP 提供的头文件(如 MCIMX6Y2.h, fsl_common.h, fsl_iomuxc.h)。
    • 使用 SDK 封装好的 API(如 IOMUXC_SetPinMux, IOMUXC_SetPinConfig)进行引脚复用和电气属性配置。
  • GPIO 驱动开发流程 (以 LED 为例)
    • 时钟使能: 配置 CCM (Clock Control Module) 寄存器,打开对应外设的时钟门控。
    • 引脚复用 (IOMUX): 配置 IOMUXC_SW_MUX_CTL_PAD_xxx 寄存器,将引脚功能设置为 GPIO。
    • 电气属性配置: 配置 IOMUXC_SW_PAD_CTL_PAD_xxx 寄存器,设置驱动能力、上下拉等。
    • GPIO 方向设置: 配置 GPIOx_GDIR 寄存器,设置为输出模式。
    • GPIO 数据操作: 通过 GPIOx_DR 寄存器进行置位(开灯)、清零(关灯)或异或(翻转)操作。
  • BSP (Board Support Package) 工程管理
    • 分层架构:
      • project/: 存放 main.c, start.S 等主程序文件。
      • imx6ull/: 存放 SoC 相关的官方头文件。
      • bsp/: 存放板级外设驱动模块(如 led.c/h, beep.c/h)。
    • 优点: 模块化、可重用、易于维护。
  • 蜂鸣器 (BEEP) 驱动
    • 原理:通过 GPIO 控制 PNP 三极管(如 S8550)的基极,从而控制蜂鸣器的通断。高电平导通,蜂鸣器响。
  • 赞(0)
    未经允许不得转载:171主机测评 » ARM裸机开发入门:从环境搭建到外设驱动
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址