AC7006定时器的使用

AC7006定时器的使用

1.TWS对耳没有配对一段时间后(一分钟)关机

1
2
3
4
5
6
7
8
//定时器的应用
//添加宏 board_ac700n_demo_cfg.h
#define TCFG_PAIR_AUTO_SHUT_DOWN_TIME 60 //对耳没有配对关机时间

//使用定时器timeout,超过时间后关机
//用于打开和关闭定时器:
void sys_pair_auto_shut_down_enable(void)
void sys_pair_auto_shut_down_disable(void)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//bt_tws.c
static u16 pair_timer = 0;
static void sys_pair_auto_shut_down_deal(void *priv)
{

printf("%s\n", __func__);
#if TCFG_USER_TWS_ENABLE
if (get_bt_tws_connect_status() == 0) //对耳没有连接的状态下
{
if(get_bt_connect_status() == BT_STATUS_WAITINT_CONN)
{
printf("----------not pair and not connect ---------- enter power off !!! ");
extern void sys_enter_soft_poweroff(void *priv);
sys_enter_soft_poweroff(0);
}
}
#endif
}
1
2
3
4
5
6
7
8
9
void sys_pair_auto_shut_down_enable(void)
{
#if TCFG_PAIR_AUTO_SHUT_DOWN_TIME
printf("%s\n", __func__);
if (pair_timer == 0) {
pair_timer = sys_timeout_add(NULL, sys_pair_auto_shut_down_deal, (TCFG_PAIR_AUTO_SHUT_DOWN_TIME* 1000));
}
#endif
}
1
2
3
4
5
6
7
8
9
10
void sys_pair_auto_shut_down_disable(void)
{
#if TCFG_PAIR_AUTO_SHUT_DOWN_TIME
printf("%s\n", __func__);
if (pair_timer) {
sys_timeout_del(pair_timer);
pair_timer = 0;
}
#endif
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//打开和关闭
#if TCFG_PAIR_AUTO_SHUT_DOWN_TIME
sys_pair_auto_shut_down_disable();
#endif

#if TCFG_PAIR_AUTO_SHUT_DOWN_TIME
sys_pair_auto_shut_down_enable();
#endif

//TWS连接后disable
//配对超时后enable
//TWS断开后,手机也未连接时enable
//更新TWS连接 UI状态后disable
//开机后enable
//蓝牙断开后,对耳未连接enable

2.定时器相关(AC69NX )

功能说明

每个芯片系列的硬件定时器timer个数,是不一样的。

  • AC695X => timer0-3 总共4个(timer1用作系统定时器,禁止修改使用),16位定时器
  • AC696X => timer0-5 总共6个(timer1用作系统定时器,禁止修改使用),16位定时器
  • AC632X => timer0-3 总共4个(timer1用作系统定时器,禁止修改使用),32位定时器

所有蓝牙芯片的SDK,都默认使用了timer1,初始化成1ms的定时中断功能,作为整个系统的滴答时钟。所以timer1是禁用的。

部分代码驱动需要us级的精度延时需求时,可能会有 偶尔使用timer0做延时功能 。详见函数: static void udelay(u32 usec)

硬件定时器有3种使用模式,对应不同的功能,互斥使用:

  • 定时中断/延时 => 一般可实现100us以上的定时中断频率(小于100us会有动态误差出现),或者4us以上的延时需求。
  • PWM输出 => 可输出1MHz到几Hz,也能输出高于1MHz,甚至几十MHz,但需要修改其他功能来辅助。
  • 边沿捕获 => 上升沿或下降沿捕获,无法同时,但可在中断函数里做动态切换,一般能捕获到10kHz以下的方波或过零点判断。

时钟选择(虽然有4种时钟源,但一般只使用2种):

  • LSB时钟:低速总线时钟,用于MCU方案,板子不带晶振的情况。 切记不可修改系统时钟。 需要从开机开始全程跑固定系统频率。
  • 24M晶振时钟:带晶振的板子,建议选此晶振,系统可以动态修改系统频率,而不会影响到外设。

接口说明

SDK没有现成的固定接口,只有一些demo参考,需要视需求,二次修改寄存器。

定时中断可参考 app_timer.c

PWM可参考 timer_pwm_init()

边沿捕获可参考红外功能 irflt.c

寄存器说明

1
2
3
4
5
6
7
//寄存器位含义详见 对应芯片的用户手册
typedef struct {
__RW __u32 CON; //部分位只有可读或可写,并非所有位均可读写
__RW __u32 CNT; //启动前可写,启动后可读,启动后硬件会让此寄存器按照时钟频率自增,起pend后停止自增
__RW __u32 PRD; //虽然为u32,但对于635是16位,必要时需检查赋值溢出,用作中断频率或PWM频率
__RW __u32 PWM; //虽然为u32,但对于635是16位,必要时需检查赋值溢出,PWM输出时用作占空比
} JL_TIMER_TypeDef;

timer设置范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
JL_TIMER_TypeDef *TIMER = JL_TIMER0; //指定定时器
if(TIMER->CON != 0){
printf("TIMER:0x%x already used", TIMER);
return -1;
}
TIMER->CON = 0; //clear control bit
TIMER->CON |= BIT(14); //clear pend
//时钟源选择
TIMER->CON |= (0b10 << 2); //对于635是BIT(2)起始,晶振(0b10),LSB(0b00)
TIMER->CON |= (0b0001 << 10); //对于632是BIT(10)起始,晶振(0b0001),LSB(0b0000)
//选择分频,所有芯片外设的最高时钟,为LSB的2分之1
//要看定时的频率,避免PRD溢出,时间越长,分频就要越大,PRD的值就越小,不容易造成溢出。
TIMER->CON |= (0b0001 << 4); //所有芯片是BIT(4)开始,一般至少为4分频(0b0001)
//启动TIMER之前需要初始化其他寄存器

//PRD周期值算法: 时钟源频率/分频系数/设置的频率 或 时钟源频率/分频系数*设置的时间(us)/1000000L
//如果溢出PRD寄存器的最大值,需要加大分频系数
TIMER->PRD = OSC_Hz / 4 / fre; //晶振源
TIMER->PRD = clk_get("timer") / 4 * usec / 1000000L; //lsb时钟源

//CNT, 可以初始化为最小值或最大值, 视需求选择
TIMER->CNT = 0;
TIMER->CNT = TIMER->PRD;

//PWM, PWM输出时, 则需要设置占空比, 占空比分辨比:100份/255份/1000份等
TIMER->PWM = (TIMER->PRD * duty) / 100; //占空比按100份分
//PWM默认IO输出
TIMER->CON |= BIT(8); // 如映射输出,则不能设置此位

//最后才能启动
TIMER->CON |= (0b01 << 0); //设置为3种模式中一种,定时(0b01)

监控timer使用状态

1
2
3
JL_TIMER_TypeDef *TIMER = JL_TIMER0; //指定定时器
printf("timer:CON-0x%08x,PRD-0x%08x,PWM-0x%08x\n", TIMER->CON, TIMER->PRD, TIMER->PWM);
///判断剩余哪些定时器能用,把所有定时器按照上面打印出来,CON为0,就可以用

定时中断范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//AC632X的范例,其他芯片可能需要小改
/////下面函数调用的使用函数都必须放在ram,中断频率小于1ms的,都建议加AT_VOLATILE_RAM_CODE,将代码指定到ram
___interrupt
AT_VOLATILE_RAM_CODE
static void timer3_isr()
{
JL_TIMER3->CON |= BIT(14);
static u8 flag = 0;
if(flag){
flag = 0;
/* gpio_set_output_value(IO_PORTA_04, 0); */
JL_PORTA->OUT &= ~BIT(8);
}else{
flag = 1;
/* gpio_set_output_value(IO_PORTA_04, 1); */
JL_PORTA->OUT |= BIT(8);
}
cnt_100us++;
}
void timer3_100us(void)
{
log_info("%s[%d]", __func__, clk_get("lsb"));
u32 prd_cnt;
prd_cnt = clk_get("lsb") / 1000 / 4 / 10 * 1;//需要几倍就改最后1
log_info("%s[0x%08x]", __func__, prd_cnt);

request_irq(IRQ_TIME3_IDX, 3, timer3_isr, 0);
JL_TIMER3->CNT = 0;
JL_TIMER3->PRD = prd_cnt;
JL_TIMER3->CON = (1 << 4) | (1 << 0);//lsb clk 4分频

gpio_set_pull_up(IO_PORTA_08, 0);
gpio_set_pull_down(IO_PORTA_08, 0);
gpio_set_die(IO_PORTA_08, 1);
gpio_set_direction(IO_PORTA_08, 0);
}