目录
[1.1 基本概念](#1.1 基本概念)
[1.2 时钟系统核心组件](#1.2 时钟系统核心组件)
[1.2.1 时钟源](#1.2.1 时钟源)
[1.2.2 时钟树关键组件](#1.2.2 时钟树关键组件)
[2.1 关键时钟计算](#2.1 关键时钟计算)
[2.2 时钟初始化](#2.2 时钟初始化)
[3.1 EPIT 定时器](#3.1 EPIT 定时器)
[3.2 GPT定时器](#3.2 GPT定时器)
一、时钟系统基础
1.1 基本概念
- **时钟(clock):**在电子系统中是一个产生稳定、周期性振荡信号的电路或组件。这个信号像节拍器或心跳一样,为数字电路中的各种操作提供同步时序基准。
- **定时器(EPIT GPT):**是一个通过对已知频率的时钟信号进行计数,来实现定时、延时或事件计数功能的硬件模块或软件机制。
- **实时时钟(RTC, real time clock):**是微处理器中的一个功能模块,用于在系统主电源关闭的情况下,继续提供精确的日历和时间信息。
1.2 时钟系统核心组件
1.2.1 时钟源
时钟源是整个时钟系统的起点,提供稳定的原始时钟信号。
- 晶体振荡器:将石英晶体切割成音叉,施加电压产生稳定震荡
- 通常使用 8MHz 或 24MHz 的晶振作为基础时钟源
1.2.2 时钟树关键组件
ARM架构的时钟系统由多个关键组件构成,形成一个复杂的时钟树:
IMX6ULL时钟树
| 组件 | 作用 | 代码示例 |
|---|---|---|
| PLL(锁相环) | 倍频,将低频时钟提升到更高频率 | CCM_ANALOG->PLL_ARM |
| Prescale(分频器) | 分频,将高频时钟降低到所需频率 | CCM->CACRR |
| PFD(相位分数分频器) | 输出频率可升可降,提供更灵活的频率选择 | CCM_ANALOG->PFD_528 |
| MUX(多路选择器) | 选择不同的时钟源 | CCM->CBCMR、CCM->CSCMR1 |
| CG门(Clock Gating | 控制时钟门控,节省功耗 | CCM->CCGR0-CCM->CCGR6 |
二、时钟系统代码实现
2.1 关键时钟计算
IMX6ULL时钟树
- ARM 内核时钟 :通过PLL倍频提升到高频率。
- PLL1 倍频系数 88(24MHz×88=2112MHz)→ 二分频 → 1056MHz(可通过 CACRR 寄存器调整分频系数);
- AHB 总线时钟 :系统总线时钟,用于处理器和外设。
- PLL2 输出 528MHz → 4 分频 → 132MHz(CBCDR 寄存器的 AHB_PODF 配置);
- IPG 总线时钟 :外设接口时钟,用于GPIO、UART等外设。
- AHB 时钟 132MHz → 2 分频 → 66MHz(CBCDR 寄存器的 IPG_PODF 配置);
- PERCLK时钟 :外设时钟,用于定时器、ADC等。
- AHB 时钟 132MHz → 2 分频 → 66MHz(CSCMR1 寄存器的 PERCLK_PODF 配置)。
2.2 时钟初始化
cpp
void clock_init(void)
{
// ARM内核时钟切换(避免配置PLL时内核故障)
CCM->CCSR &= ~(1 << 8); // 选择osc_clk作为step_clk(24MHz)
CCM->CCSR |= (1 << 2); // 让ARM暂时工作在step_clk(24MHz)
// 配置ARM内核分频器(CACRR)
CCM->CACRR &= ~(7 << 0);
CCM->CACRR |= (1 << 0); // 二分频(后续PLL1输出1056MHz,分频后528MHz)
// 配置PLL1(ARM PLL)为1056MHz
unsigned int t = CCM_ANALOG->PLL_ARM;
t &= ~(3 << 4); // 清除原有倍频系数
t |= (1 << 13); // 使能PLL
t &= ~(0x7F << 0);
t |= (88 << 0); // 倍频系数88(24×88=2112MHz,二分频后1056MHz)
CCM_ANALOG->PLL_ARM = t;
CCM->CCSR &= ~(1 << 2); // 切换回PLL1输出(pll1_main_clk)
// 配置PLL2(528MHz)的PFD分频器
t = CCM_ANALOG->PFD_528;
t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24));
// PFD0=27→352M,PFD1=16→594M,PFD2=24→396M,PFD3=32→297M
t |= ((27 << 0) | (16 << 8) | (24 << 16) | (32 << 24));
CCM_ANALOG->PFD_528 = t;
// 配置PLL3(480MHz)的PFD分频器
t = CCM_ANALOG->PFD_480;
t &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24));
t |= ((27 << 0) | (16 << 8) | (24 << 16) | (32 << 24));
CCM_ANALOG->PFD_480 = t;
// 配置AHB_CLK_ROOT(132M)
t = CCM->CBCMR;
t &= ~(3 << 18);
t |= (1 << 18); // 选择PLL2_PFD2(396M)作为输入
CCM->CBCMR = t;
t = CCM->CBCDR;
t &= ~(1 << 25);
t &= ~(7 << 10);
t |= (2 << 10); // AHB分频系数3(396M/3=132M)
CCM->CBCDR = t;
// 配置IPG_CLK_ROOT(66M)
t &= ~(3 << 8);
t |= (1 << 8); // IPG分频系数2(132M/2=66M)
CCM->CBCDR = t;
// 配置PERCLK_CLK_ROOT(66M)
t = CCM->CSCMR1;
t &= ~(1 << 6); // 选择IPG_CLK_ROOT作为输入
t &= ~(0x3F << 0); // 不分频
CCM->CSCMR1 = t;
// 使能所有外设时钟门控(CG)
clock_cg_init();
}
时钟配置关键注意事项:
- 时钟切换顺序:配置 PLL1 时,必须先将 ARM 内核切换到 24MHz 的 step_clk,避免倍频过程中内核因时钟突变而故障;
- 分频系数匹配:外设时钟必须与器件手册要求一致(如 USB 需 480MHz,以太网需特定频率),否则会导致外设工作异常;
- 时钟门控使能:clock_cg_init() 函数将所有 CCGR 寄存器置为 0xFFFFFFFF,即开启所有外设时钟,实际项目中可按需关闭未使用外设的时钟以降低功耗。
三、定时器模块详解
3.1 EPIT 定时器
Enhanced Periodic Interrupt Timer ------ 增强型周期中断定时器
**应用场景:**周期性任务,如 1 秒翻转一次 LED。
工作原理:
- 定时器根据配置的时钟源进行计数。
- 当计数达到 LR (Load Register) 设定的值时,产生中断,并将计数器自动重置为 LR 值。
实验: 1秒中断翻转 LED
使用 PERCLK_CLK_ROOT (66MHz) 作为时钟源。
- 目标频率:1Hz(即每1秒一次)。
- 1秒中断的计算:1000 * 1000 = 1MHz(1MHz时钟,100万计数=1秒)
- 代码实现:
cpp
void epit1_init(void)
{
unsigned int t;
t = EPIT1->CR;
t &= ~(3 << 24); // 清除时钟源选择
t |= (1 << 24); // 选择PERCLK_CLK_ROOT(66M)作为时钟源
t |= (1 << 17); // 覆盖计数器值
t &= ~(0xFFF << 4); // 清除预分频值
t |= (65 << 4); // 分频系数66(66M/66=1MHz)
t |= (1 << 3); // 计数器达到零时,从模数寄存器重新加载(置位-遗忘模式)
t |= (1 << 2); // 启用中断
t |= (1 << 1); // 从加载值开始计数
EPIT1->CR = t;
EPIT1->LR = 1000 * 1000; // 载入值 = 1MHz时钟,1秒
EPIT1->CMPR = 0; // 比较寄存器
EPIT1->CNR = 1000 * 1000; // 当前计数值
// 中断配置
GIC_EnableIRQ(EPIT1_IRQn);
GIC_SetPriority(EPIT1_IRQn, 0);
system_interrupt_register(EPIT1_IRQn, epit_irq_handler);
EPIT1->CR |= (1 << 0); // 使能定时器
}
// 中断服务函数:1s翻转LED和蜂鸣器
void epit_irq_handler(void)
{
if ((EPIT1->SR & (1 << 0)) != 0)
{
led_nor();
beep_nor();
EPIT1->SR |= (1 << 0); // 清除中断标志
}
}
3.2 GPT定时器
General Purpose Timer ------ 通用目的定时器
**应用场景:**高精度延时函数。
**工作原理:**GPT 支持自由运行模式、输入捕获和比较输出。我们利用其自由运行模式读取当前计数值来实现软件延时。
实验: 精准延时
通过读取 GPT1->CNT 寄存器,对比当前值与上一次的值,累加时间差,直到达到所需的微秒数。
- 代码实现:
cpp
void gpt1_init(void)
{
// 复位定时器
reset_fun();
unsigned int t;
t = GPT1->CR;
t &= ~(7 << 26); // 输出断开
t &= ~(3 << 18); // 捕获已禁用
t |= (1 << 9); // 自由运行模式
t &= ~(7 << 6); // 清除时钟源选择
t |= (1 << 6); // 选择PERCLK_CLK_ROOT(66M)作为时钟源
t &= ~(1 << 1); // 失能定时器后保留原来的值
GPT1->CR = t;
GPT1->PR &= ~(0xFFF << 0); // 清除预分频值
GPT1->PR |= (65 << 0); // 分频系数66(66M/66=1MHz)
GPT1->CNT = 0; // 清零计数器
GPT1->CR |= (1 << 0); // 使能定时器
}
// 微秒级延时函数
void delay_us(unsigned int us)
{
unsigned int count = 0;
unsigned int old_count = 0, new_count = 0;
old_count = GPT1->CNT; // 记录起始时刻
while (1)
{
new_count = GPT1->CNT;
// 计算溢出情况(例如从0xFFFFFFFF变到0)
if (new_count != old_count)
{
if (new_count > old_count)
{
count += new_count - old_count;
}
else
{
count += 0xFFFFFFFF - old_count + new_count;
}
}
if (count >= us)
{
return;
}
old_count = new_count;
}
}
四、总结
ARM架构的时钟系统和定时器模块是嵌入式系统设计的核心组件。理解时钟树的结构和工作原理,对于实现精确的定时控制、优化系统功耗和性能至关重要。
- 时钟系统:通过PLL、分频器和多路选择器构建复杂的时钟树,为系统提供精确的时序基准
- 定时器:EPIT提供精确的周期中断,GPT提供灵活的延时功能,两者在嵌入式系统中广泛应用
- 配置实践:通过寄存器配置,实现时钟频率的精确控制和定时器的精准工作