前言
基于 100ask_imx6ull_pro 开发板
使用 100ASK_IMX6ULL_Flashing_tool 工具进行程序烧写
参考资料为《i.MX 6ULL Applications Processor Reference Manual, Rev. 1, 11/2017》
默认情况下 iMX6ULL 的主频为 396MHz,我们想要让 iMX6ULL 运行时候达到最大性能,可将主频上调至最大 528MHz
本文将修改时钟树来提升主频,并通过 LED 亮灭快慢来观察主频的变化
基础知识
BYPASS Clock(旁路时钟) 是指在不使用外部晶体的情况下,直接从外部引入时钟信号,绕过芯片内部的时钟驱动组件,用作替代主时钟信号的备用时钟。
旁路时钟通常用于以下几种情况:
- PLL 故障或失效:当锁相环(PLL)发生故障或不稳定时,系统可以切换到旁路时钟,以继续提供稳定的时钟信号,确保系统不因时钟信号丢失或不稳定而崩溃或停止运行。
- 系统测试或调试:在某些测试或调试场景中,工程师可能需要绕过 PLL,直接使用外部时钟信号。旁路时钟信号使得对系统进行测试或调试更加方便。
- 启动或复位过程:在系统启动或复位时,PLL 需要一段时间来锁定稳定的输出频率。在此期间,系统可以使用旁路时钟信号,以确保启动或复位过程的顺利进行。
时钟树分析
由下图可以看出 I/MX6ULL 时钟的整体结构
CCM_ANALOG
CCM 的模拟时钟电路部分,接收外部晶振时钟输入,输出倍频后的 PLLs,以及 PFDs
PLL 可以调节倍频系数
PFD 由相应的 PLL 进一步分频得来,imx6ull 只有 PLL2&PLL3 下面有 PFD
CCM_CLK_SWITCHER
接收来自 CCM_ANALOG 模块的 PLL 及 PFD 时钟输出,以及 PLL 的旁路时钟, 并为 CCM_CLK_ROOT_GEN 子模块生成选择后的时钟输出
CCM_CLK_ROOT_GEN
接收来自 CCM_CLK_SWITCHER 模块筛选后的 PLL 或 PFD 时钟
进一步进行选择、分频等操作之后产生并输出根时钟
根时钟将会作内核或外设的时钟源
修改 ARM 主频
要修改 ARM_CLK_ROOT 为 528MHZ,那么可以设置 CACRR 的 ARM_PODF 位为 2 分频, PLL1=1056MHz
1. 设置 CCM_CACRR:ARM_PODF
设置pll1_sw_clk->arm_clk_root分频系数为2
由于 PLL1 = pll1_sw_clk,当修改 PLL1 时,需要将 pll1_sw_clk 从 pll1_main_clk 切换到 step_clk,待 PLL1 修改稳定后再切换回来
step_clk 我们选择直接接收 外部晶振的输出 osc_clk
2. 设置 CCM_CCSR:step_sel
设置step_clk时钟来源为osc_clk
3. 设置 CCM_CCSR:pll1_sw_clk_sel
设置pll1_sw_clk时钟来源为step_clk
切换为 24MHz 作为时钟来源后,就可以修改 PLL1 的倍频系数了。由于 PLL1 属于模拟电路,所有在模拟(ANALOG)相关的寄存器设置
需要注意的是在 i.MX 6ULL
应用处理器的参考手册中,关于 CCM_ANALOG_PLL_ARM
有四种不同的寄存器。这四个寄存器分别是:
- CCM_ANALOG_PLL_ARM(原始寄存器)
- CCM_ANALOG_PLL_ARM_SET(设置寄存器)
- CCM_ANALOG_PLL_ARM_CLR(清除寄存器)
- CCM_ANALOG_PLL_ARM_TOG(切换寄存器)
这四种寄存器的存在是为了方便对 PLL(Phase-Locked Loop,锁相环)进行更灵活的控制和操作。每个寄存器的功能如下:
- CCM_ANALOG_PLL_ARM: 这是标准的控制寄存器,用于配置和读取 PLL 的状态。通过该寄存器,可以直接写入新的值来配置 PLL。
- CCM_ANALOG_PLL_ARM_SET: 这个寄存器允许你在不影响其他位的情况下,设置寄存器中的特定位(即,将特定位置 1)。这在需要更改寄存器的部分内容时非常有用,而无需修改整个寄存器的值。
- CCM_ANALOG_PLL_ARM_CLR: 与 SET 寄存器相反,CLR 寄存器用于清除寄存器中的特定位(即,将特定位置 0)。这提供了一种方便的方法来清除某些配置,而不干扰其他位。
- CCM_ANALOG_PLL_ARM_TOG: 这个寄存器用于切换寄存器中的特定位(即,将特定位从 0 变为 1,或者从 1 变为 0)。这可以用于快速切换状态,而无需单独设置或清除某个位。
由计算公式我们需要设置 DIV_SELECT 为
4. 设置 CCM_ANALOG_PLL_ARM:DIV_SELECT
将 DIV_SELECT 位设为 0101_1000(88)
5. 等待 CCM_ANALOG_PLL_ARM:LOCK
等待 PLL LOCK 位为 1 ,PLL 锁环输出稳定下来才能使用
6. 设置 CCM_CCSR:pll1_sw_clk_sel
设 0,选择 pll1_sw_clk
的时钟来源为 pll1_main_clk
此时主频应该就修改完成了
参考代码
下面的参考代码我分别将主频设为了 81mhz-648mhz,这样观察起来更加明显
main.c
#include "include/led.h"
#include "include/clock.h"
uint32_t count = 10;
int main()
{
led_init();
set_arm_clk_root_81mhz();
while (count--)
{
led_toggle();
delay(1);
}
set_arm_clk_root_648mhz();
while (1)
{
led_toggle();
delay(1);
}
return 0;
}
clock.h
#ifndef CLOCK_H // 如果未定义 CLOCK_H
#define CLOCK_H // 定义 CLOCK_H
#define uint32_t unsigned int
// CCM Arm Clock Root Register (CACRR)
#define CCM_CACRR (volatile unsigned long *)0x020C4010
#define CCM_CCSR (volatile unsigned long *)0x020C400C
#define CCM_ANALOG_PLL_ARM (volatile unsigned long *)0x020C8000
// 设置pll1_sw_clk->arm_clk_root分频系数为2
void set_arm_podf_2div(void);
// 设置设置step_clk时钟来源为osc_clk
void set_step_sel_osc(void);
// 设置pll1_sw_clk时钟来源: 0则pll1_main_clk 1则osc_clk
void set_pll1_sw_clk_sel(uint32_t value);
// 设置pll1_div_sel分频系数为8,则pll1_main_clk为96MHz
void set_pll1_div_sel_8(void);
// 设置pll1_div_sel分频系数为108,则pll1_main_clk1296MHz
void set_pll1_div_sel_108(void);
// 获取pll1_lock状态, 0则未锁定 1则锁定
uint32_t get_pll1_lock_status(void);
void set_arm_clk_root_81mhz(void);
void set_arm_clk_root_648mhz(void);
#endif // CLOCK_H
/*
24MHz OSC --> ARM_CLK_ROOT 路径:
切换前: OSC -> PLL1 -> pll1_main_clk -> CCSR:pll1_sw_clk_sel -> pll1_sw_clk -> CACRR[ARM_PODF] -> ARM_CLK_ROOT
切换中: OSC -> osc_clk -> CCSR:step_sel -> step_clk -> CCSR:pll1_sw_clk_sel -> pll1_sw_clk -> CACRR[ARM_PODF] -> ARM_CLK_ROOT
切换后: OSC -> PLL1 -> pll1_main_clk -> CCSR:pll1_sw_clk_sel -> pll1_sw_clk -> CACRR[ARM_PODF] -> ARM_CLK_ROOT
*/
clock.c
#include "../include/clock.h"
void set_arm_podf_2div(void)
{
*(CCM_CACRR) = 0x00000001;
}
void set_step_sel(uint32_t value)
{
uint32_t reg_value = *(CCM_CCSR) & ~((0xFF) << 8); // 清除旧值
*(CCM_CCSR) = reg_value | (value << 8); // 设置新值
}
void set_pll1_sw_clk_sel(uint32_t value)
{
uint32_t reg_value = *(CCM_CCSR) & ~((0x3) << 2); // 清除旧值
*(CCM_CCSR) = reg_value | (value << 2); // 设置新值
}
void set_pll1_div_sel_8(void)
{
uint32_t reg_value = *(CCM_ANALOG_PLL_ARM) & ~0x7F; // 清除低7位
*(CCM_ANALOG_PLL_ARM) = reg_value | 8; // 设置新值8
}
void set_pll1_div_sel_108(void)
{
uint32_t reg_value = *(CCM_ANALOG_PLL_ARM) & ~0x7F; // 清除低7位
*(CCM_ANALOG_PLL_ARM) = reg_value | 108; // 设置新值108
}
uint32_t get_pll1_lock_status(void)
{
unsigned long value = *CCM_ANALOG_PLL_ARM;
unsigned long highest_bit = (value >> 31) & 0x1;
return highest_bit;
}
void set_arm_clk_root_81mhz(void)
{
set_arm_podf_2div();
set_step_sel(0);
set_pll1_sw_clk_sel(1);
set_pll1_div_sel_8();
while (get_pll1_lock_status() == 0)
;
set_pll1_sw_clk_sel(0);
}
void set_arm_clk_root_648mhz(void)
{
set_arm_podf_2div();
set_step_sel(0);
set_pll1_sw_clk_sel(1);
set_pll1_div_sel_108();
while (get_pll1_lock_status() == 0)
;
set_pll1_sw_clk_sel(0);
}