问题
最近在学习 imx6ull 裸机开发,学到配置时钟的时候有一个问题一直没有解决。就是配置 AHB_CLK_ROOT 为3分频一直无法实现,会出现无法预测的BUG,所以我们就无法设置它的分频系数,这是正点原子的配置 AHB 时钟部分的代码
/* 4、设置AHB时钟 最小6Mhz, 最大132Mhz (boot rom自动设置好了可以不用设置)*/
CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
CCM->CBCMR |= (1 << 18); /* pre_periph_clk=PLL2_PFD2=396MHz */
CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
/* 修改AHB_PODF位的时候需要先禁止AHB_CLK_ROOT的输出,但是
* 我没有找到关闭AHB_CLK_ROOT输出的的寄存器,所以就没法设置。
* 下面设置AHB_PODF的代码仅供学习参考不能直接拿来使用!!
* 内部boot rom将AHB_PODF设置为了3分频,即使我们不设置AHB_PODF,
* AHB_ROOT_CLK也依旧等于396/3=132Mhz。
*/
#if 0
/* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */
CCM->CBCDR &= ~(7 << 10); /* CBCDR的AHB_PODF清零 */
CCM->CBCDR |= 2 << 10; /* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */
while(CCM->CDHIPR & (1 << 1));/
* 等待握手完成 */
#endif
可以看到依然没有解决这个问题,虽然内部 boot rom 已经将 AHB_PODF 设置为了3分频,即使我们不设置 AHB_ROOT_CLK 也默认为132MHZ。但是这不自由!
原因
通过网上寻找
我试着从网上找看看有没有解决方法,但是很遗憾网上关于这方面的问题几乎没有,唯一的几个讨论时钟问题的帖子也没有解决这个问题,都是一笔带过,只告诉你需要把 AHB_CLK_ROOT 的时钟输出关闭然后在修改分频系数,然后代码也使用的正点原子的代码,并没有告诉你怎么做
数据手册查找
所以我决定从正点原子给我们的线索作为突破口,就是控制 AHB_CLK_ROOT 开关的寄存器,既然正点原子没有找到那我就再去找找。果然正点原子没有找到的东西也不是那么容易就找到的,但是我还是找到了一个比较接近的寄存器 IOMUXC_GPR_GPR1 寄存器的26位 ARMA7_CLK_AHB_EN
它的功能差不多就是我们需要的功能,而 IOMUXC_GPR_GPR1 寄存器的地址为20E_4004h 所以我们程序可以修改为
/* 4、设置AHB时钟 最小6Mhz, 最大132Mhz (boot rom自动设置好了可以不用设置)*/
CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
CCM->CBCMR |= (1 << 18); /* pre_periph_clk=PLL2_PFD2=396MHz */
CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
/* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */
*(volatile unsigned int*)0x020e4004 &= ~(1 << 26); /* 将bit26位清零也就是不运行 AHB_CLK */
CCM->CBCDR &= ~(7 << 10); /* CBCDR的AHB_PODF清零 */
CCM->CBCDR |= 2 << 10; /* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */
while(CCM->CDHIPR & (1 << 1));/
*(volatile unsigned int*)0x020e4004 |= (1 << 26); /* 设置完分频系数后在置1运行 AHB_CLK */
* 等待握手完成 */
但是当我重新运行代码的时候依然会死机,但是比之前死机的频率小了很多(之前运行10次可能有5次以上死机,现在可能只有2、3次死机),这个寄存器起作用了,但没有完全起作用,说明核心问题不在这里。
NXP官方SDK提供的API
这时我又想到 NXP 官方提供了 SDK 而官方的 SDK 里面不可能没有修改 AHB_CLK_ROOT 分频系数的 API ,果不其然在 clock_config_ocram.c 这个文件里我发现了 AHB_CLK_ROOT 初始化相关的代码
/* Set pre_periph2_clk and pre_periph_clk MUX to SYS PLL PFD2*/
CCM->CBCMR = (CCM->CBCMR & ~(CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK | CCM_CBCMR_PRE_PERIPH2_CLK_SEL_MASK)) |
CCM_CBCMR_PRE_PERIPH_CLK_SEL(1) | CCM_CBCMR_PRE_PERIPH2_CLK_SEL(1);
/* Set periph2_clk and periph_clk MUX to PLL2*/
CCM->CBCDR = (CCM->CBCDR & ~(CCM_CBCDR_PERIPH2_CLK_SEL_MASK | CCM_CBCDR_PERIPH_CLK_SEL_MASK)) |
(CCM_CBCDR_PERIPH2_CLK_SEL(0) | CCM_CBCDR_PERIPH_CLK_SEL(0));
/* Config AXI divide by 2, AHB divide by 3, IPG divide by 2, MMDC divide by 1*/
CCM->CBCDR =
(CCM->CBCDR &
~(CCM_CBCDR_AXI_PODF_MASK | CCM_CBCDR_AHB_PODF_MASK | CCM_CBCDR_IPG_PODF_MASK |
CCM_CBCDR_FABRIC_MMDC_PODF_MASK)) |
(CCM_CBCDR_AXI_PODF(1) | CCM_CBCDR_AHB_PODF(2) | CCM_CBCDR_IPG_PODF(1) | CCM_CBCDR_FABRIC_MMDC_PODF(0));
/* Wait handshake process */
while (CCM->CDHIPR &
(CCM_CDHIPR_PERIPH2_CLK_SEL_BUSY_MASK | CCM_CDHIPR_PERIPH_CLK_SEL_BUSY_MASK | CCM_CDHIPR_AXI_PODF_BUSY_MASK |
CCM_CDHIPR_AHB_PODF_BUSY_MASK | CCM_CDHIPR_MMDC_PODF_BUSY_MASK))
{
}
这里我把其他时钟相关的代码给去掉了方便对比
/* 正点原子初始化AHB_CLK_ROOT的代码 */
#if 0
/* pre_periph_clk=PLL2_PFD2=396MHz */
CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
CCM->CBCMR |= (1 << 18);
/* periph_clk=pre_periph_clk=396MHz */
CCM->CBCDR &= ~(1 << 25);
/* 等待握手完成 */
while(CCM->CDHIPR & (1 << 5));
/* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */
*(volatile unsigned int*)0x020e4004 &= ~(1 << 26); /* 将bit26位清零也就是不运行 AHB_CLK */
/* CBCDR的AHB_PODF清零 */
CCM->CBCDR &= ~(7 << 10);
/* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */
CCM->CBCDR |= 2 << 10;
while(CCM->CDHIPR & (1 << 1));
/* 等待握手完成 */
*(volatile unsigned int*)0x020e4004 |= (1 << 26); /* 设置完分频系数后在置1运行 AHB_CLK */
#endif
/* NXP官方SDK提供的初始化AHB_CLK_ROOT的代码 */
/* Set pre_periph_clk MUX to SYS PLL PFD2*/
CCM->CBCMR = ((CCM->CBCMR & ~(CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK)) | (CCM_CBCMR_PRE_PERIPH_CLK_SEL(1)));
/* Set periph_clk MUX to PLL2*/
CCM->CBCDR = ((CCM->CBCDR & ~(CCM_CBCDR_PERIPH_CLK_SEL_MASK)) | (CCM_CBCDR_PERIPH_CLK_SEL(0)));
/* Config AHB divide by 3 */
CCM->CBCDR = ((CCM->CBCDR & ~(CCM_CBCDR_AHB_PODF_MASK)) | (CCM_CBCDR_AHB_PODF(2)));
/* Wait handshake process */
while (CCM->CDHIPR & CCM_CDHIPR_AHB_PODF_BUSY_MASK)
{
}
通过对比可以发现,两段代码的几乎一样,几乎一样就说明还是有不同的,不同之处就在于正点原子的代码是先将寄存器对应位清零赋值给寄存器,然后在向寄存器对于位写入值,而 NXP 的 SDK 代码是先将寄存器对应位清零,这里它比没有直接赋值给寄存器而是继续与上要写入的值,然后在将结果赋值给寄存器。虽然两种做法得到的值都是一样的,但我感觉问题就出现在这里。
解决
然后我又通过对比实验将问题锁定在了这段代码上
CCM->CBCDR = ((CCM->CBCDR & ~(CCM_CBCDR_AHB_PODF_MASK)) | (CCM_CBCDR_AHB_PODF(2)));
如果使用这段代码程序就可以正常运行。现在工作就很简单了,对比这两段代码有什么不同行了。又是一通对比,然后就发现只要把 CCM->CBCDR 寄存器的相应为清零,也就是将 bit10~12 位清零,程序就会出问题
这时就需要看数据手册了
通过之前的设置 AHB_CLK_ROOT 的时钟来源是通过 CBCMR 寄存器的 PRE_PERIPH_CLK_SEL 位设置为 PLL2_PDF2 ,然后再通过 CBCDR 寄存器的 PERIPH_CLK_SEL 位设置为 pll2_main_clk 时钟来源,最后设置 CBCDR 寄存器的 AHB_PODF 位来设置分频系数,在没有设置分配系数前,AHB_CLK_ROOT 的时钟源一直为396MHZ,经过一个3分频变成132MHZ,这很重要
我们在来看数据手册里面系统时钟频率表
通过这种表可以知道 AHB_CLK_ROOT 的频率范围为6 - 132MHZ ,结合上面说的很快就能发现问题所在。因为正点原子在设置3分频时先将 CBCDR 的bit10-12 位清零了,也就是设置分频系数的位,那么 AHB_CLK_ROOT 的分频系数就为1分频了(bit10-12为0是1分频),即 AHB_CLK_ROOT 的时钟频率为 396MHZ 这明显已经超出最大值 132MHZ了。而 NXP 官方 SDK 初始化 AHB_CLK_ROOT 的代码中虽然也加入了清零操作,但是清零的值并没有马上赋值给寄存器,而是与要写入的值进行运算后才放入寄存器,所以 NXP 官方 SDK 代码可以正常设置而不会出现问题,所以我们只需要稍微修改下代码
/* pre_periph_clk=PLL2_PFD2=396MHz */
CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
CCM->CBCMR |= (1 << 18);
/* periph_clk=pre_periph_clk=396MHz */
CCM->CBCDR &= ~(1 << 25);
/* 等待握手完成 */
while(CCM->CDHIPR & (1 << 5));
/* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */
IOMUXC_GPR->GPR1 &= ~(1 << 26); /* 将bit26位清零也就是不运行 AHB_CLK */
/* 将AHB_ROOT_CLK设置为3分频 */
CCM->CBCDR = ((CCM->CBCDR & ~(7 << 10)) | (2 << 10));
/* 或者创建一个变量来保存寄存器的值
value = CCM->CBCDR;
value &= ~(7 << 10);
value |= (2 << 10);
CCM->CBCDR = value;
*/
/* 等待握手完成 */
while ((CCM->CDHIPR >> 1) & 0X1);
IOMUXC_GPR->GPR1 |= (1 << 26); /* 设置完分频系数后在置1运行 AHB_CLK */
然后上传代码,问题解决
疑惑
虽然上述代码已经能稳定运行了,但是这并不符文档的描述,正常来说我们应该将 AHB_CLK_ROOT 的时钟输出关闭然后在进行操作,但是官方给的初始化代码里面也没有关闭 AHB_CLK_ROOT 时钟输出的操作(或者是我没有找到?),最后关于 IOMUXC_GPR_GPR1 寄存器的功能仍不清楚,留个坑,也希望大佬可以解答解答