i.MX6ULL嵌入式系统代码详解(从零开始)
一、系统启动与初始化(start.S)
1. 异常向量表
.global _start _start: ldr pc, =_reset_handler ; 复位异常(系统启动) ldr pc, =_undef_handler ; 未定义指令异常 ldr pc, =_software_handler ; 软件中断(SWI) ldr pc, =_prefect_handler ; 预取指令异常 ldr pc, =_data_abort_handler ; 数据访问异常 nop ; 保留 ldr pc, =_irq_handler ; IRQ中断 ldr pc, =_fiq_handler ; FIQ中断解释:这是ARM处理器的异常向量表,位于内存起始位置(0x87800000)。处理器发生异常时,会根据异常类型自动跳转到对应地址。每个向量占用4字节,第7个向量为IRQ中断入口。
2. 复位处理程序(系统初始化)
_reset_handler: cpsid i ; 禁用IRQ中断(保证初始化不被中断) ; 配置CP15系统控制寄存器 mrc p15, 0, r0, c1, c0, 0 ; 读取控制寄存器 bic r0, r0, #(1 <<13) ; 清除V位,使用VBAR重映射异常向量表 orr r0, r0, #(1 <<12) ; 设置I位,启用指令缓存(ICache) mcr p15, 0, r0, c1, c0, 0 ; 写回控制寄存器 ; 设置IRQ模式栈指针 cps #0x12 ; 切换到IRQ模式(模式编码0x12) ldr sp, =0x82000000 ; IRQ栈起始地址 ; 设置系统模式栈指针 cps #0x1F ; 切换到系统模式(0x1F) ldr sp, =0x84000000 ; 系统栈起始地址 cpsie i ; 启用IRQ中断 bl _bss_init ; 初始化BSS段(清零) b main ; 跳转到C语言main函数关键点:
cpsid i/cpsie i:中断禁用/使能指令不同模式有不同的栈指针,避免模式切换时数据破坏
BSS段存放未初始化的全局/静态变量,启动时需要清零
3. IRQ中断处理程序
_irq_handler: sub lr, lr, #4 ; 调整返回地址(ARM中断返回特性) stmfd sp!, {r0-r12, lr} ; 保存现场(寄存器入栈) ; 获取GIC中断控制器基地址 mrc p15, 4, r1, c15, c0, 0 ; 从CP15读取GIC基地址 add r1, r1, #0x2000 ; GIC CPU接口偏移 ldr r0, [r1, #0xC] ; 读取中断ID(ICC_IAR) ; 保存中断ID并调用C中断处理函数 stmfd sp!, {r0, r1} cps #0x1F ; 切换到系统模式 stmfd sp!, {lr} bl system_interrupt_handler ; C语言中断分发函数 ldmfd sp!, {lr} cps #0x12 ; 切换回IRQ模式 ; 中断处理完成,发送EOI ldmfd sp!, {r0, r1} str r0, [r1, #0x10] ; 写ICC_EOIR,中断结束 ldmfd sp!, {r0-r12, pc}^ ; 恢复现场并返回(^表示恢复CPSR)二、时钟系统初始化(clock.c)
1. 主要时钟源配置
void clock_init(void) { // 1. ARM内核时钟配置 CCM->CCSR &= ~(1 << 8); // 清除pll1_sw_clk_sel位 CCM->CCSR |= (1 << 2); // step_clk选择24MHz晶振 // 设置ARM时钟分频 CCM->CACRR &= ~(7 << 0); // 清除分频系数 CCM->CACRR |= (1 << 0); // 设置2分频(ARM_PODF=1) // 配置PLL1(ARM PLL) unsigned int t = CCM_ANALOG->PLL_ARM; t &= ~(3 << 14); // 清除旁路控制位 t |= (1 << 13); // 使能PLL输出 t &= ~(0x7F << 0); // 清除倍频系数 t |= (88 << 0); // 设置倍频系数N=88 CCM_ANALOG->PLL_ARM = t; // 24MHz × 88 = 2112MHz CCM->CCSR &= ~(1 << 2); // step_clk切换回PLL1 // 2. 配置528 PLL的PFD通道 t = CCM_ANALOG->PFD_528; t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24)); t |= ((27 << 0) | (16 << 8) | (24 << 16) | (32 << 24)); CCM_ANALOG->PFD_528 = t; /* PFD0: 528×18/27 = 352MHz PFD1: 528×18/16 = 594MHz PFD2: 528×18/24 = 396MHz PFD3: 528×18/32 = 297MHz */ // 3. 配置480 PLL的PFD通道 t = CCM_ANALOG->PFD_480; t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24)); t |= ((12 << 0) | (16 << 8) | (17 << 16) | (19 << 24)); CCM_ANALOG->PFD_480 = t; /* PFD0: 480×18/12 = 720MHz PFD1: 480×18/16 = 540MHz PFD2: 480×18/17 ≈ 508MHz PFD3: 480×18/19 ≈ 454MHz */ // 4. 配置AHB总线时钟(132MHz) t = CCM->CBCMR; t &= ~(3 << 18); // 清除PRE_PERIPH_CLK_SEL t |= (1 << 18); // 选择PLL2 PFD2(396MHz) CCM->CBCMR = t; t = CCM->CBCDR; t &= ~(1 << 25); // PERIPH_CLK_SEL=0(选择PRE_PERIPH) t &= ~(7 << 10); // 清除AHB_PODF t |= (2 << 10); // AHB_PODF=2(3分频),396MHz÷3=132MHz // 5. 配置IPG时钟(66MHz) t &= ~(3 << 8); // 清除IPG_PODF t |= (1 << 8); // IPG_PODF=1(2分频),132MHz÷2=66MHz CCM->CBCDR = t; // 6. 配置PERCLK时钟(66MHz) t = CCM->CSCMR1; t &= ~(1 << 6); // PERCLK_CLK_SEL=0(选择IPG时钟) t &= ~(0x3F << 0); // PERCLK_PODF=0(1分频) CCM->CSCMR1 = t; clock_cg_init(); // 使能所有外设时钟 }2. 外设时钟使能
void clock_cg_init(void) { CCM->CCGR0 = 0XFFFFFFFF; // 使能CCGR0控制的所有外设时钟 CCM->CCGR1 = 0XFFFFFFFF; // GPIO1-5等 CCM->CCGR2 = 0XFFFFFFFF; // GPT、EPIT等定时器 CCM->CCGR3 = 0XFFFFFFFF; // UART、I2C等 CCM->CCGR4 = 0XFFFFFFFF; // PWM、ADC等 CCM->CCGR5 = 0XFFFFFFFF; // 视频相关 CCM->CCGR6 = 0XFFFFFFFF; // 以太网等 }时钟门控:每个CCGR寄存器控制一组外设的时钟,写入1使能时钟,0禁用时钟(低功耗)。
三、中断系统(interrupt.c)
1. 中断向量表
irq_handler_t Vector_table[160] = {NULL}; // 定义中断向量表,最多支持160个中断源 // irq_handler_t是函数指针类型:typedef void (*irq_handler_t)(void);2. 中断系统初始化
void system_interrupt_init(void) { __set_VBAR(0x87800000); // 设置异常向量表基地址到0x87800000 GIC_Init(); // 初始化通用中断控制器(GIC) }VBAR:Vector Base Address Register,ARMv7异常向量表基址寄存器。
3. 中断注册与管理
// 中断服务函数注册 int system_interrupt_register(IRQn_Type irq, irq_handler_t handler) { if (irq > PMU_IRQ2_IRQn || irq < IOMUXC_IRQn) return -1; // 中断号范围检查 if (handler == NULL) return -2; // 空指针检查 Vector_table[irq] = handler; // 注册中断处理函数 return 0; } // 中断分发函数(由汇编_irq_handler调用) void system_interrupt_handler(IRQn_Type irq) { if (Vector_table[irq] != NULL){ Vector_table[irq](); // 调用注册的中断服务函数 } }四、GPIO外设驱动
1. LED驱动(led.c)
void led_init(void) { // 1. 引脚复用配置 IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0); // 将GPIO1_IO03配置为GPIO功能(ALT5模式) // 2. 电气特性配置 IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0); // 0x10B0配置含义: // - 驱动强度:R0/6(普通驱动) // - 速度:100MHz // - 上拉电阻:47K上拉 // - 滞回器:使能 // - 开漏输出:禁用 // 3. 方向配置(输出模式) GPIO1->GDIR |= (1 << 3); // 设置GPIO1_IO03为输出 led_off(); // 初始状态关闭 } void led_on(void) { GPIO1->DR &= ~(1 << 3); } // 低电平点亮 void led_off(void) { GPIO1->DR |= (1 << 3); } // 高电平熄灭 void led_nor(void) { GPIO1->DR ^= (1 << 3); } // 电平翻转2. 蜂鸣器驱动(beep.c)
void beep_init(void) { // SNVS域GPIO(低功耗域) IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0); IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0); GPIO5->GDIR |= (1 << 1); // GPIO5_IO01设为输出 beep_off(); }SNVS域:始终供电域,系统休眠时仍可工作。
3. 按键驱动(key.c)与中断配置
void key_irq_handler(void) { // 检查GPIO1_IO18中断状态 if ((GPIO1->ISR & (1 << 18)) != 0) { led_nor(); // 翻转LED beep_nor(); // 翻转蜂鸣器 GPIO1->ISR |= (1 << 18); // 清除中断标志 } } void key_init(void) { // 1. 引脚配置 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0); IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0); GPIO1->GDIR &= ~(1 << 18); // 设为输入模式 // 2. 中断触发方式配置 GPIO1->ICR2 |= (3 << 4); // 设置GPIO1_IO18双边沿触发 /* ICR2寄存器位说明: - bits[5:4]:ICR18配置位 - 00:低电平触发 01:高电平触发 - 10:上升沿触发 11:双边沿触发 */ // 3. 中断使能 GPIO1->IMR |= (1 << 18); // 使能GPIO1_IO18中断屏蔽 // 4. GIC中断配置 GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); // 使能GIC中断 GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0); // 设置最高优先级 // 5. 注册中断处理函数 system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_irq_handler); }五、主程序(main.c)
int main(void) { system_interrupt_init(); // 1. 中断系统初始化 clock_init(); // 2. 时钟系统初始化 led_init(); // 3. LED初始化 beep_init(); // 4. 蜂鸣器初始化 key_init(); // 5. 按键中断初始化 while(1) // 主循环 { led_nor(); // 翻转LED(非中断方式) g_delay(0x7FFFF); // 软件延时 } return 0; } // 简单软件延时函数 void g_delay(unsigned int t) { while(t--); // 空循环消耗时间 }六、系统工作流程总结
1. 启动流程
上电复位 → 执行_start(汇编) → 初始化CPU → 设置栈指针 → 初始化BSS段 → 跳转到main() → 外设初始化 → 进入主循环
2. 中断响应流程
按键按下 → GPIO检测边沿 → 置位中断标志 → GIC接收中断 → CPU响应IRQ → 执行_irq_handler(汇编) → 保存现场 → 调用system_interrupt_handler → 执行key_irq_handler → 处理中断(翻转LED/蜂鸣器) → 清除中断标志 → 恢复现场返回
3. 时钟树概要![]()
24MHz晶振 → PLL1(2112MHz) → 2分频 → ARM内核(1056MHz) → PLL2(528MHz) → PFD2(396MHz) → 3分频 → AHB(132MHz) → 2分频 → IPG(66MHz) → PLL3(480MHz) → 各PFD通道 → 各外设时钟
4. 硬件连接关系
GPIO1_IO03 → LED(低电平点亮) GPIO5_IO01 → 蜂鸣器(低电平发声) GPIO1_IO18 → 按键(按下为低电平)
七、关键概念解析
1. 内存地址映射
0x87800000:异常向量表起始地址(由VBAR指定) 0x82000000:IRQ模式栈空间 0x84000000:系统模式栈空间 0x0209C000:GPIO1寄存器基地址 0x020A8000:CCM时钟控制器基地址
2. 中断优先级
GIC优先级:0为最高,数值越大优先级越低
嵌套中断:高优先级中断可打断低优先级中断处理
中断屏蔽:通过CPSID/CPSIE指令全局控制
3. GPIO寄存器关键位
GDIR:方向寄存器(1=输出,0=输入)
DR:数据寄存器(读写引脚电平)
ICR1/2:中断配置寄存器(触发方式)
IMR:中断屏蔽寄存器(1=使能中断)
ISR:中断状态寄存器(1=中断发生,写1清除)
4. 时钟配置安全原则
先降频后升频:配置PLL前切换到低频时钟
分频保护:设置倍频前先配置输出分频
等待锁定:PLL配置后需等待LOCK标志
顺序切换:时钟源切换按标准流程操作