08-裸机实验总结.md 27 KB


title: 裸机实验总结 tags: [裸机开发, IMX6ULL, 实验总结, 嵌入式Linux] created: 2026-09-16 updated: 2026-09-17 pdf_ref:

  • "第八章 汇编LED灯实验.pdf"
  • "第十章 C语言版LED灯实验.pdf"
  • "第十四章 蜂鸣器实验.pdf"
  • "第十五章 按键输入实验.pdf"
  • "第二十章 高精度延时实验.pdf"
  • "第二十三章 DDR3实验.pdf"
  • "第二十四章 RGBLCD显示实验.pdf"
  • "第二十五章 RTC实时时钟实验.pdf"
  • "第二十六章 I2C实验.pdf"
  • "第二十七章 SPI实验.pdf" ---

裸机实验总结

关联知识: [[01-Cortex-A7架构精讲]] | [[02-交叉编译工具链]] | [[03-IMX6ULL启动流程]] 本章目标: 系统梳理10个核心裸机实验,建立从汇编到C语言、从GPIO到复杂外设的完整认知链路


一、裸机开发概述

1.1 裸机 vs RTOS vs Linux

维度 裸机 (Bare Metal) RTOS Linux
调度机制 无,main循环轮询 优先级抢占调度 多进程/多线程调度
内存管理 物理地址直接访问 堆+静态分区 虚拟内存+MMU
外设访问 直接操作寄存器 HAL/驱动封装 子系统框架
中断处理 直接注册ISR 有限中断嵌套 上下半部机制
适用场景 简单控制、启动加载 实时控制、物联网 复杂应用、多媒体
开发复杂度
资源占用 最小(KB级) 中等(几十KB) 大(MB级)

1.2 裸机开发应用场景

  • Boot ROM/SPL: 芯片上电后的第一段代码,初始化DDR、时钟等
  • 简单传感器采集: 无需OS的低功耗设备
  • 产品原型验证: 快速验证硬件功能
  • Bootloader开发: U-Boot等引导加载程序
  • 安全启动链: 信任根验证、加密启动

1.3 I.MX6ULL裸机开发流程

1. 查阅芯片手册 → 定位外设寄存器地址
2. 编写汇编启动文件 → 初始化C运行环境(栈、BSS段)
3. 编写C语言业务代码 → 外设初始化、功能实现
4. 编写链接脚本(.lds) → 定义内存布局
5. 编写Makefile → 编译、链接、生成.bin
6. 烧录验证 → 通过SD卡/串口下载运行

1.4 I.MX6U GPIO体系架构

I.MX6ULL的GPIO系统由5组控制器组成:

GPIO组 IO数量 基地址 说明
GPIO1 32个 0x0209C000 仅GPIO1_IO00~IO09为专用GPIO
GPIO2 22个 0x020A0000 其余IO需通过IOMUXC复用
GPIO3 29个 0x020A4000
GPIO4 29个 0x020A8000
GPIO5 12个 0x020AC000 包含SNVS域IO

GPIO初始化三步曲

  1. 时钟使能: 设置CCGR寄存器对应位
  2. 引足复用: 配置IOMUXC_SW_MUX_CTL_PAD_XXX(MUX_MODE位选择ALT0~ALT8)
  3. 电气属性: 配置IOMUXC_SW_PAD_CTL_PAD_XXX(上拉/下拉/驱动强度/压摆率)

二、实验1: 汇编LED灯实验

2.1 实验目的

  • 掌握ARM汇编语言基本语法
  • 理解I.MX6ULL GPIO寄存器配置方法
  • 建立"查手册→写代码"的裸机开发思维

2.2 硬件连接

LED0连接到GPIO1_IO03,通过三极管驱动,低电平点亮。

2.3 关键代码

.global _start

_start:
    @ 使能GPIO1时钟
    ldr r0, =0x020C4068     @ CCM_CCGR1寄存器地址
    ldr r1, [r0]
    orr r1, r1, #(3 << 26)  @ 设置CG14位[27:26]=11
    str r1, [r0]

    @ 配置GPIO1_IO03复用为GPIO
    ldr r0, =0x020E0068     @ IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
    mov r1, #5              @ ALT5 = GPIO模式
    str r1, [r0]

    @ 配置GPIO1_IO03电气属性
    ldr r0, =0x020E02F4     @ IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
    ldr r1, =0x10B0
    str r1, [r0]

    @ 设置GPIO1_IO03为输出
    ldr r0, =0x0209C004     @ GPIO1_GDIR寄存器
    ldr r1, [r0]
    orr r1, r1, #(1 << 3)
    str r1, [r0]

    @ 点亮LED(输出低电平)
    ldr r0, =0x0209C000     @ GPIO1_DR寄存器
    ldr r1, [r0]
    bic r1, r1, #(1 << 3)
    str r1, [r0]

loop:
    b loop

2.4 运行效果

LED0常亮,程序死循环保持状态。


三、实验2: C语言LED灯实验

3.1 实验目的

  • 掌握汇编到C语言环境的跳转机制
  • 理解C运行环境初始化(SVC模式、栈指针)
  • 学会模块化驱动编程

3.2 关键代码

启动文件 start.S:

.global _start

_start:
    @ 进入SVC模式
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x13
    msr cpsr, r0

    ldr sp, =0x80200000     @ 设置栈指针
    b main                  @ 跳转到main函数

C语言主函数 main.c:

#include "main.h"

int main(void)
{
    /* 使能GPIO1时钟 */
    unsigned int *CCM_CCGR1 = (unsigned int *)0x020C4068;
    *CCM_CCGR1 |= (3 << 26);

    /* 配置GPIO1_IO03复用 */
    unsigned int *SW_MUX = (unsigned int *)0x020E0068;
    *SW_MUX = 5;

    /* 配置电气属性 */
    unsigned int *SW_PAD = (unsigned int *)0x020E02F4;
    *SW_PAD = 0x10B0;

    /* 设置为输出 */
    unsigned int *GPIO1_GDIR = (unsigned int *)0x0209C004;
    *GPIO1_GDIR |= (1 << 3);

    /* 输出低电平点亮LED */
    unsigned int *GPIO1_DR = (unsigned int *)0x0209C000;
    *GPIO1_DR &= ~(1 << 3);

    while(1);
}

3.3 从汇编到C的演进

  • 汇编文件仅负责设置处理器模式(SVC)、初始化栈指针、跳转到main
  • DDR由Boot ROM读取DCD数据自动初始化,无需在代码中手动配置
  • C代码通过指针直接操作寄存器,可读性和可维护性大幅提升

四、实验3: 蜂鸣器实验

4.1 实验目的

  • 巩固GPIO输出控制
  • 理解三极管驱动电路原理
  • 掌握模块化驱动开发(bsp_beep.c/h)

4.2 硬件连接

蜂鸣器通过PNP三极管8550驱动,SNVS_TAMPER1(GPIO5_IO01)控制:

  • 输出低电平 → 三极管导通 → 蜂鸣器鸣叫
  • 输出高电平 → 三极管截止 → 蜂鸣器静音

4.3 关键代码

void beep_init(void)
{
    /* 1、复用为GPIO5_IO01 */
    IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0);

    /* 2、配置电气属性 */
    IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0X10B0);

    /* 3、设置为输出 */
    GPIO5->GDIR |= (1 << 1);

    /* 4、默认高电平,关闭蜂鸣器 */
    GPIO5->DR |= (1 << 1);
}

void beep_switch(int status)
{
    if(status == ON)
        GPIO5->DR &= ~(1 << 1);    /* 打开蜂鸣器 */
    else if(status == OFF)
        GPIO5->DR |= (1 << 1);     /* 关闭蜂鸣器 */
}

4.4 运行效果

LED0与蜂鸣器同步闪烁,周期约500ms。


五、实验4: 按键输入实验

5.1 实验目的

  • 掌握GPIO输入配置方法
  • 理解按键电平检测原理
  • 建立通用GPIO驱动框架

5.2 硬件连接

KEY0连接到UART1_CTS(GPIO1_IO18),外接10K上拉电阻:

  • 未按下:高电平
  • 按下:低电平

5.3 关键代码

通用GPIO驱动:

typedef enum _gpio_pin_direction {
    kGPIO_DigitalInput = 0U,
    kGPIO_DigitalOutput = 1U,
} gpio_pin_direction_t;

void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
    if(config->direction == kGPIO_DigitalInput)
        base->GDIR &= ~(1 << pin);
    else {
        base->GDIR |= (1 << pin);
        gpio_pinwrite(base, pin, config->outputLogic);
    }
}

int gpio_pinread(GPIO_Type *base, int pin)
{
    return (((base->DR) >> pin) & 0x1);
}

按键检测逻辑:

int main(void)
{
    int value = 0;
    gpio_pin_config_t key_config;

    /* 初始化KEY0为输入 */
    key_config.direction = kGPIO_DigitalInput;
    gpio_init(GPIO1, 18, &key_config);

    while(1) {
        value = gpio_pinread(GPIO1, 18);  /* 读取按键状态 */
        if(value == 0) {                   /* 按下 */
            delay(10);                     /* 消抖 */
            if(gpio_pinread(GPIO1, 18) == 0) {
                beep_switch(!beep_status);  /* 翻转蜂鸣器状态 */
            }
            while(!gpio_pinread(GPIO1, 18)); /* 等待松开 */
        }
    }
}

5.4 运行效果

按下KEY0切换蜂鸣器开关状态,实现按键控制。


六、实验5: 高精度延时实验

6.1 实验目的

  • 掌握GPT定时器配置方法
  • 实现微秒级精确延时
  • 理解硬件定时器工作原理

6.2 GPT定时器简介

GPT(General Purpose Timer)是32位向上计数器,特性:

  • 5个可选时钟源:ipg_clk(66MHz)、ipg_clk_32k、ipg_clk_24M等
  • 12位分频器(1~4096分频)
  • 3路输出比较通道
  • 2路输入捕获通道
  • 两种工作模式:restart(重启)和free-run(自由运行)

6.3 关键代码

void delay_init(void)
{
    GPT1->CR = 0;                        /* 先关闭GPT */
    GPT1->CR = (1 << 15);               /* 软复位GPT */
    while(GPT1->CR & (1 << 15));         /* 等待复位完成 */

    GPT1->CR = (1 << 1) | (1 << 6);     /* 使能+ipg_clk时钟源 */
    GPT1->PR = 65;                       /* 66分频,1MHz */
    GPT1->CR |= (1 << 0);               /* 使能GPT计数器 */
}

void delay_us(unsigned int us)
{
    unsigned int cnt_start = GPT1->CNT;
    unsigned int cnt_end = cnt_start + us;

    /* 处理溢出情况 */
    if(cnt_end >= cnt_start) {
        while(GPT1->CNT < cnt_end);
    } else {
        while(GPT1->CNT >= cnt_start);   /* 等待溢出 */
        while(GPT1->CNT < cnt_end);
    }
}

void delay_ms(unsigned int ms)
{
    unsigned int i;
    for(i = 0; i < ms; i++)
        delay_us(1000);
}

6.4 运行效果

延时精度达到1μs级别,比循环延时准确得多。

6.5 延时精度对比

延时方式 精度 原理 适用场景
循环延时 ms级 编译器优化影响大 仅用于简单等待
SysTick μs级 Cortex-M专用 STM32系列
GPT定时器 1μs 硬件计数 I.MX6U通用
EPIT定时器 1μs 单次定时+中断 需要中断触发

七、实验6: DDR3实验

7.1 实验目的

  • 了解SRAM/SDRAM/DDR3发展历史
  • 掌握DDR3初始化配置(通过DCD)
  • 理解DDR3寻址方式和时序参数

7.2 DDR内存发展历程

类型 全称 特点 代表芯片
SRAM Static RAM 不需刷新,速度快,成本高 IS62WV51216
SDRAM Synchronous DRAM 同步时钟,需刷新,成本低 W9825G6KH
DDR Double Data Rate 双沿采样,带宽翻倍
DDR2 DDR2 SDRAM 4bit预取,1.8V
DDR3 DDR3 SDRAM 8bit预取,1.5V,自动校准 MT41K256M16
DDR4 DDR4 SDRAM 16bit预取,1.2V

7.3 I.MX6U的DCD机制

I.MX6U Boot ROM会读取IVT中的DCD(Data Configuration Data)表,自动完成DDR3初始化:

  • DCD表存储在SD卡/eMMC的特定位置
  • 包含DDR控制器寄存器配置和时序参数
  • 用户无需手动编写DDR初始化代码

7.4 DDR3初始化步骤(参考)

/* DDR3初始化主要参数 */
1. 设置CCM_CSCMR1选择DDR时钟源(528MHz)
2. 配置CCM_CACRR设置DDR时钟分频
3. 设置MMDC类型寄存器(DDR3类型、位宽)
4. 配置时序参数(tRCD, tRP, tRAS等)
5. 配置校准寄存器(MPWLDECTRL, MPRDDLCTL等)
6. 使能MMDC控制器
7. 执行ZQ校准

7.5 运行效果

DDR3初始化成功后,程序可使用0x80000000~0xA0000000地址范围(512MB)。


八、实验7: RGB LCD显示实验

8.1 实验目的

  • 掌握RGB LCD接口时序
  • 实现像素点绘制和字符显示
  • 理解eLCDIF控制器配置

8.2 LCD核心概念

分辨率: 1080P=1920×1080,2K=2560×1440

像素格式:

格式 位宽 字节 说明
RGB565 16bit 2B R5+G6+B5,STM32常用
RGB888 24bit 3B R8+G8+B8
ARGB8888 32bit 4B A8+R8+G8+B8,本实验使用

RGB LCD信号线:

信号线 说明
R[7:0]/G[7:0]/B[7:0] 24根数据线
DE 数据使能
VSYNC 垂直同步
HSYNC 水平同步
PCLK 像素时钟

8.3 关键代码

/* LCD初始化 */
void lcd_init(void)
{
    /* 1、初始化eLCDIF控制器 */
    /* 2、配置LCD时序参数 */
    /* 3、设置帧缓冲地址 */
    /* 4、使能LCD控制器 */
}

/* 绘制像素点 */
void lcd_drawpoint(unsigned int x, unsigned int y, unsigned int color)
{
    unsigned int *fb = (unsigned int *)FRAME_BUFFER_ADDR;
    fb[y * LCD_WIDTH + x] = color;
}

/* 填充矩形 */
void lcd_fill(unsigned int x0, unsigned int y0,
              unsigned int x1, unsigned int y1, unsigned int color)
{
    unsigned int x, y;
    for(y = y0; y < y1; y++) {
        for(x = x0; x < x1; x++) {
            lcd_drawpoint(x, y, color);
        }
    }
}

8.4 运行效果

LCD屏幕显示彩色测试图案和字符信息。


九、实验8: RTC实时时钟实验

9.1 实验目的

  • 掌握I.MX6U SNVS模块配置
  • 实现年/月/日/时/分/秒时间读写
  • 理解RTC后备电池供电机制

9.2 SNVS架构

I.MX6U的RTC位于SNVS(Secure Non-Volatile Storage)模块内:

  • SNVS_HP: 高功耗域,主电源供电
  • SNVS_LP: 低功耗域,后备电池供电,包含SRTC
  • SRTC本质是一个32位秒计数器,外接32.768KHz晶振
  • 以1970-01-01 00:00:00为基准时间

9.3 关键代码

void rtc_init(void)
{
    /* 1、使能SNVS时钟 */
    CCM->CCGR4 |= (3 << 20);

    /* 2、配置SNVS_LP SRTC */
    SNVS->LPCR &= ~(1 << 0);  /* 先关闭SRTC */
    while(SNVS->LPCR & (1 << 0)); /* 等待关闭 */

    /* 3、设置初始时间(2026-09-17 12:00:00) */
    rtc_settime(2026, 9, 17, 12, 0, 0);

    /* 4、使能SRTC */
    SNVS->LPCR |= (1 << 0);
    while(!(SNVS->LPCR & (1 << 0))); /* 等待使能 */
}

void rtc_settime(int year, int month, int day, int hour, int min, int sec)
{
    /* 将日期时间转换为秒数 */
    unsigned int seconds = timestamp_from_ymd(year, month, day, hour, min, sec);

    /* SRTC计数器是32位(非47位) */
    SNVS->LPSRTCMR = (seconds >> 17) & 0x7FFF;  /* 高15位 */
    SNVS->LPSRTCLR = (seconds << 15) & 0xFFFF8000; /* 低17位 */
}

9.4 寄存器注意事项

NXP参考手册中SNVS_SRTCMR和SNVS_SRTCLR的描述有误:

  • SRTC计数器实际是32位(非47位)
  • SNVS_SRTCMR的bit14:0是高15位
  • SNVS_SRTCLR的bit31:bit15是低17位

9.5 运行效果

LCD显示实时时间,秒数持续递增。


十、实验9: I2C实验

10.1 实验目的

  • 掌握I2C总线协议时序
  • 驱动AP3216C距离/光线/红外传感器
  • 理解I2C读写操作流程

10.2 I2C协议要点

要素 说明
总线 SCL(时钟) + SDA(数据),需4.7K上拉
速率 标准模式100Kb/s,快速模式400Kb/s
寻址 7位地址,支持多从机
时序 起始位→设备地址→ACK→数据→停止位

写时序: START → 设备地址+W → ACK → 寄存器地址 → ACK → 数据 → ACK → STOP

读时序: START → 设备地址+W → ACK → 寄存器地址 → ACK → 重复START → 设备地址+R → ACK → 数据 → NACK → STOP

10.3 关键代码

void i2c_write_reg(unsigned char addr, unsigned char reg, unsigned char data)
{
    /* 1、发送起始信号 */
    i2c_start();

    /* 2、发送设备地址 + 写位 */
    i2c_send_byte((addr << 1) | 0);
    i2c_wait_ack();

    /* 3、发送寄存器地址 */
    i2c_send_byte(reg);
    i2c_wait_ack();

    /* 4、发送数据 */
    i2c_send_byte(data);
    i2c_wait_ack();

    /* 5、发送停止信号 */
    i2c_stop();
}

unsigned char i2c_read_reg(unsigned char addr, unsigned char reg)
{
    unsigned char data;

    i2c_start();
    i2c_send_byte((addr << 1) | 0);  /* 写 */
    i2c_wait_ack();
    i2c_send_byte(reg);
    i2c_wait_ack();

    i2c_start();                       /* 重复起始 */
    i2c_send_byte((addr << 1) | 1);  /* 读 */
    i2c_wait_ack();
    data = i2c_read_byte(0);          /* NACK + STOP */
    i2c_stop();

    return data;
}

10.4 运行效果

读取AP3216C的光强度、距离、红外数据,通过LCD显示。


十一、实验10: SPI实验

11.1 实验目的

  • 掌握SPI总线协议(四线全双工)
  • 驱动ICM-20608六轴传感器
  • 理解SPI四种工作模式

11.2 SPI协议要点

要素 说明
总线 CS(片选) + SCK(时钟) + MOSI(主出) + MISO(主入)
速率 最高可达几十MHz,远超I2C
模式 CPOL+CPHA组合,4种模式
通信 全双工,收发同时进行
寻址 通过CS片选,无需发送设备地址

SPI四种模式:

模式 CPOL CPHA 空闲电平 采样沿
Mode 0 0 0 上升沿
Mode 1 0 1 下降沿
Mode 2 1 0 下降沿
Mode 3 1 1 上升沿

11.3 关键代码

void spi_init(void)
{
    /* 1、配置ECSPI3为主模式 */
    ECSPI3->CONREG = 0;                /* 先关闭 */
    ECSPI3->CONREG |= (7 << 24);      /* 突发长度=8bit */
    ECSPI3->CONREG |= (0 << 18);      /* 通道0 */
    ECSPI3->CONREG |= (0 << 16);      /* 不关心SPI_RDY */
    ECSPI3->CONREG |= (0 << 12);      /* 预分频=1 */
    ECSPI3->CONREG |= (0 << 8);       /* 分频=1 */
    ECSPI3->CONREG |= (1 << 4);       /* 通道0主模式 */
    ECSPI3->CONREG |= (0 << 3);       /* SMC=0 */
    ECSPI3->CONREG |= (1 << 2);       /* XCH=1,开始突发 */
    ECSPI3->CONREG |= (1 << 0);       /* 使能SPI */

    /* 2、配置SPI模式(CPOL=1, CPHA=1 → Mode 3) */
    ECSPI3->CONFIGREG = 0;
    ECSPI3->CONFIGREG |= (3 << 4);     /* 模式3 */
}

unsigned char spi_transfer(unsigned char data)
{
    unsigned int dummy;

    while(!(ECSPI3->STATUSREG & (1 << 0)));  /* 等待TXFIFO空 */
    ECSPI3->TXDATA = data;                    /* 发送数据 */

    while(!(ECSPI3->STATUSREG & (1 << 3)));  /* 等待RXFIFO满 */
    dummy = ECSPI3->RXDATA;                   /* 读取数据 */

    return (unsigned char)dummy;
}

11.4 运行效果

读取ICM-20608的加速度、陀螺仪、温度数据,通过LCD显示。


十二、裸机开发技巧

12.1 调试方法

方法 工具 适用场景
LED指示 GPIO输出 最基础的状态指示
串口打印 UART printf 信息输出最常用
JTAG/SWD 调试器+IDE 单步调试、变量查看
寄存器查看 Memory窗口 验证配置是否正确
逻辑分析仪 Saleae等 时序分析

12.2 代码规范

/* 1. 寄存器操作使用位域宏 */
#define BIT(n)          (1 << (n))
#define SET_BIT(reg, n) ((reg) |= BIT(n))
#define CLR_BIT(reg, n) ((reg) &= ~BIT(n))
#define GET_BIT(reg, n) (((reg) >> (n)) & 1)

/* 2. 寄存器地址使用宏定义 */
#define GPIO1_DR        (*(volatile unsigned int *)0x0209C000)

/* 3. 模块化驱动结构 */
/* bsp_xxx.h → 函数声明、类型定义 */
/* bsp_xxx.c → 函数实现 */
/* main.c   → 业务逻辑调用 */

/* 4. 头文件保护 */
#ifndef __BSP_XXX_H
#define __BSP_XXX_H
/* ... */
#endif

12.3 性能优化

  • 避免函数调用开销: 关键路径使用内联函数
  • 减少内存访问: 使用寄存器缓存变量
  • DMA传输: 大数据量外设使用DMA
  • 编译优化: 使用-O2/-O3优化选项
  • Cache使能: ARM Cortex-A7支持I-Cache和D-Cache

12.4 链接脚本模板

SECTIONS {
    . = 0x87800000;           /* 链接地址 */

    .text : {
        *(.text)               /* 代码段 */
        *(.rodata)             /* 只读数据 */
    }

    .data : {
        *(.data)               /* 初始化数据 */
    }

    .bss : {
        __bss_start = .;
        *(.bss)                /* 未初始化数据 */
        __bss_end = .;
    }
}

十三、跨平台对比

13.1 I.MX6ULL vs STM32 vs RK3568裸机差异

特性 I.MX6ULL STM32F103 RK3568
内核架构 Cortex-A7 Cortex-M3 Cortex-A55
主频 528MHz 72MHz 1.8GHz
内存管理 MMU(可选) 无MMU MMU
GPIO命名 GPIO1_IO00~IO31 PA0~15 GPIO0_A0~D7
寄存器操作 直接访问 HAL库封装 直接访问
启动方式 Boot ROM→IVT→DCD 向量表→Reset_Handler Boot ROM→TPL→SPL
DDR配置 DCD表自动初始化 无需(片内SRAM) TPL/SPL初始化
GPIO数量 124个 51个 156个
时钟配置 CCM+PLL RCC CRU
中断控制器 GIC NVIC GIC-500
开发难度 中等 简单 较高

13.2 寄存器操作对比

STM32 (HAL库):

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);

I.MX6ULL (寄存器):

GPIO1->DR |= (1 << 3);   /* 输出高电平 */
GPIO1->DR &= ~(1 << 3);  /* 输出低电平 */

RK3568 (寄存器):

GPIO0->PORT_DR |= BIT(0);   /* 输出高电平 */
GPIO0->PORT_DR &= ~BIT(0);  /* 输出低电平 */

13.3 关键差异总结

  • I.MX6U: GPIO以功能命名,需配置IOMUXC,Boot ROM提供DCD自动初始化DDR
  • STM32: GPIO以端口+引脚命名,HAL库封装好,NVIC中断控制器简单
  • RK3568: 更复杂,支持Linux启动,裸机开发较少使用,通常用于启动Linux内核

十四、面试精选

Q1: 请解释I.MX6U的GPIO初始化流程,与STM32有何不同?

答案:

I.MX6U GPIO初始化需要4步:

  1. 时钟使能: 配置CCM_CCGR寄存器对应位(STM32是RCC->APB2ENR等)
  2. 引足复用: 配置IOMUXC_SW_MUX_CTL_PAD_XXX的MUX_MODE位,选择ALT0~ALT8(STM32是GPIOx->AFRL/AFRH)
  3. 电气属性: 配置IOMUXC_SW_PAD_CTL_PAD_XXX,设置上拉/下拉/驱动强度/压摆率(STM32是GPIOx->OSPEEDR/PUPDR等)
  4. 功能配置: 配置GPIOx_GDIR(方向)和GPIOx_DR(数据)

关键差异:I.MX6U的GPIO复用是通过IOMUXC控制器集中管理的,而STM32是通过AFR寄存器分散管理的。


Q2: 裸机开发中如何实现精确延时?请对比不同方法的优缺点。

答案:

方法 原理 优点 缺点
循环延时 空指令消耗CPU周期 实现简单 受编译器优化影响,精度差
SysTick Cortex-M专用24位计数器 精度高(1μs) 仅Cortex-M可用
EPIT 32位定时器+中断 精度高,可触发中断 中断开销
GPT 32位定时器轮询 精度高(1μs),无需中断 CPU忙等

I.MX6U推荐使用GPT定时器,配置ipg_clk(66MHz)为时钟源,66分频得到1MHz,计数器每加1表示1μs。


Q3: I2C和SPI有什么区别?各适用于什么场景?

答案:

特性 I2C SPI
线数 2根(SCL+SDA) 4根(CS+SCK+MOSI+MISO)
速率 最高400Kb/s 最高几十MHz
模式 半双工 全双工
寻址 软件发送设备地址 硬件CS片选
多设备 通过地址区分 通过CS片选
复杂度 时序复杂 时序简单

适用场景

  • I2C: 低速传感器(温湿度、加速度计)、EEPROM、配置寄存器
  • SPI: 高速设备(Flash、LCD控制器、高速ADC)、需要全双工通信

Q4: DDR3初始化为什么可以不在代码中手动配置?

答案:

I.MX6U的Boot ROM在启动时会读取IVT(Image Vector Table)中的DCD(Data Configuration Data)表。DCD表存储在SD卡/eMMC的特定位置,包含了DDR控制器的所有寄存器配置和时序参数。Boot ROM读取DCD表后自动完成DDR初始化,因此用户代码中无需手动编写DDR初始化代码。

但需要注意:

  1. DCD表必须正确配置,否则DDR无法使用
  2. 在SPL阶段可能需要重新校准DDR参数
  3. 如果不使用Boot ROM启动(如JTAG调试),则需要手动初始化DDR

Q5: 解释I.MX6U的SNVS模块架构,为什么RTC需要后备电池?

答案:

I.MX6U的SNVS(Secure Non-Volatile Storage)分为两个子模块:

  • SNVS_HP(高功耗域): 由系统主电源供电,主电源掉电后关闭
  • SNVS_LP(低功耗域): 由后备电池(VDD_SNVS_IN)供电,主电源掉电后继续运行

RTC(SRTC)位于SNVS_LP中,需要32.768KHz晶振提供时钟。即使系统主电源关闭,SNVS_LP仍由纽扣电池供电继续计时。这保证了:

  1. 系统断电后时间不丢失
  2. 不需要每次开机都设置时间
  3. 支持低功耗唤醒功能

十五、实验总结

15.1 实验知识图谱

汇编LED → C语言LED → 蜂鸣器 → 按键输入
    ↓           ↓          ↓         ↓
GPIO输出    模块化驱动   三极管驱动  GPIO输入
                                ↓
高精度延时 ← GPT定时器
    ↓
DDR3 → LCD → RTC → I2C → SPI
 ↓      ↓      ↓     ↓     ↓
内存   显示   时间   传感器  传感器

15.2 学习路径建议

阶段 实验 目标
入门 汇编LED → C语言LED → 蜂鸣器 → 按键 掌握GPIO和基础驱动
进阶 高精度延时 → DDR3 → LCD 理解定时器和显示
综合 RTC → I2C → SPI 掌握通信协议
实战 串联所有实验,构建完整系统 集成能力

15.3 下一步学习方向

  1. 中断系统: GIC中断控制器、GPIO中断、定时器中断
  2. UART串口: 串口通信、printf重定向
  3. DMA传输: 高效数据搬运
  4. 触摸屏: I2C触摸 + LCD显示
  5. PWM: 背光控制、电机驱动
  6. ADC: 模拟信号采集

参考文献:

  • 《I.MX6ULL参考手册》第32章 IOMUX Controller
  • 《I.MX6ULL参考手册》第14章 GPT Timer
  • 《I.MX6UL参考手册》第46章 SNVS
  • 《I.MX6ULL参考手册》第30章 ECSPI