TIMER
📎 原始文档:https://www.kdocs.cn/l/clz7XRyAheQk TIMER
- 概述 Timer是一个集合了定时/计数/捕获功能于一体的多功能定时器。
它的驱动源可以选择片内时钟或片外信号。
它带有一个可配置的最高达 64 的异步预分频器, 用于扩展定时时间或片外信号的最高频率。
它还具有上升沿/下降沿捕获功能,可以方便的对片外信号的高电平/低电平宽度进行测量。
- 芯片产品介绍
| 芯片 | 位数 | 捕获模式 | 反向输出 | IOMAP |
|---|---|---|---|---|
| AC695X | 16 | 向上/向下 | 有 | 支持 |
| AC696X | 16 | 向上/向下 | 有 | 支持 |
| AC701X | 32 | 向上/向下 | 有 | 支持 |
寄存器说明
寄存器说明,此处以695数据手册举例



定时器应用
定时功能 (此处以696,led7_timer.c文件举例)
static void timer2_isr()
{
//local_irq_disable(); //需要极准确的定时效果时开启
TIMER_CON |= BIT(14); //清中断标志位
//用户自定义代码
//......
//local_irq_enable(); //需要极准确的定时效果时开启
}
int led7_timer_init()
{
u32 prd_cnt;
u8 index;
printf("------------%s :%d", __func__, __LINE__);
for (index = 0; index < (sizeof(timer_div) / sizeof(timer_div[0])); index++) {
prd_cnt = TIMER_UNIT_MS * (APP_TIMER_CLK / 1000) / timer_div[index];
if (prd_cnt > MIN_TIME_CNT && prd_cnt < MAX_TIME_CNT) {
break;
}
}
__this->index = index;
__this->prd = prd_cnt;
TIMER_CNT = 0;
TIMER_PRD = prd_cnt; //1ms
request_irq(TIMER_VETOR, 6, timer2_isr, 0); //最高优先级7,需要极准确的定时效果时开启
TIMER_CON = (index << 4) | BIT(0) | BIT(3);
printf("PRD : 0x%x / %d", TIMER_PRD, clk_get("timer"));
return 0;
}
701N V160 timer demo
extern const int CONFIG_CPU_UNMASK_IRQ_ENABLE;//需要极准确的定时效果时开启不可屏蔽中断
static JL_TIMER_TypeDef *TIMER = NULL;
static int irq_id = -1;
#define TIMER_UNIT_US 100//设置中断时间,单位us
#define MAX_TIME_CNT 0x7fff //分频准确范围,更具实际情况调整
#define MIN_TIME_CNT 0x0030
static const u16 timer_div[] = {
/*0000*/ 1,
/*0001*/ 4,
/*0010*/ 16,
/*0011*/ 64,
/*0100*/ 2,
/*0101*/ 8,
/*0110*/ 32,
/*0111*/ 128,
/*1000*/ 256,
/*1001*/ 4 * 256,
/*1010*/ 16 * 256,
/*1011*/ 64 * 256,
/*1100*/ 2 * 256,
/*1101*/ 8 * 256,
/*1110*/ 32 * 256,
/*1111*/ 128 * 256,
};
static u32 time_tick_cnt = 0;
SET_INTERRUPT
static void user_timer_isr()
{
if(CONFIG_CPU_UNMASK_IRQ_ENABLE){
unmask_enter_critical();
}
else{
local_irq_disable();
}
TIMER->CON |= BIT(14); //清中断标志位
//用户自定义代码
//......
time_tick_cnt++;
if(CONFIG_CPU_UNMASK_IRQ_ENABLE){
unmask_exit_critical();
}
else{
local_irq_enable();
}
if (time_tick_cnt % 10000 == 0)
{
printf("time_tick_cnt:%d\n", time_tick_cnt);
}
}
static u8 timer_idle_query(void)
{
return TIMER == NULL;
}
REGISTER_LP_TARGET(timer_lp_target) = {
.name = "tiemr",
.is_idle = timer_idle_query,
};
int user_timer_uninit();
int user_timer_init()
{
u32 prd_cnt;
u8 index;
u32 app_timer_clk = 24000000;//时钟用STD24M
user_timer_uninit();
printf("------------%s :%d", __func__, __LINE__);
printf("%d %d %d %d %d %d\n", JL_TIMER0->CON, JL_TIMER1->CON, JL_TIMER2->CON,
JL_TIMER3->CON, JL_TIMER4->CON, JL_TIMER5->CON);
/*if((JL_TIMER0->CON & 0x3) == 0) {
TIMER = JL_TIMER0;
irq_id = IRQ_TIME0_IDX;
}
else */if((JL_TIMER1->CON & 0x3) == 0) {
TIMER = JL_TIMER1;
irq_id = IRQ_TIME1_IDX;
}
else if((JL_TIMER2->CON & 0x3) == 0) {
TIMER = JL_TIMER2;
irq_id = IRQ_TIME2_IDX;
}
else if((JL_TIMER3->CON & 0x3) == 0) {
TIMER = JL_TIMER3;
irq_id = IRQ_TIME3_IDX;
}
else if((JL_TIMER4->CON & 0x3) == 0) {
TIMER = JL_TIMER4;
irq_id = IRQ_TIME4_IDX;
}
else if((JL_TIMER5->CON & 0x3) == 0) {
TIMER = JL_TIMER5;
irq_id = IRQ_TIME5_IDX;
}
else{
return -1;
}
for (index = 0; index < (sizeof(timer_div) / sizeof(timer_div[0])); index++) {
prd_cnt = TIMER_UNIT_US * (app_timer_clk / 1000000) / timer_div[index];
if (prd_cnt > MIN_TIME_CNT && prd_cnt < MAX_TIME_CNT) {
break;
}
}
printf("irq_id %d PRD : 0x%x / %d", irq_id, prd_cnt, app_timer_clk);
TIMER->CNT = 0;
TIMER->PRD = prd_cnt - 1;
request_irq(irq_id, 2, user_timer_isr, 0);
if(CONFIG_CPU_UNMASK_IRQ_ENABLE){
irq_unmask_set(irq_id, 1, 0);
}
TIMER->CON = (index << 4) | BIT(0) | BIT(11)| BIT(12);//BIT(11)| BIT(12):时钟用STD24M
printf("PRD : 0x%x / %d", prd_cnt, app_timer_clk);
return 0;
}
int user_timer_uninit()
{
if(TIMER == NULL){
return 0;
}
TIMER->CON = BIT(14);
unrequest_irq(irq_id);
time_tick_cnt = 0;
TIMER = NULL;
return 0;
}
可视化timer demo
不可屏蔽中断(700N不支持)的修改见:项目帮助文档->7. 操作系统->7.10. 不可屏蔽中断demo
void timerx_irq_callback(int tid)
{
static int count = 0;
count++;
if(count % 10000 == 0){
printf("timerx_irq_callback\n");
}
}
#include "gptimer.h"
static int tid = -1;
void user_timer_init(void)
{
const struct gptimer_config gptimer = {
.resolution_us = 100,//100us中断
.irq_cb = timerx_irq_callback,//中断回调函数
.irq_priority = 2,//中断优先级
.tid = -1,//指定定时器id,-1为驱动自动选择空闲定时器
};
tid = gptimer_init(&gptimer);
gptimer_start(tid);
}
int user_timer_uninit()
{
if(tid != -1){
gptimer_deinit(tid);
}
}
拓展:定时器us级延时功能
- 捕捉功能
- 701NDemo 双边沿捕获功能
代码:JL701N音箱问题整理.otl
#include "asm/includes.h"
#include "timer.h"
#include "generic/gpio.h"
#include "asm/mcpwm.h"
static u8 timer_io_port = IO_PORTC_08;
static u32 timer5_clk_set;
___interrupt void timer5_double_edge_isr(void)
{
JL_TIMER5->CON |= BIT(14); // 清中断标志位
u32 timer_cnt = JL_TIMER5->PRD;
JL_TIMER5->CNT = 0; // 清计数器
u32 cur_level_sustain_us = (u64)timer_cnt * 1000000 / timer5_clk_set;
if(gpio_read(timer_io_port))
{
JL_TIMER5->CON |= (BIT(1) | BIT(0));// 如果当前为上升沿,则配置为下降沿触发
printf("low_level cnt=%d, us=%d\n", timer_cnt, cur_level_sustain_us);
}
else
{
JL_TIMER5->CON &= ~BIT(0); // 如果当前为下降沿,则配置为上升沿触发
printf("high_level cnt=%d, us=%d\n", timer_cnt, cur_level_sustain_us);
}
}
void user_timer5_edge_capture_init(void)
{
gpio_ich_sel_iutput_signal(timer_io_port, INPUT_CH_SIGNAL_TIMER5_CAPTURE, INPUT_CH_TYPE_GP_ICH);
gpio_set_direction(timer_io_port, 1);
gpio_set_die(timer_io_port, 1);
gpio_set_pull_up(timer_io_port, 1);
gpio_set_pull_down(timer_io_port, 0);
request_irq(IRQ_TIME5_IDX, 5, timer5_double_edge_isr, 0);
// 配置时钟源STD_24M为时钟源,配置分频系数为4,配置捕获模式端口为 IO Mux In,初始化为下降沿触发
JL_TIMER5->CON = ((0b0110 << 10) | (0b0001 << 4) | BIT(1) | BIT(0));
timer5_clk_set = 24000000 / 4;
printf("timer5_edge_capture_init, timer5_clk_set=%d\n", timer5_clk_set);
// 初始化一个mcpwm通道,用于测试验证用
timer_pwm_init(JL_TIMER3, IO_PORTG_07, 1000, 2000);
}
- 696NDemo 代码:AC695N&AC696N&AC608N音箱问题整理.otl
//基于696程序 存在一些IO口无法做此功能,先测试,再画板 !!!!!!
#define TIMER5 7 //见irflt.h
#define CATCH_TIMER TIMER5
#define CATCH_IRQ_TIME_IDX IRQ_TIME5_IDX
#define CATCH_TIME_REG JL_TIMER5
#define CATCH_GPIO IO_PORTB_06
int num = 0;
void timer_ms()
{
static int flag = 0;
if(flag){
gpio_set_pull_down(IO_PORTA_02,0);
gpio_set_pull_up(IO_PORTA_02,0);
gpio_set_direction(IO_PORTA_02,1);
flag = 0;
}
else{
gpio_set_pull_down(IO_PORTA_02,0);
gpio_set_pull_up(IO_PORTA_02,0);
gpio_set_direction(IO_PORTA_02,0);
gpio_set_output_value(IO_PORTA_02,0);
putchar('0');
flag = 1;
}
}
___interrupt
void timer_catch_isr(void)
{
CATCH_TIME_REG->CON |= BIT(14);
u16 bCap1 = CATCH_TIME_REG->PRD; //上次中断到这次的时间,可以用于计时
CATCH_TIME_REG->CNT = 0;
num++; //计数
}
void timer_100ms()
{
printf("num == %d\n",num);
num = 0;
}
void timer_catch_init()
{
printf("timer_catch_init\n");
//IO口配置
gpio_irflt_in(CATCH_GPIO);
gpio_set_direction(CATCH_GPIO, 1);
gpio_set_die(CATCH_GPIO, 1);
gpio_set_pull_up(CATCH_GPIO, 1);
gpio_set_pull_down(CATCH_GPIO, 0);
//设置时钟
SFR(JL_IOMAP->CON0, 5, 3, CATCH_TIMER); //TIMER5:7 irflt.h
CATCH_TIME_REG->CON = 0;
CATCH_TIME_REG->CON |= (0b10 << 2); //选择晶振时钟源:24MHz
CATCH_TIME_REG->CON |= (0b0001 << 4); //时钟源4分频
//设置周期,初始值,定时器模式
CATCH_TIME_REG->PRD = OSC_Hz / (4 * 1000);
CATCH_TIME_REG->CNT = CATCH_TIME_REG->PRD;
CATCH_TIME_REG->CON |= BIT(14);
CATCH_TIME_REG->CON |= (0b11 << 0);
request_irq(CATCH_IRQ_TIME_IDX, 5, timer_catch_isr, 0);
sys_timer_add(NULL,timer_100ms,100); //定时读取中断次数
sys_timer_add(NULL,timer_ms,1); //设置生成波形,供中断采集,不准,非1ms
}
701_sdk
void io_ext_interrupt_test(void)
{
set_io_ext_interrupt_cbfun(my_io_isr_cbfun);
io_ext_interrupt_init(0, IO_PORTA_04, 1);
io_ext_interrupt_init(1, IO_PORTB_01, 1);
io_ext_interrupt_init(2, IO_PORTC_02, 1);
io_ext_interrupt_init(3, IO_PORTG_01, 1);
extern void wdt_clear();
while (1) {
wdt_clear();
}
}
- 695NDemo 代码:AC695N&AC696N&AC608N音箱问题整理.otl
- 微秒延时使用的定时器 701_sdk
//ums timer
#define USER_UDELAY_TIMER JL_TIMER3//微秒延时使用的定时器 地址 可以其他的定时器
#define USER_UDELAY_TIMER_IRQ IRQ_TIME3_IDX//微秒延时使用的定时器 终端号
/**
* @brief 初始化 自定义微秒延时定时器
*
*/
void user_udelay_init(void){
bit_clr_ie(USER_UDELAY_TIMER_IRQ);
}
/**
* @brief 自定义微秒延时接口
*
* @param usec 延时时间 单位微秒
*/
__attribute__((weak)) void user_udelay(u32 usec){
USER_UDELAY_TIMER->CON = BIT(14);//0x4000;
USER_UDELAY_TIMER->CNT = 0;
USER_UDELAY_TIMER->PRD = (24* usec/4) ;//延时时间=时钟源频率/分频系数*计数次数
USER_UDELAY_TIMER->CON = BIT(0)|BIT(3)|BIT(4);//BIT(0)定时计数模式 BIT(3):晶振为时钟源 BIT(4):4分频
while ((USER_UDELAY_TIMER->CON & BIT(15)) == 0);
USER_UDELAY_TIMER->CON = BIT(14);
}
void user_delay_us(u32 usec)
{
user_udelay(usec);
}
- PWM 重要参数介绍:占空比、频率
- 占空比: 一个周期内高电平占整个周期的时间比。

以AC695N为例子(下图寄存器):输出的占空比取决于设置的我们两个寄存器Tx_PR,Tx_PWM的值。其Tx_PWM实际取决高电平的宽度(0~Tx_PR:0为占空比为0,一直低电平)。
占空比计算公式 N = (Tx_PWM / Tx_PR) * 100%

- 频率: PWM波形一般由一个固定的时钟控制产生,一般时钟的频率是固定的,所以有以下关系: 时钟 = 频率 * 周期时间。
周期时间:就是一个周期内可以改变的精度,如果一个周期内有100个时间点,那占空比控制的精度就是1/100。
所以当提高频率时,占空比控制的精度就会下降。
参考代码:
使用mcpwm.c文件的timer_pwm_init函数产生。
timer_pwm_init函数介绍:
#define OSC_Hz 24000000
/**
* @param JL_TIMERx : JL_TIMER0/1/2/3/4/5
* @param fre : 频率,单位Hz,不小于95
* @param duty : 初始占空比,0~10000对应0~100%
* @param port : pwm脚,可选硬件脚,也可选非硬件脚。(建议选择硬件引脚)
* @param output_ch : 映射通道,当pwm脚选择非硬件脚时有效,这时我们给他分配output_channel 0/1/2
*/
int timer_pwm_init(JL_TIMER_TypeDef *JL_TIMERx, u32 fre, u32 duty, u32 port, int output_ch)
{
u32 hw_port;
switch ((u32)JL_TIMERx) {
case (u32)JL_TIMER0 :
hw_port = IO_PORTA_05;
break;
case (u32)JL_TIMER1 :
hw_port = IO_PORTC_04;
break;
case (u32)JL_TIMER2 :
hw_port = IO_PORTB_03;
break;
case (u32)JL_TIMER3 :
bit_clr_ie(IRQ_TIME3_IDX);
hw_port = IO_PORTB_05;
break;
case (u32)JL_TIMER4 :
hw_port = IO_PORTA_01;
break;
case (u32)JL_TIMER5 :
hw_port = IO_PORTB_07;
break;
default:
return (-1);
}
if ((output_ch == (-1)) && (hw_port != port)) {
//not support output_ch
return (-1);
}
//初始化timer
JL_TIMERx->CON = 0;
JL_TIMERx->CON |= (0b10 << 2); //选择晶振时钟源:24MHz
JL_TIMERx->CON |= (0b0001 << 4); //时钟源4分频
JL_TIMERx->PRD = OSC_Hz / (4 * fre); //设置周期
JL_TIMERx->PWM = (JL_TIMERx->PRD * duty) / fre; //0~10000对应0~100g
JL_TIMERx->CNT = 0; //清计数值
JL_TIMERx->CON |= (0b01 << 0); //计数模式
if (hw_port == port) {
gpio_set_die(hw_port, 1);
gpio_set_pull_up(hw_port, 0);
gpio_set_pull_down(hw_port, 0);
gpio_set_direction(hw_port, 0);
JL_TIMERx->CON |= BIT(8); //PWM使能
} else {
gpio_output_channle(port, output_ch);
return 1;
}
return 0;
}
参数输入如上图所示,注意点如下:
每个定时器都对应一个固定的IO口,如TIMER1->IO_PORTA_12,如果在芯片上能找到对应定时器的对应脚则优先使用。没有对应IO口的情况下,则可以任选一个IO口,然后通过通道映射的方式来使选择的IO口产生PWM。 示例:
timer_pwm_init(JL_TIMER3, 2000, 5000, IO_PORTB_05, 0); //对应IO口产生
timer_pwm_init(JL_TIMER3, 2000, 5000, IO_PORTB_04, CH2_T3_PWM_OUT); //通道映射
通道可以在gpio.h文件查询到,CHx_Tn_PWM_OUT,x代表通道x,n代表定时器,每个通道只能映射一个IO口,定时器必须跟选择的定时器一致。
备注:定时器无论是正常对应IO口输出,映射输出都只会产生一个PWM波形,由最后执行的函数为准,且映射输出时,定时器对应的IO口也会产生对应波形,所以最优先选择定时器对应的IO口,避免造成IO口浪费。占空比,公版代码的占空比输入不是0-1000控制0-100%,如果需要改成此效果改此处: JL_TIMERx->PWM = (JL_TIMERx->PRD * duty) / 10000; //0~10000对应0~100%
时钟选择: JL_TIMERx->CON |= (0b10 << 2); //选择晶振时钟源:24MHz
公版SDK默认选择晶振时钟源,695,696均为24MHZ,608由于不带晶振的缘故需要选择系统时钟源,配置如下:
JL_TIMERx->CON |= (0b00 << 2); //选择系统时钟源
695,696的系统时钟源一般会比晶振时钟源的时钟高,695为60MHZ,如果需要产生高频率的PWM波形时可以改为系统时钟源。
DEMO
- AC696X 对于使用 AC696X 需要 PWM 功能的, 可以见如下参考例子, 详情请见网盘链接:
链接: https://pan.baidu.com/s/166ibbi08er-tHo5LVHr11Q
提取码: s2qq