嵌入式开发代码实践——时钟控制
本文详细介绍了i.MX6ULL嵌入式系统的启动流程和底层驱动实现。主要内容包括:1)系统启动过程,包含异常向量表设置、CPU初始化、栈指针配置和BSS段清零;2)时钟系统初始化,详细讲解了ARM内核时钟、PLL配置和总线时钟分频;3)中断系统实现,涵盖GIC控制器初始化和中断服务注册机制;4)GPIO外设驱动开发,包括LED、蜂鸣器和按键的中断驱动实现;5)主程序工作流程和关键概念解析。文章通过汇
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标志
-
顺序切换:时钟源切换按标准流程操作

所有评论(0)