EPIT 是周期性中断定时器,会定期调用指定的中断服务函数,其实可以看做是一种IRQ的外设中断,对应的中断ID是88(=56+32)或者 89(=57+32)。
目录
一、定时器计数流程
二、寄存器解析
1、EPIT1_CR
2、EPIT1_LR
3、EPIT1_CMPR
4、EPIT1_CNR
5、EPT1_SR
三、其他初始化操作
1、全局中断初始化
2、GIC 的 EPIT 中断使能
3、注册中断服务函数
四、总结
一、定时器计数流程
定时器计数主要涉及到三个寄存器:
- count register:计数器启动以后,每隔一个时钟周期,自减1
- compare register:当 count register == compare register 时,触发中断,调用对应的中断服务函数
- load register:处理完中断服务函数以后,下一次开始自减的初始值是多少(有两种模式,分别对应着不同的初始值)
这里使用简单的 C 代码阐述一下这三个寄存器之间的关系
/*
* mode: 表示计数模式
* count register: 计数寄存器(每隔一个时钟周期,自减1)
* compare register: 比较寄存器(保存每次停止自减的临界值)
* load register: 加载寄存器。保存每次重新自减的初始值
*/
if(mode == 0) // 每次从 0xffff ffff 开始自减
count_register = 0xffffffff;
else // mode = 1,每次从 load register 开始自减
count_register = load_register;
// 假设每一次while循环都是一个时钟周期
while(1)
{
if(count_register == compare_register) // 停止计数
{
// 调用中断服务函数
// 重新开始计数
if(mode == 0) // 每次从 0xffff ffff 开始自减
count_register = 0xffffffff;
else // mode = 1,每次从 load register 开始自减
count_register = load_register;
}
else
count_register --;
}
二、寄存器解析
了解定时器的计数流程后,我们也大致知道了计数过程需要用到哪些寄存器,整个过程涉及到的寄存器如下:
- EPIT1_CR —— 配置 EPIT(选择时钟源、计数使能、比较使能等)
- EPIT1_LR —— 对应 load register
- EPIT1_CMPR —— 对应 compare register
- EPIT1_CNR —— 对应 count register
- EPT1_SR —— 记录 EPIT 的状态(每次处理完中断,都要置1)
1、EPIT1_CR
CR 是控制寄存器,用于配置EPIT的使能、分频、时钟源等。主要配置的字段:15~0 bit、25~24 bit
bit 0:EPIT使能。设为 1
bit 1:EPIT打开以后的最初计数值来源。0 表示从上一次关闭EPIT时的值开始,1 表示从 Load Register 或者 0xFFFF_FFFF 开始。设为 1
bit 2:比较寄存器使能。设为 1
bit 3:计数加载控制,也就是模式选择。mode = 0 表示当计数器和比较寄存器相等时,下一次从0xFFFF_FFFF 开始;mode = 1 表示当计数器和比较寄存器相等时,下一次从Load Register 的值开始。设为 1
bit 15-4:时钟源分频数。取值为0~4095
bit 25-24:选择时钟源。我们选择 01,即 ipg_clk(66MHz)
寄存器: EPIT1_CR
基地址: 0x20D0000
初始化操作:
// bit 3-0
EPIT1_CR &= ~(0xF); // bit 3-0 清零
EPIT1_CR |= 0xF;
// bit 15-4
EPIT1_CR &= ~(0xFFF0); // bit 15-4 清零
EPIT1_CR |= (0x1 << 4); // 2 分频
// bit 25-24
EPIT1_CR &= ~(0x3 << 24); // bit 25-24 清零
EPIT1_CR |= (0x1 << 24); // 01
2、EPIT1_LR
LR 是加载寄存器,保存每次触发中断以后,重新计数的起始值。默认起始值是 0xFFFF FFFF。假设定时器的时间间隔为 1 s,时钟频率是 66MHz / 2 = 33 MHz(这里的2表示2分频),即时钟周期是 1/33M s
因此,LR 寄存器的值应设为 33000000,对应十六进制为 0x1F7 8A40
寄存器: EPIT1_LR
基地址: 0x20D0008
初始化操作:
EPIT1_LR = 0x1F78A40;
3、EPIT1_CMPR
比较寄存器决定何时产生中断,因为当计数寄存器和比较寄存器相等时,就会触发中断,从而调用对应的中断服务函数。默认值是 0 (一般无需设置)
寄存器: EPIT1_CMPR
基地址: 0x20D000C
初始化操作:
EPIT1_CMPR = 0;
4、EPIT1_CNR
计数寄存器保存着当前计数值。初始情况下可以和上面 LR 寄存器的值保持一致
寄存器: EPIT1_CNR
基地址: 0x20D0010
初始化操作:
EPIT1_CNR = 0x1F78A40;
5、EPT1_SR
EPIT状态寄存器记录着EPIT中断是否发生。在中断服务函数执行完毕以后,要清除标志位,即改寄存器要置1。
寄存器: EPIT1_SR
基地址: 0x20D0004
清除标志位操作:
EPIT1_SR = 1;
三、其他初始化操作
1、全局中断初始化
参考:IRQ 全局中断初始化
2、GIC 的 EPIT 中断使能
EPIT 的中断ID 在本文开始位置已经给出了,如果要用 EPIT1,中断ID = 88;如果要使用 EPIT2,中断ID = 89。
/* EPIT1 中断使能 */
GIC_EnableIRQ(88);
3、注册中断服务函数
当定时器中断触发时,我们反转 LED 灯的状态。不要忘记清除中断标志位!
static unsigned int status = OFF;
void epit_irqhandler(void* userParams)
{
switch_led(status);
status = !status;
EPIT1_SR = 1; // 清除中断标志位
}
void led_on()
{
GPIO1_DR &= ~(1 << 3);
}
void led_off()
{
GPIO1_DR |= (1 << 3);
}
void switch_led(uint8_t status)
{
if (status == ON)
{
led_on();
}
else
{
led_off();
}
}
四、总结
static unsigned int status = OFF;
/*
* frac: 代表分频数
* interval: 代表定时器的时间间隔
*/
void epit_init(unsigned int frac, unsigned int interval)
{
/* EPIT1 中断使能 */
GIC_EnableIRQ(88);
/* 注册中断服务函数 */
register_irqhandler(88, epit_irqhandler);
/* EPIT1 使能、分频、时钟源初始化 */
// bit 3-0
EPIT1_CR &= ~(0xF); // bit 3-0 清零
EPIT1_CR |= 0xF;
// bit 15-4
EPIT1_CR &= ~(0xFFF0); // bit 15-4 清零
EPIT1_CR |= (0x1 << 4); // 2 分频
// bit 25-24
EPIT1_CR &= ~(0x3 << 24); // bit 25-24 清零
EPIT1_CR |= (0x1 << 24); // 01
/* 加载寄存器 */
EPIT1_LR = 0x1F78A40;
/* 比较寄存器 */
EPIT1_CMPR = 0;
/* 计数寄存器 */
EPIT1_CNR = 0x1F78A40;
}
void epit_irqhandler(void* userParams)
{
switch_led(status);
status = !status;
EPIT1_SR = 1; // 清除中断标志位
}