news 2026/3/26 3:54:28

嵌入式开发代码实践——时钟控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式开发代码实践——时钟控制

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. 时钟配置安全原则

  1. 先降频后升频:配置PLL前切换到低频时钟

  2. 分频保护:设置倍频前先配置输出分频

  3. 等待锁定:PLL配置后需等待LOCK标志

  4. 顺序切换:时钟源切换按标准流程操作

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/14 8:21:50

NewBie-image-Exp0.1科研应用案例:动漫风格迁移实验部署教程

NewBie-image-Exp0.1科研应用案例&#xff1a;动漫风格迁移实验部署教程 1. 引言&#xff1a;开启高质量动漫生成的科研之旅 你是否在寻找一个稳定、高效、开箱即用的工具&#xff0c;来支持你的动漫图像生成研究&#xff1f;NewBie-image-Exp0.1 正是为此而生。它不是一个简…

作者头像 李华
网站建设 2026/3/18 0:22:17

Glyph教育公平应用:偏远地区智能辅导系统部署

Glyph教育公平应用&#xff1a;偏远地区智能辅导系统部署 1. 为什么偏远地区的老师和学生需要Glyph这样的工具 在很多交通不便、网络不稳、设备老旧的偏远教学点&#xff0c;老师们常常面临一个现实困境&#xff1a;想用AI辅助备课、批改作业、生成练习题&#xff0c;但主流大…

作者头像 李华
网站建设 2026/3/17 11:30:53

开源大模型选型指南:Qwen3-4B多维度性能评测与部署建议

开源大模型选型指南&#xff1a;Qwen3-4B多维度性能评测与部署建议 1. 为什么Qwen3-4B值得你认真考虑 如果你正在为中小团队或个人开发者寻找一款不依赖云端API、能本地跑得稳、效果又不拉胯的中文大模型&#xff0c;那Qwen3-4B-Instruct-2507很可能就是那个“刚刚好”的答案…

作者头像 李华
网站建设 2026/3/14 14:52:07

光线太暗会影响效果?正确拍照姿势要掌握

光线太暗会影响效果&#xff1f;正确拍照姿势要掌握 1. 这不是玄学&#xff0c;是真实的技术限制 你有没有试过——兴冲冲拍了一张自拍&#xff0c;上传到人像卡通化工具里&#xff0c;结果生成的卡通图人物脸发灰、轮廓糊成一团、连眼睛都看不清&#xff1f; 别急着怀疑模型…

作者头像 李华
网站建设 2026/3/23 15:40:25

YOLOv10官方镜像Python调用示例,快速集成API

YOLOv10官方镜像Python调用示例&#xff0c;快速集成API 你是否曾为部署一个目标检测模型耗费整整两天&#xff1f;装CUDA版本、配PyTorch、编译TensorRT、调试ONNX导出……最后发现只是因为torchvision和Pillow版本冲突&#xff1f;别再重复造轮子了。YOLOv10官方镜像已预装全…

作者头像 李华
网站建设 2026/3/21 20:21:48

Cute_Animal_For_Kids_Qwen_Image商业应用案例:IP形象设计自动化

Cute_Animal_For_Kids_Qwen_Image商业应用案例&#xff1a;IP形象设计自动化 1. 这个工具到底能帮你做什么&#xff1f; 你有没有遇到过这样的情况&#xff1a;一家儿童早教机构要上线新课程&#xff0c;急需一套原创动物IP形象——小熊老师、兔子助教、海豚引导员&#xff0…

作者头像 李华