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


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

  • "第八章 汇编LED灯实验.pdf"
  • "第十章 C语言版LED灯实验.pdf"
  • "第十二章 官方SDK移植实验.pdf"
  • "第十三章 BSP工程管理实验.pdf"
  • "第十四章 蜂鸣器实验.pdf"
  • "第十五章 按键输入实验.pdf"
  • "第十六章 主频和时钟配置实验.pdf"
  • "第十七章 GPIO中断实验.pdf"
  • "第十八章 EPIT定时器实验.pdf"
  • "第十九章 定时器按键消抖实验.pdf"
  • "第二十章 高精度延时实验.pdf"
  • "第二十一章 UART串口通信实验.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 在 IOMUXC 中有独立 GPIO 命名
GPIO2 22个 0x020A0000 其余 IO 需先经 IOMUXC 复用(MUX_MODE=ALT5)为 GPIO
GPIO3 29个 0x020A4000
GPIO4 29个 0x020A8000
GPIO5 12个 0x020AC000 包含 SNVS 域 IO(如 SNVS_TAMPER1)

5 组 GPIO 共 124 个 IO(32+22+29+29+12)。每组都有 8 个寄存器:DR(数据)、GDIR(方向)、PSR(状态)、ICR1/ICR2(中断配置,每个 IO 2 位)、IMR(中断屏蔽)、ISR(中断状态,写 1 清零)、EDGE_SEL(边沿选择,置 1 为双边沿,覆盖 ICR)。ICR 触发方式:00 低电平、01 高电平、10 上升沿、11 下降沿。

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时钟:GPIO1时钟由 CCM_CCGR1 的 bit27:26 控制
    @ 注意:0x020C4068 是 CCM_CCGR0,CCM_CCGR1 应为 0x020C406C
    ldr r0, =0x020C406C     @ CCM_CCGR1寄存器地址
    ldr r1, [r0]
    orr r1, r1, #(3 << 26)  @ CG13 位[27:26]=11,使能GPIO1时钟
    str r1, [r0]
    @ (教材例程更简单:CCGR0~CCGR6 从 0x020C4068 起每 4 字节一个,全部写 0xFFFFFFFF)

    @ 配置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函数

main.h(以宏定义寄存器地址):

#define CCM_CCGR0   *((volatile unsigned int *)0x020C4068)   /* 注意 CCGR0=0x020C4068 */
#define CCM_CCGR1   *((volatile unsigned int *)0x020C406C)   /*      CCGR1=0x020C406C */
#define CCM_CCGR2   *((volatile unsigned int *)0x020C4070)
#define CCM_CCGR3   *((volatile unsigned int *)0x020C4074)
#define CCM_CCGR4   *((volatile unsigned int *)0x020C4078)
#define CCM_CCGR5   *((volatile unsigned int *)0x020C407C)
#define CCM_CCGR6   *((volatile unsigned int *)0x020C4080)
#define SW_MUX_GPIO1_IO03  *((volatile unsigned int *)0x020E0068)
#define SW_PAD_GPIO1_IO03  *((volatile unsigned int *)0x020E02F4)
#define GPIO1_DR    *((volatile unsigned int *)0x0209C000)
#define GPIO1_GDIR  *((volatile unsigned int *)0x0209C004)

C语言主函数 main.c:

#include "main.h"

void clk_enable(void)              /* 使能所有外设时钟 */
{
    CCM_CCGR0 = 0xffffffff;
    CCM_CCGR1 = 0xffffffff;
    CCM_CCGR2 = 0xffffffff;
    CCM_CCGR3 = 0xffffffff;
    CCM_CCGR4 = 0xffffffff;
    CCM_CCGR5 = 0xffffffff;
    CCM_CCGR6 = 0xffffffff;
}

void led_init(void)                /* 初始化 LED 所用 GPIO */
{
    SW_MUX_GPIO1_IO03 = 0x5;       /* 复用为 GPIO1_IO03(ALT5) */
    SW_PAD_GPIO1_IO03 = 0x10B0;    /* IO 电气属性 */
    GPIO1_GDIR = 0x8;              /* bit3=1,设置为输出 */
    GPIO1_DR = 0x0;               /* 输出低电平,打开 LED0 */
}

int main(void)
{
    clk_enable();                  /* 使能所有的时钟 */
    led_init();                    /* 初始化 LED */

    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);
}

按键驱动 bsp_key.c(教材第15章):

void key_init(void)
{
    gpio_pin_config_t key_config;

    /* 1、初始化 IO 复用,复用为 GPIO1_IO18 */
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);

    /* 2、配置 UART1_CTS_B 的 IO 属性:0xF080
     * bit[15:14]=11 默认 22K 上拉  bit[13]=1 pull  bit[12]=1 pull/keeper 使能
     * bit[5:3]=000 关闭输出        bit[0]=0 低转换率
     */
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);

    /* 3、初始化 GPIO,GPIO1_IO18 设置为输入 */
    key_config.direction = kGPIO_DigitalInput;
    gpio_init(GPIO1, 18, &key_config);
}

int key_getvalue(void)          /* 0:无按键,其他值:对应按键值 */
{
    int ret = 0;
    static unsigned char release = 1;              /* 按键已松开标志 */

    if((release == 1) && (gpio_pinread(GPIO1, 18) == 0))   /* KEY0 按下 */
    {
        delay(10);                                  /* 延时消抖约 10ms(教材第57行) */
        release = 0;                                /* 标记按键按下 */
        if(gpio_pinread(GPIO1, 18) == 0)
            ret = KEY0_VALUE;
    }
    else if(gpio_pinread(GPIO1, 18) == 1)           /* KEY0 未按下 */
    {
        ret = 0;
        release = 1;                                /* 标记按键释放 */
    }
    return ret;
}

按键检测逻辑(main.c):

int main(void)
{
    int keyvalue = 0;
    unsigned char led_state = OFF;
    unsigned char beep_state = OFF;

    clk_enable();
    led_init();
    beep_init();
    key_init();

    while(1)
    {
        keyvalue = key_getvalue();
        if(keyvalue)
        {
            switch (keyvalue)
            {
                case KEY0_VALUE:
                    beep_state = !beep_state;      /* 翻转蜂鸣器 */
                    beep_switch(beep_state);
                    break;
            }
        }
        delay(10);
    }
}

说明:教材的 key_init 把 UART1_CTS 配置为 22K 上拉(0xF080),key_getvalue 用静态变量 release 记录按键是否已释放,并用 delay(10) 做约 10ms 的软件消抖——这是第19章「定时器按键消抖」要优化的对象。

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;                        /* 清零 */
    GPT1->CR = 1 << 15;                 /* bit15=1 进入软复位 */
    while((GPT1->CR >> 15) & 0x01);     /* 等待复位完成 */

    /* CLKSRC(bit8:6)=001 选择 ipg_clk=66MHz;FRR(bit9)=0 为 restart 模式 */
    GPT1->CR = (1 << 6);

    GPT1->PR = 65;                       /* 66 分频,GPT1 时钟 = 66M/(65+1)=1MHz */
    GPT1->OCR[0] = 0xFFFFFFFF;           /* 不使用比较中断,设为最大值 */
    GPT1->CR |= 1 << 0;                  /* 使能 GPT1 */
}

/* 微秒级延时(教材 delayus) */
void delayus(unsigned int usdelay)
{
    unsigned long oldcnt, newcnt;
    unsigned long tcntvalue = 0;         /* 走过的总时间 */

    oldcnt = GPT1->CNT;
    while(1)
    {
        newcnt = GPT1->CNT;
        if(newcnt != oldcnt)
        {
            if(newcnt > oldcnt)                       /* 向上计数且未溢出 */
                tcntvalue += newcnt - oldcnt;
            else                                      /* 发生溢出回滚 */
                tcntvalue += 0xFFFFFFFF - oldcnt + newcnt;
            oldcnt = newcnt;
            if(tcntvalue >= usdelay)                  /* 延时时间到 */
                break;
        }
    }
}

/* 毫秒级延时(教材 delayms) */
void delayms(unsigned int msdelay)
{
    int i = 0;
    for(i = 0; i < msdelay; i++)
        delayus(1000);
}

6.4 运行效果

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

6.5 延时精度对比

延时方式 精度 原理 适用场景
循环延时 ms级 编译器优化影响大 仅用于简单等待
SysTick μs级 Cortex-M专用 STM32系列
GPT定时器 1μs 硬件计数(读 GPTx_CNT) I.MX6U 高精度延时(本章选用)
EPIT定时器 32位向下计数 + 比较中断 周期中断、定时器消抖,不做延时

七、实验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 MMDC 控制器与 DDR3 初始化流程

  • MMDC 基地址MMDC = 0x021B0000。MMDC 包含内核(MMDC_CORE)和 PHY(MMDC_PHY),PHY 负责时序调整与校准,保证数据在 400MHz 被正确采样。
  • DDR 时钟源(PDF 23.3.3):MMDC 时钟为 400MHz(实际 396MHz),来自 PLL2_PFD2
    1. CCM_CBCMR.PRE_PERIPH2_CLK_SEL(bit22:21)=01 → 选 PLL2_PFD2 作为 pre_periph2;
    2. CCM_CBCDR.PERIPH2_CLK_SEL(bit26)=0 → periph2_clk 选 pll2_main_clk;
    3. CCM_CBCDR.FABRIC_MMDC_PODF(bit5:3)=0 → 1 分频,得到 MMDC 时钟 ≈396MHz。

NXP 官方工具流程(教材第23章):

  1. I.MX6UL_DDR3_Script_Aid_V0.02.xlsx 填写 DDR3 参数(容量、位宽、行/列地址、tRCD/tRC/tRAS 等),生成 .inc 初始化脚本(全是“寄存器地址=寄存器值”);
  2. ddr_stress_testerDDR_Tester.exe)经 USB OTG 把脚本下载到开发板;
  3. 先做 校准(Calibration),得到 6 个校准寄存器的值;
  4. 用校准值更新 .inc 后重新加载、下载;
  5. 超频测试(Stress Test)——能稳定超频 10%~15% 即认为 DDR 硬件走线正常。

校准得到的 6 个寄存器(教材示例代码 23.5.3.1):

寄存器名 地址
MMDC_MPWLDECTRL0 0x021B080C
MMDC_MPWLDECTRL1 0x021B0810
MPDGCTRL0 PHY0 0x021B083C
MPDGCTRL1 PHY0 0x021B0840
MPRDDLCTL PHY0 0x021B0848
MPWRDLCTL PHY0 0x021B0850

DDR3 关键时间参数(NT5CC256M16EP-EK): tRCD=tRP=CL=13.91ns(CL-tRCD-tRP = 13-13-13)、tRC=47.91ns、tRAS=34ns。

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 eLCDIF 关键寄存器(PDF 24.1.2)

eLCDIF 基地址 LCDIF = 0x021C8000,支持 MPU / VSYNC / DOTCLK 三种接口,驱动 RGB 屏使用 DOTCLK 接口。

寄存器 作用
LCDIF_CTRL 控制:SFTRST(bit31) 软复位、MASTER(bit5) 主模式、RUN(bit0) 使能、DATA_FORMAT_16_BIT(bit3) 选 ARGB555/RGB565、bit11:10 总线宽度、bit9:8 数据宽度
LCDIF_CTRL1 BYTE_PACKING_FORMAT(bit19:16),ARGB8888 下设为 0x7
LCDIF_TRANSFER_COUNT bit31:16 = 帧高 V_COUNT,bit15:0 = 帧宽 H_COUNT
LCDIF_VDCTRL0 VSYNC/DOTCLK 控制:VSYNC_PULSE_WIDTH(bit17:0) 设置 VSPW,bit28 使能 ENABLE 输出等
LCDIF_VDCTRL1 VSYNC 总周期 = 屏高 + VSPW + VBP + VFP
LCDIF_VDCTRL2 bit31:18 = HSPW;bit17:0 = HSYNC 总周期 = 屏宽 + HSPW + HBP + HFP
LCDIF_VDCTRL3 bit27:16 = HBP+HSPW;bit15:0 = VBP+VSPW
LCDIF_VDCTRL4 bit18 置 1(使用 VSYNC/HSYNC/DOTCLK);bit17:0 = 屏宽
LCDIF_CUR_BUF / LCDIF_NEXT_BUF 当前/下一帧缓存地址

8.4 ATK-7016(1024×600)时序参数(教材例程)

| 参数 | 含义 | 值 | | ---- | ---- | -- | | width / height | 屏宽 / 屏高 | 1024 / 600 | | pixsize | 每像素字节数(ARGB8888) | 4 | | hspw | HSYNC 信号宽度 | 20 | | hbpd | 水平同步后肩 HBP | 140 | | hfpd | 水平同步前肩 HFP | 160 | | vspw | VSYNC 信号宽度 | 3 | | vbpd | 帧同步后肩 VBP | 20 | | vfpd | 帧同步前肩 VFP | 12 | | framebuffer | 显存首地址 | 0x89000000 |

帧周期:T = (VSPW+VBP+LINE+VFP) × (HSPW+HBP+HVALID+HFP)

8.5 关键代码

#define LCD_FRAMEBUF_ADDR   (0x89000000)   /* LCD 显存地址 */

struct tftlcd_typedef {
    unsigned short height, width;
    unsigned char  pixsize;
    unsigned short vspw, vbpd, vfpd;
    unsigned short hspw, hbpd, hfpd;
    unsigned int   framebuffer, forecolor, backcolor;
} tftlcd_dev;

void lcd_init(void)
{
    lcdgpio_init();           /* 初始化 IO,复用为 eLCDIF */
    lcdclk_init(32, 3, 5);    /* 初始化 LCD 时钟 */
    lcd_reset();  delayms(10);  lcd_noreset();

    tftlcd_dev.height  = 600;
    tftlcd_dev.width   = 1024;
    tftlcd_dev.pixsize = 4;               /* ARGB8888,每像素 4 字节 */
    tftlcd_dev.vspw = 3;    tftlcd_dev.vbpd = 20;  tftlcd_dev.vfpd = 12;
    tftlcd_dev.hspw = 20;   tftlcd_dev.hbpd = 140; tftlcd_dev.hfpd = 160;
    tftlcd_dev.framebuffer = LCD_FRAMEBUF_ADDR;

    LCDIF->CTRL |= (1<<19)|(1<<17)|(3<<10)|(3<<8)|(1<<5);  /* DOTCLK、24位总线/数据、主模式 */
    LCDIF->CTRL1 = 0x7 << 16;                              /* ARGB 模式,A 通道不传输 */
    LCDIF->TRANSFER_COUNT = (tftlcd_dev.height << 16) | tftlcd_dev.width;
    LCDIF->VDCTRL0 = (1<<28)|(1<<24)|(1<<21)|(1<<20)|(tftlcd_dev.vspw << 0);
    LCDIF->VDCTRL1 = tftlcd_dev.height + tftlcd_dev.vspw + tftlcd_dev.vfpd + tftlcd_dev.vbpd;
    LCDIF->VDCTRL2 = (tftlcd_dev.hspw << 18) |
                     (tftlcd_dev.width + tftlcd_dev.hspw + tftlcd_dev.hfpd + tftlcd_dev.hbpd);
    LCDIF->VDCTRL3 = ((tftlcd_dev.hbpd + tftlcd_dev.hspw) << 16) |
                     (tftlcd_dev.vbpd + tftlcd_dev.vspw);
    LCDIF->VDCTRL4 = (1 << 18) | tftlcd_dev.width;
    LCDIF->CUR_BUF = LCDIF->NEXT_BUF = (unsigned int)tftlcd_dev.framebuffer;
    lcd_enable();             /* LCDIF->CTRL |= (1 << 0) */
}

/* 绘制像素点(ARGB8888,每像素 4 字节) */
void lcd_drawpoint(unsigned int x, unsigned int y, unsigned int color)
{
    *(unsigned int *)(tftlcd_dev.framebuffer + 4 * (y * tftlcd_dev.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);
}

显存 0x89000000 对 256MB(0x80000000~0x90000000)和 512MB(0x80000000~0xA0000000)两款 DDR 都适用。教材另提供 bsp_lcdapi.c,封装 lcd_show_stringlcd_shownum 等字符/数字显示函数(依赖字库)。

8.6 运行效果

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 关键代码

SNVS 基地址 SNVS = 0x020CC000,SRTC 位于 SNVS_LP。相关寄存器:SNVS_HPCOMR(NPSWA_EN bit31)、SNVS_LPCR(SRTC_ENV bit0)、SNVS_LPSRTCMR/SNVS_LPSRTCLR(秒数)。

/* 教材 bsp_rtc.c(I.MX6ULL 的 MCIMX6Y2.h 需手动补齐 SNVS_Type 寄存器) */
void rtc_init(void)
{
    /* HPCOMR 的 NPSWA_EN(bit31) 必须置 1,否则非特权软件无法访问 SNVS 寄存器 */
    SNVS->HPCOMR |= (1 << 31);

    /* 首次使用可设置时间(教材中此段用 #if 0 屏蔽,实际由 KEY0 触发设置) */
    rtc_enable();
}

void rtc_enable(void)
{
    SNVS->LPCR |= (1 << 0);              /* SRTC_ENV=1 使能计数器 */
    while(!(SNVS->LPCR & 0x01));         /* 等待使能完成 */
}

void rtc_disable(void)
{
    SNVS->LPCR &= ~(1 << 0);             /* 关闭 RTC */
    while(SNVS->LPCR & 0x01);            /* 等待关闭完成 */
}

/* 设置日期时间:SRTC 计数器实际为 32 位 */
void rtc_setdatetime(struct rtc_datetime *datetime)
{
    unsigned int seconds = rtc_coverdate_to_seconds(datetime);
    unsigned int tmp = SNVS->LPCR;

    rtc_disable();                                   /* 修改前先关闭 RTC */
    SNVS->LPSRTCMR = (unsigned int)(seconds >> 17);  /* 高 15 位 */
    SNVS->LPSRTCLR = (unsigned int)(seconds << 15);  /* 低 17 位(bit31:15) */
    if(tmp & 0x1) rtc_enable();                      /* 之前开着则重新打开 */
}

/* 读取当前秒数 */
unsigned int rtc_getseconds(void)
{
    return (SNVS->LPSRTCMR << 17) | (SNVS->LPSRTCLR >> 15);
}

教材 bsp_rtc.c 共 9 个函数rtc_initrtc_enablertc_disablertc_isleapyearrtc_coverdate_to_secondsrtc_setdatetimertc_convertseconds_to_datetimertc_getsecondsrtc_getdatetime。时间基准为 1970-01-01,年份范围 1970~2099。

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 I2C 寄存器(PDF 26.1.2)

I2C1 基地址 I2C1 = 0x021A0000。I.MX6U 使用硬件 I2C 控制器,关键寄存器:

寄存器 关键位 说明
I2Cx_IADR ADR(bit7:1) I2C 从设备地址
I2Cx_IFDR IC(bit5:0) 波特率分频(档位固定,IC=0x15 为 640 分频)
I2Cx_I2CR IEN(bit7)/IIEN(bit6)/MSTA(bit5)/MTX(bit4)/TXAK(bit3)/RSTA(bit2) 使能/中断/主从/方向/应答/重复起始
I2Cx_I2SR ICF(bit7)/IAAS(bit6)/IBB(bit5)/IAL(bit4)/SRW(bit2)/IIF(bit1)/RXAK(bit0) 状态寄存器
I2Cx_I2DR 低 8 位 收发数据

时钟源为 IPG_CLK_ROOT=66MHz;设 100KHz:IC=0x15(640 分频),66MHz/640=103.125KHz≈100KHz。

10.4 关键代码(教材 bsp_i2c.c)

void i2c_init(I2C_Type *base)
{
    base->I2CR &= ~(1 << 7);        /* 要访问 I2C 寄存器,先关闭 I2C */
    base->IFDR = 0x15 << 0;         /* 640 分频 → ≈100KHz */
    base->I2CR |= (1 << 7);         /* 使能 I2C */
}

/* 发送开始信号(顺带发送从设备地址 + 读写位) */
unsigned char i2c_master_start(I2C_Type *base, unsigned char address,
                               enum i2c_direction direction)
{
    if(base->I2SR & (1 << 5)) return 1;          /* 总线忙,返回错误 */
    base->I2CR |= (1 << 5) | (1 << 4);           /* MSTA=1 主模式,MTX=1 发送 */
    base->I2DR = ((unsigned int)address << 1) |
                 ((direction == kI2C_Read) ? 1 : 0);
    return 0;
}

i2c_master_repeated_start 通过 I2CR |= (1<<4)|(1<<2)(MTX=1、RSTA=1)产生重复起始。教材 bsp_i2c.c 共 8 个函数:i2c_initi2c_master_starti2c_master_repeated_starti2c_check_and_clear_errori2c_master_stopi2c_master_writei2c_master_readi2c_master_transfer

10.5 AP3216C 驱动(PDF 26.1.3)

  • 器件地址 0x1E;I2C1 引脚为 I2C1_SCL=UART4_TXD、I2C1_SDA=UART4_RXD,复用值 1,电气属性 0x70B0。
  • 寄存器:0x00 系统模式(写 0x04 软复位,写 0x03 开 ALS+PS+IR)、0x0A~0x0F 数据寄存器(IR/ALS/PS)。
  • AP3216C 没有 ID 寄存器,教材用“向 0x00 写 0x03 再读回判断是否相等”来检测芯片是否存在。
  • 同时打开 ALS+PS+IR 时,两次读取间隔需 ≥112.5ms。
  • 数据解析:*ir = (buf[1]<<2)|(buf[0]&0x03)*als = (buf[3]<<8)|buf[2]*ps = ((buf[5]&0x3F)<<4)|(buf[4]&0x0F)

10.6 运行效果

读取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 ECSPI 寄存器(PDF 27.1.2)

ECSPI3 基地址 ECSPI3 = 0x02010000。I.MX6U 的 SPI 外设叫 ECSPI,收发各带 32×64 位 FIFO。

寄存器 关键位 说明
ECSPIx_CONREG BURST_LENGTH(bit31:20)/CHANNEL_SELECT(bit19:18)/DRCTL(bit17:16)/PRE_DIVIDER(bit15:12)/POST_DIVIDER(bit11:8)/CHANNEL_MODE(bit7:4)/SMC(bit3)/XCH(bit2)/EN(bit0) 控制寄存器
ECSPIx_CONFIGREG HT_LENGTH(bit28:24)/SCLK_CTL(bit23:20)/DATA_CTL(bit19:16)/SS_POL(bit15:12)/SCLK_POL(bit7:4)/SCLK_PHA(bit3:0) 通道极性/相位(各 4 位对应通道 3~0)
ECSPIx_PERIODREG CSD_CTL(bit21:16)/CSRC(bit15)/SAMPLE_PERIOD(bit14:0) 采样周期/片选延时
ECSPIx_STATREG TC(bit7)/RO(bit6)/RF(bit5)/RDR(bit4)/RR(bit3)/TF(bit2)/TDR(bit1)/TE(bit0) 状态寄存器
ECSPIx_TXDATA / ECSPIx_RXDATA 32 位 发送/接收数据

11.4 关键代码(教材 bsp_spi.c)

void spi_init(ECSPI_Type *base)
{
    /* 配置 CONREG:EN=1、SMC=1、通道0主模式、突发长度 8bit */
    base->CONREG = 0;
    base->CONREG |= (1 << 0) | (1 << 3) | (1 << 4) | (7 << 20);
    /*   bit0  EN=1 使能
     *   bit3  SMC=1,向 TXFIFO 写数据即启动 SPI 突发
     *   bit7:4 CHANNEL_MODE=1,通道0 主模式(ICM-20608 接 SS0)
     *   bit31:20 BURST_LENGTH=7,表示 8bit */

    /* 配置 CONFIGREG:通道0 PHA=0、POL=0(SPI Mode 0) */
    base->CONFIGREG = 0;

    /* PERIODREG:采样周期 0x2000;CSRC(bit15)=0 选择 SPI CLK 为采样时钟源 */
    base->PERIODREG = 0x2000;

    /* SPI CLK = (SourceCLK / (PRE_DIVIDER+1)) / 2^POST_DIVIDER
     * 源时钟 pll3_sw_clk/8 = 480/8 = 60MHz;PRE_DIVIDER=0x9 → 6MHz */
    base->CONREG &= ~((0xF << 12) | (0xF << 8));
    base->CONREG |= (0x9 << 12);
}

/* 通道0 收发一个字节(SPI 全双工) */
unsigned char spich0_readwrite_byte(ECSPI_Type *base, unsigned char txdata)
{
    uint32_t spirxdata = 0, spitxdata = txdata;

    base->CONREG &= ~(3 << 18);              /* 选择通道0 */
    base->CONREG |= (0 << 18);

    while((base->STATREG & (1 << 0)) == 0){} /* 等待 TXFIFO 空(TE=1) */
    base->TXDATA = spitxdata;

    while((base->STATREG & (1 << 3)) == 0){} /* 等待 RXFIFO 有数据(RR=1) */
    spirxdata = base->RXDATA;

    return (unsigned char)spirxdata;
}

11.5 ICM-20608 驱动要点(PDF 27.1.3)

  • 软件片选:CS 接 UART2_TX_DATA(GPIO1_IO20),拉低选中;SCLK=UART2_RXD、MISO=UART2_RTS、MOSI=UART2_CTS,电气属性 0x10B1。
  • 寄存器地址最高位为读写标志:写时 reg &= ~0x80,读时 reg |= 0x80,低 7 位才是真实地址。
  • ID 寄存器 0x75 WHO_AM_I:ICM-20608G=0xAF,ICM-20608D=0xAE。
  • 常用寄存器:0x6B PWR_MGMT_1(写 0x80 复位、0x01 关睡眠)、0x1B GYRO_CONFIG、0x1C ACCEL_CONFIG、0x3B~0x48 加速度/温度/陀螺仪数据。
  • 分辨率:陀螺仪 FS=±250/±500/±1000/±2000dps → 131/65.5/32.8/16.4;加速度 ±2/±4/±8/±16g → 16384/8192/4096/2048。
  • 使用浮点运算前需 imx6ul_hardfpu_enable() 开启 NEON 和硬件 FPU。

11.6 运行效果

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


十二、其余裸机实验速览

本节补充教材中、上文未展开的裸机实验要点(官方SDK移植、BSP工程管理、主频时钟、GPIO中断、EPIT定时器、定时器消抖、UART、printf移植)。

12.1 官方SDK移植(第12章)

  • 把 NXP 官方 SDK 中的 MCIMX6Y2.hfsl_common.hfsl_iomuxc.hcc.h 移植到 imx6ul/ 目录,用 imx6ul.h 统一包含。
  • 从此不再手写寄存器地址,改用 IOMUXC_SetPinMux() / IOMUXC_SetPinConfig()GPIO1->GDIR 等 SDK 风格接口。
  • cc.h 定义 __I/__O/__IOON/OFF 以及 uint8_t~uint64_t 等类型(第13章的 LED0 之外,ON/OFF 也来自这里)。

12.2 BSP工程管理(第13章)

  • 按功能分目录:bsp/{clk,delay,led,beep,gpio,key,exit,int,epittimer,keyfilter,uart,lcd,rtc,i2c,ap3216c,spi,icm20608}imx6ul/project/obj/
  • 通用 Makefile:INCDIRS/SRCDIRS 配合 wildcard/foreach/notdir/patsubst 自动搜集源文件,.o 统一输出到 obj/
  • 链接脚本 imx6ul.lds:链接地址 0x87800000.text 首行放 obj/start.o__bss_start/__bss_end 标记 BSS 段。

12.3 主频和时钟配置(第16章)

  • I.MX6U 默认主频 396MHz(Boot ROM 设置),本章提升到 528MHz
  • 核心时钟源为 PLL1(ARM_PLL):先切到 step_clk(24MHz osc) 再改 PLL1=1056MHz,最后 CCM_CACRR.ARM_PODF=2 分频得 528MHz。
  • 关键寄存器:CCM_CCSR(STEP_SEL / PLL1_SW_CLK_SEL)、CCM_ANALOG_PLL_ARMn(DIV_SELECT)、CCM_CACRR(ARM_PODF)、CCM_CBCDR/CCM_CBCMR
  • 其他 PLL:PLL2=528MHz(固定22倍频,分出 PLL2_PFD0~3)、PLL3=480MHz(固定20倍频,USB 用)。

12.4 GPIO中断实验(第17章)

  • Cortex-A7 只有 7 个异常向量,所有外设中断共用 IRQ 向量,需由 GIC 分发(GICD_* 分发器、GICC_* CPU 接口)。
  • 中断服务流程:int_init() 初始化 GIC → GIC_EnableIRQ(IRQn) 使能中断 → system_register_irqhandler(IRQn, handler, NULL) 注册处理函数。
  • GPIO 中断需配置 ICR1/ICR2(触发方式) 或 EDGE_SEL(双边沿)、IMR(使能)、ISR(状态,写 1 清零);KEY0 用 GPIO1_Combined_16_31_IRQn

12.5 EPIT定时器实验(第18章)

  • EPIT 是 32 位向下计数器,仅做周期中断;关键寄存器 EPITx_CR/SR/LR/CMPR/CNR,基地址 EPIT1=0x020D0000
  • 配置:CR 的 CLKSRC(bit25:24)=1(ipg_clk=66MHz)、PRESCALAR(bit15:4)、RLD(bit3)=1(set-and-forget)、OCIEN(bit2)=1、ENMOD(bit1)=1;LR 设初值,CMPR=0
  • 溢出时间 Tout = ((frac+1)×value)/Tclk;例程 epit1_init(0, 66000000/2) → 500ms 中断翻转 LED0。
  • 中断号 EPIT1_IRQnSR 的 OCIF(bit0) 写 1 清零

12.6 定时器按键消抖(第19章)

  • 思路:按键 GPIO 中断只负责重启 EPIT1(10ms),EPIT1 中断里再读 GPIO 电平确认有效按键,避免在中断里使用延时。
  • 关键函数:filterkey_init/filtertimer_init/filtertimer_restart/filtertimer_stop/filtertimer_irqhandler/gpio1_16_31_irqhandler;GPIO1_IO18,filtertimer_init(66000000/100)=10ms。
  • 这也是 Linux 按键驱动的标准做法。

12.7 UART串口通信实验(第21章)

  • UART1 基地址 0x02020000,寄存器 UCR1~UCR4/UFCR/USR1/USR2/URXD/UTXD/UBIR/UBMR/UTS
  • 时钟源 CCM_CSCDR1.UART_CLK_SEL 选 pll3_80m(80MHz),UFCR.RFDIV(bit9:7)=101 为 1 分频。
  • 波特率公式 Baud = Ref Freq × (UBIR+1) / (16 × (UBMR+1));115200 用 UBIR=71、UBMR=3124。
  • 配置顺序:关 UART → 软复位(UCR2.SRST 写 0,轮询等待自动置 1)→ 清 UCR1 → 配 UCR2(bit14 IRTS=1、bit5 WS=1、bit2 TXEN、bit1 RXEN)→ UCR3.bit2 RXDMUXSEL=1 → 写 UFCR/UBIR/UBMR先 UBIR 后 UBMR)→ UCR1.UARTEN=1
  • 关键 API:uart_inituart_io_inituart_disableuart_enableuart_softresetuart_setbaudrateputcputsgetcraise

12.8 串口格式化函数移植实验(第22章)

  • 把从 U-Boot 抠出的 stdio/{include,lib} 复制到工程根目录,Makefile 的 INCDIRS/SRCDIRS 加入 stdio/includestdio/lib,并加编译选项 -Wa,-mimplicit-it=thumb
  • 重定向:实现 fputc(int ch, FILE *f)putc(ch)fgetc(FILE *f)getc(),即可使用 printf/scanf
  • 注意该 stdio 不支持浮点 %f

十三、裸机开发技巧

13.1 调试方法

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

13.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

13.3 性能优化

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

13.4 链接脚本模板

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

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

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

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

十四、跨平台对比

14.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
开发难度 中等 简单 较高

14.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);  /* 输出低电平 */

14.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. 支持低功耗唤醒功能

十六、实验总结

16.1 实验知识图谱

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

16.2 学习路径建议

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

16.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

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第8章 汇编LED灯实验、第10章 C语言版LED灯实验、第12章 官方SDK移植实验、第13章 BSP工程管理实验、第14章 蜂鸣器实验、第15章 按键输入实验、第16章 主频和时钟配置实验、第17章 GPIO中断实验、第18章 EPIT定时器实验、第19章 定时器按键消抖实验、第20章 高精度延时实验、第21章 UART串口通信实验、第22章 串口格式化函数移植实验、第23章 DDR3实验、第24章 RGBLCD显示实验、第25章 RTC实时时钟实验、第26章 I2C实验、第27章 SPI实验