零基础开发小安派-Eyes-S1【外设篇】——TIMER
TIMER也就是定时器,定时器故名思意就是用来定时的东西,可以根据时钟源来分配计时的时间周期,实现准确的计时,一般软件的定时会出现误差,一些特殊情况需要精准的定时,那就需要使用到硬件定时器,如定时5分钟执行某些特殊任务。定时器可以搭配中断来使用,利用好时间间隔而满足我们的需求。
一、了解小安派-Eyes-S1的TIMER
芯片内置了两个32-Bit定时器,这两个定时器在LHAL库里对应timer0和timer1。
这两组TIMER有以下特征:
• 多种时钟来源,最高可支持 80M 时钟
• 8-bit 时钟分频器,分频系数为 1-256
• 两个 32-bit 定时器:channel 0 和 channel 1
• 定时器包含三组报警值设定,可设定报警值溢出时报警
• 支持 Free Run 模式和 Pre_load 模式
• 一个 16-bit 看门狗定时器
• 支持写入密码保护,防止误设定造成系统异常
• 支持中断或复位两种看门狗溢出方式
• 支持测量外部 GPIO 的脉冲宽度
定时器的时钟来源有以下五种选择:
• BCLK--总线时钟
• 32K--32K 时钟
• 1K--1K 时钟(32K 的分频)
• XTAL--外部晶振
• GPIO--外部 GPIO
#define TIMER_CLKSRC_BCLK 0
#define TIMER_CLKSRC_32K 1
#define TIMER_CLKSRC_1K 2
#define TIMER_CLKSRC_XTAL 3
#define TIMER_CLKSRC_GPIO 4
#define TIMER_CLKSRC_NO 5
计数模式有以下两种:
定时器计数模式分为两种: freerun(向上计数模式)、preload(重装载模式)。
#define TIMER_COUNTER_MODE_PROLOAD 0
#define TIMER_COUNTER_MODE_UP 1
定时器一共三个 compare id, 用于设置不同的定时时间,可以当三个定时器使用。
#define TIMER_COMP_ID_0 0
#define TIMER_COMP_ID_1 1
#define TIMER_COMP_ID_2 2
二、结构体与函数接口
struct bflb_timer_config_s
说明:Timer初始化配置结构体。
struct bflb_timer_config_s {
uint8_t counter_mode;
uint8_t clock_source;
uint8_t clock_div;
uint8_t trigger_comp_id;
uint32_t comp0_val;
uint32_t comp1_val;
uint32_t comp2_val;
uint32_t preload_val;
};
parameter |
description |
counter_mode |
计数模式 |
clock_source |
时钟源选择 |
clock_div |
分频值 |
trigger_comp_id |
选择触发的最高 comp id(选择越高,则能够定时的个数越多) |
comp0_val |
comp0 比较值 |
comp1_val |
comp1 比较值(需要大于 comp0_val) |
comp2_val |
comp2 比较值(需要大于 comp1_val) |
preload_val |
重装载值 |
bflb_timer_init
说明: 初始化 timer。使用之前需要开启 timer ip 时钟。
void bflb_timer_init(struct bflb_device_s *dev, const struct bflb_timer_config_s *config);
parameter |
description |
dev |
设备句柄 |
config |
配置项 |
bflb_timer_deinit
说明: 反初始化 timer。
void bflb_timer_deinit(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_timer_start
说明: 启动 timer 。
void bflb_timer_start(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_timer_stop
说明: 停止 timer。
void bflb_timer_stop(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
bflb_timer_set_compvalue
说明: 设置 timer comp id 比较值。
void bflb_timer_set_compvalue(struct bflb_device_s *dev, uint8_t cmp_no, uint32_t val);
parameter |
description |
dev |
设备句柄 |
cmp_no |
comp id |
val |
比较值 |
bflb_timer_get_compvalue
说明: 获取 comp id 比较值。
uint32_t bflb_timer_get_compvalue(struct bflb_device_s *dev, uint8_t cmp_no);
parameter |
description |
dev |
设备句柄 |
cmp_no |
comp id |
val |
比较值 |
bflb_timer_get_countervalue
说明: 获取 timer 计数值。
uint32_t bflb_timer_get_countervalue(struct bflb_device_s *dev);
parameter |
description |
dev |
设备句柄 |
return |
计数值 |
bflb_timer_compint_mask
说明: timer comp 中断屏蔽开关。
void bflb_timer_compint_mask(struct bflb_device_s *dev, uint8_t cmp_no, bool mask);
parameter |
description |
dev |
设备句柄 |
cmp_no |
comp id |
mask |
是否屏蔽中断 |
bflb_timer_get_compint_status
说明: 获取 timer comp id 中断匹配标志。
bool bflb_timer_get_compint_status(struct bflb_device_s *dev, uint8_t cmp_no);
parameter |
description |
dev |
设备句柄 |
cmp_no |
comp id |
return |
为 true 表示匹配 |
bflb_timer_compint_clear
说明: 清除 timer comp id 中断标志。
void bflb_timer_compint_clear(struct bflb_device_s *dev, uint8_t cmp_no);
parameter |
description |
dev |
设备句柄 |
cmp_no |
comp id |
三、定时器的两种计数方式以及中断触发
一、定时器时钟源的选择以及分频
以选择TIMER_CLKSRC_XTAL这个外部晶振的时钟源来举例,频率为40MHz,而分频系数,也就是结构体中的clock_div,这里系数可选0~255,选择39,时钟计数=时钟频率/(分频系数+1)。也就是40Mhz/(39+1),也就是1Mhz,而周期与频率互为倒数,也就是1us一个计数。这样分频的话就是一微秒计数+1。
二、计数模式
TIMER有两种计数模式,分别是freerun(向上计数模式)、preload(重装载模式)。
FreeRun模式下,计数器的初始值为0,定时器开始后,累加计数,当达到计数最大值后,然后从 0 再次开始计数。而最大值的数量估计是comp0的数据类型最大值,也就是32位数据。
相比之下,PreLoad模式就好用多了,计数器的初始值是 PreLoad 寄存器的值,然后向上累加计数,当满足 PreLoad 条件时,计数器的值被置为 PreLoad 寄存器的值,然后计数器再次开始向上累加计数。
三、中断
结构体有trigger_comp_id选择几个比较ID,如果选择三个ID的情况下,在定时器的计数器计数过程中,一旦计数器的值与三个比较器中的某比较值一致,该比较器的比较标志就会置位,并可以产生相应的比较中断。在所有的ID中断调节都达到后,会回到PreLoad的值,也就是preload_val重新开始计时。有如下一个示例的时序图,若预加载寄存器的值为 10,比较器 0 的值为 13,比较器 1 的值为 16,比较器 2 的值为 19。
在 FreeRun 模式下,定时器工作时序与 PreLoad 基本相同,只是计数器会从 0 开始累计到最大值,期间产生的比较标志和比较中断的机制与 FreeRun 模式相同。
四、简单示例——定时器分频一秒进入一次中断,在中断修改全局变量在主函数中打印
Main
#include "bflb_mtimer.h"
#include "bflb_timer.h"
#include "board.h"
struct bflb_device_s *timer0;
volatile static uint16_t MyTime_s = 0; //定义一个全局变量,在中断中修改,这里注意要用volatile关键字防止变量被优化
void timer0_isr(int irq, void *arg)
{
bool status = bflb_timer_get_compint_status(timer0, TIMER_COMP_ID_0);
if (status) {
bflb_timer_compint_clear(timer0, TIMER_COMP_ID_0);
if (MyTime_s==60)
{
MyTime_s = 0;
}
MyTime_s++;
printf("time is %d\r\n",MyTime_s);
}
}
//中断服务函数,每进入一次变量自增1,到达60也就是1分钟置为0
int main(void)
{
board_init();
printf("Timer basic test\n");
/* timer clk = XCLK/(div + 1 )*/
struct bflb_timer_config_s cfg0;
cfg0.counter_mode = TIMER_COUNTER_MODE_PROLOAD; /* 选择重装载模式 */
cfg0.clock_source = TIMER_CLKSRC_XTAL;//选择外部时钟晶振,40MHz
cfg0.clock_div = 39; /* for bl616/bl808/bl606p is 39, for bl702 is 31 */
cfg0.trigger_comp_id = TIMER_COMP_ID_0;//选择比较ID的个数,这里选择一个ID,也就是只会到达下面的ID1
cfg0.comp0_val = 1000000; /* 比较值ID1,当计数达到1000000时,根据前面的分频一微秒一个计数,也就是总共1秒 */
cfg0.comp1_val = 2500000; /* 比较值ID2,需要大于ID1,由于前面只设置了一个ID,所以这里不会触发 */
cfg0.comp2_val = 3500000; /* 比较值ID2,需要大于ID2,由于前面只设置了一个ID,所以这里不会触发 */
cfg0.preload_val = 0; /* 重装载值,开始的值,以及比较完所有ID个数后重启的值 */
timer0 = bflb_device_get_by_name("timer0");
/* Timer init with default configuration */
bflb_timer_init(timer0, &cfg0);
bflb_irq_attach(timer0->irq_num, timer0_isr, NULL);
bflb_irq_enable(timer0->irq_num);
/* Enable timer */
bflb_timer_start(timer0);//开启定时器
printf("case success.\r\n");
while (1) {
switch (MyTime_s)
{
case 10:
printf("10 seconds have passed\r\n");
break;
case 20:
printf("20 seconds have passed\r\n");
break;
case 30:
printf("30 seconds have passed\r\n");
break;
case 40:
printf("40 seconds have passed\r\n");
break;
case 50:
printf("50 seconds have passed\r\n");
break;
case 60:
printf("One minute has already passed\r\n");
break;
default:
break;
}
//对全局变量进行判断,通过switch语句分别打印
bflb_mtimer_delay_ms(900);
//这个延迟是为了防止在主函数中重复判断导致疯狂打印
}
}
效果