ARM-时钟、定时器

时钟可以说是整个芯片的"心跳",就像乐队演奏离不开节拍器,CPU、各类外设、定时器的所有操作,全部要跟随稳定的时钟信号同步工作。一旦时钟配置出错,整个系统就直接跑飞。除了普通工作时钟,还有RTC实时时钟模块,就算板子主电源断电,依靠纽扣电池供电,依旧可以保存日历和时间,这就是开发板掉电时间不丢失的秘密。

芯片最原始的时钟来源是板载石英晶振,我们开发板基础晶振频率是24MHz。但ARM内核需要几百兆的高频,外设又需要各种各样不同的工作频率,不可能给每一个外设都焊接独立晶振。这时就需要PLL锁相环、Prescale预分频器、PFD相位分数分频器来完成频率变换。

简单区分三者的作用:

PLL锁相环:负责倍频,把晶振输出的低频信号拉升到几百兆赫兹的高频,供给内核和高速外设;

Prescale预分频器:和倍频相反,把高频时钟向下分频,得到适合低速外设使用的时钟;

PFD相位分数分频器:输出频率可升可降,在PLL的基础上衍生多路不同时钟,满足USB、网络等外设多样化需求。

IMX6ULL内部拥有多个PLL,分别服务ARM内核、音频、以太网等不同模块;其中528 PLL、480 PLL各自引出4路PFD,总共8路PFD输出,给外设提供丰富的时钟选择。

注意:配置ARM PLL内核时钟的时候,不能直接修改倍频,极易造成内核死机。

标准配置流程:

  1. 先把ARM内核临时切换到24MHz晶振的低速时钟,保证系统处于安全状态;
  2. 配置PLL相关寄存器,要优先设置PLL后端的二分频,再修改倍频因子;
  3. 等待PLL时钟输出稳定之后,再把内核时钟源切回PLL输出的高频时钟。

配置完PLL之后,还需要配置AHB、IPG、PERCLK这些根时钟,总线和外设都依赖它们,每一路时钟都有对应的CCM系列寄存器,用来选择时钟源、设置分频系数。光配置时钟树还不够,每个外设还有独立的时钟门控,如果门控没打开,外设直接不工作。练习的时候可以直接把全部门控打开,正式项目里建议按需开启。

时钟初始化完整代码如下:

c 复制代码
#include "clock.h"
#include "MCIMX6Y2.h"

//开启全部外设时钟门控,练习使用,实际项目按需开启
void clock_gt_int(void)
{
    CCM->CCGR0 = 0xFFFFFFFF;
    CCM->CCGR1 = 0xFFFFFFFF;
    CCM->CCGR2 = 0xFFFFFFFF;
    CCM->CCGR3 = 0xFFFFFFFF;
    CCM->CCGR4 = 0xFFFFFFFF;
    CCM->CCGR5 = 0xFFFFFFFF;
    CCM->CCGR6 = 0xFFFFFFFF;
}

void clock_init(void)
{
    //1.配置ARM Core PLL1,内核切换至528MHz
    CCM->CCSR &= ~(1 << 8);
    CCM->CCSR |= (1 << 2);  

    CCM->CACRR &= ~(7 << 0);
    CCM->CACRR |= (1 << 0); 

    unsigned int r = CCM_ANALOG->PLL_ARM;
    r &= ~(3 << 14);  
    r &= ~(0x7F << 0);
    r |= (88 << 0);       //设置倍频因子
    CCM_ANALOG->PLL_ARM = r;
    CCM_ANALOG->PLL_ARM |= (1 << 13);
    CCM->CCSR &= ~(1 << 2); 

    //2.配置528PLL 和480PLL 的PFD分频输出
    r = CCM_ANALOG->PFD_528; 
    r &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24));
    r |= ((27 << 0) | (16 << 8) | (24 << 16) | (32 << 24));
    CCM_ANALOG->PFD_528 = r;  

    r = CCM_ANALOG->PFD_480; 
    r &= ~((0x3F << 0) | (0x3F << 8) | (0x3F << 16) | (0x3F << 24));
    r |= ((12 << 0) | (16 << 8) | (17 << 16) | (19 << 24));
    CCM_ANALOG->PFD_480 = r;  

    //3.配置AHB_CLK_ROOT总线时钟
    CCM->CBCMR &= ~(3 << 18);
    CCM->CBCMR |= (1 << 18); 
    r = CCM->CBCDR;
    r &= ~(1 << 25);
    r &= ~(7 << 10);
    r |= (2 << 10);

    //配置IPG_CLK_ROOT
    r &= ~(3 << 8);
    r |= (1 << 8);
    CCM->CBCDR = r;

    //配置PERCLK_CLK_ROOT
    CCM->CSCMR1 &= ~((0x3F << 0) | (1 << 6));

    //打开所有外设时钟门控
    clock_gt_int();
}

搞定时钟这个地基,接下来学习定时器。定时器本质就是对稳定的时钟脉冲做计数,以此实现延时、定时中断、事件计数。顺带回忆51单片机定时器:8位支持自动重装,16位模式需要在中断服务函数里手动重装初值。对比之下IMX6ULL的硬件定时器功能更强大,今天重点学习EPIT和GPT这两个定时器,二者看着相似,实际定位差别很大。

EPIT增强型周期中断定时器,主打周期性中断,属于向下递减计数器。写入重装载值之后,计数器不断向下计数,计数值匹配比较寄存器时触发中断,并且可以自动重装初值,循环产生定时信号,非常适合周期性任务,比如定时采样、周期闪烁LED。

实验需求:1秒产生一次中断,在中断服务函数翻转LED。这里一定要注意,中断触发后需要写1清除中断标志位,否则会重复进入中断,造成程序卡死。

c 复制代码
#include "epit.h"
#include "MCIMX6Y2.h"
#include "led.h"
#include "interrupt.h"

//EPIT1中断服务函数
void epit1_irq_handler(void)
{
    if ((EPIT1->SR & (1 << 0)) != 0)
    {
        led_nor();              //翻转LED
        EPIT1->SR |= (1 << 0);  //写1清除中断标志,重中之重
    }
}

void epit1_init(void)
{
    unsigned int r = EPIT1->CR;
    r &= ~(3 << 24);
    r |= (1 << 24);
    r |= (1 << 17); 
    r &= ~(0xFFF << 4);
    r |= (65 << 4);
    r |= ((1 << 3) | (1 << 2) | (1 << 1));
    EPIT1->CR = r;

    EPIT1->LR = 1000*1000;     //重装载值
    EPIT1->CMPR = 0;
    EPIT1->CNR = 1000 * 1000;

    //GIC中断控制器配置
    GIC_SetPriority(EPIT1_IRQn, 0);
    system_interrupt_register(EPIT1_IRQn, epit1_irq_handler);
    GIC_EnableIRQ(EPIT1_IRQn);

    EPIT1->CR |= (1 << 0);  //使能EPIT定时器
}

GPT通用定时器,采用向上计数,功能更加丰富。除普通定时以外,还支持输入捕获、比较输出,可以测量脉冲宽度、生成PWM波形。本次实验使用GPT自由运行模式,实现硬件精准延时。

软件空循环延时会受CPU主频影响,换个主频延时就不准;而GPT硬件延时读取硬件CNT计数值,精度不受CPU负载影响。写delay_us的时候还要处理计数器溢出回零的情况,不然溢出之后计算出错,延时直接失效。

c 复制代码
#include "gpt.h"
#include "MCIMX6Y2.h"

void gpt1_init(void)
{
    GPT1->CR |= (1 << 15);                  //GPT软件复位
    while((GPT1->CR & (1 << 15)) != 0);     //等待复位完成

    unsigned int r = GPT1->CR;
    r &= ~(0xFF << 16);
    r |= (1 << 9);          //选择时钟源
    r &= ~(7 << 6);
    r |= (1 << 6);          //设置自由运行模式
    r &= ~(1 << 1);
    GPT1->CR = r;

    GPT1->PR &= ~(0XFFF << 0);
    GPT1->PR |= (65 << 0); //配置预分频

    GPT1->CR |= (1 << 0);  //使能GPT定时器
}

void delay_ms(unsigned int ms)
{
    while(ms--)
    {
        delay_us(1000);
    }
}

//硬件微秒延时,处理计数器溢出
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;
        if (new_count != old_count)
        {
            if (new_count > old_count)
            {
                count += new_count - old_count;
            }
            //计数器溢出回0的处理逻辑
            if (new_count < old_count)
            {
                count += 0xFFFFFFFF - old_count + new_count;
            }
        }
        if (us <= count)
        {
            return;
        }
        old_count = new_count;
    }
}

最后看主函数,初始化顺序非常重要:优先初始化中断控制器,再配置系统时钟,之后再初始化LED、蜂鸣器这类外设。如果外设初始化跑在时钟初始化前面,外设没有时钟供给,初始化完全无效。

下面的代码我暂时注释掉EPIT,测试GPT硬件延时实现LED闪烁;把注释打开,就可以切换为EPIT中断的方式来完成同样的效果。这里还要注意:硬件计数里面1M代表10001000,存储容量的1M才是10241024,写定时数值的时候不要搞混。

c 复制代码
#include "fsl_iomuxc.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "interrupt.h"
#include "clock.h"
#include "epit.h"
#include "gpt.h"

//软件空循环延时,仅做演示,延时不准
void led_delay(unsigned int t)
{
    while(t--);
}

int main(void)
{
    system_interrupt_init();  //初始化GIC中断控制器
    clock_init();             //系统时钟初始化
    led_init();               //LED初始化
    beep_init();              //蜂鸣器初始化
    key_init();               //按键初始化

    //epit1_init();  //注释,当前测试GPT延时
    gpt1_init();    //GPT定时器初始化

    while(1)
    {
        led_nor();
        delay_us(1000 * 1000); //1s硬件延时翻转LED
    }
    return 0;
}

今天理论结合代码跑完整套实验,最大的感悟就是嵌入式裸机开发,时钟就是地基。很多小伙伴调试外设,中断不触发、定时器不工作,排查很久,最后发现只是忘记开时钟门控或者分频配置错误。

再次总结两个定时器的区别:

EPIT:向下计数,自动重装载,适合周期性中断场景;

GPT:向上计数,功能更强,适合精准延时、输入捕获、PWM输出。

相关推荐
dadaobusi5 小时前
学习: ARM SMMU stream id
arm开发·学习
AOI小白新手上路6 小时前
韦东山《ARM 架构与编程》3-2 GPIO 引脚操作方法概述 · 学习笔记
arm开发·学习·架构
AOI小白新手上路8 小时前
韦东山《ARM 架构与编程》3-3 IMX6ULL 的 GPIO 操作方法 · 学习笔记
arm开发·学习·架构
Awh-8 小时前
ARM 裸机开发 DAY5 学习笔记
arm开发·笔记·学习
Eloudy8 小时前
全文 - 第 01 章 CHI Issue.B specification
arm开发·issue
狂奔蜗牛(bradley)21 小时前
拒绝SOEM黑盒!死磕EtherCAT主站:ARM+FPGA架构、DC同步与状态机修复等,50+篇实战开发文档全公开
arm开发·fpga开发·架构
女神下凡1 天前
芯参谋(31):SPI NOR Flash 电路设计指南
arm开发·单片机·嵌入式硬件·fpga开发·设计规范
程序员JerrySUN1 天前
Jetson边缘嵌入式实战课程第四讲:用 Yocto 定制 Jetson 系统,从开发走向产品化
arm开发·人工智能·安全·计算机视觉·目标跟踪
女神下凡7 天前
嵌入式设计的各种存储芯片的硬件/软件设计规范,非常全面。
arm开发·单片机·嵌入式硬件