Введение
Таймеры кажутся простыми, но их неправильное использование либо увеличивает энергопотребление, либо приводит к неточностям синхронизации – и отладка может быть весьма болезненной. SDK для JL JieLi AC696N предоставляет два набора таймеров: sys_timer и usr_timer. При первом знакомстве легко запутаться – когда какой использовать? Почему мой таймер начинает работать неточно после перехода в сон? Можно ли выполнять работу в callback-функции? Со всеми этими вопросами я столкнулся при отладке решений с низким энергопотреблением на плате разработки AC696N. Вот краткое изложение различий и логики выбора, чтобы можно было напрямую обращаться к нему при написании кода.
Информация о чипе
ЧипРазрядностьРежим захватаРеверсивный выводIOMAPAC696X32Вверх/ВнизЕстьПоддерживается
I. Системный таймер (sys_timer) – "Программный таймер"
Особенности: Управляется потоком systimer, синхронный интерфейс. Callback-функция выполняется в том же потоке, где был добавлен таймер.
Низкое энергопотребление: Система может спать; таймер разбудит систему по истечении времени, тики не теряются.
Применение: Подходит для задач, требующих периодического выполнения и допускающих небольшие задержки.
Интерфейсы: sys_timer_add, sys_timer_del, sys_timeout_add (однократный)
II. Пользовательский таймер (usr_timer) – "Аппаратный таймер"
Особенности: Управляется аппаратным таймером, асинхронный интерфейс, callback-функция выполняется в контексте прерывания.
Влияние приоритета:
- priority=1: Система не может переходить в режим низкого энергопотребления, синхронизация точная
- priority=0: Система может спать, но период таймера может увеличиваться из-за сна
Применение: Подходит для задач, требующих точной синхронизации и быстрого реагирования, или однократных задержек.
Интерфейсы: usr_timer_add, usr_timer_del, usr_timeout_add
III. Применение таймеров
1) Функция синхронизации
Пример на основе файла led7_timer.c для AC696:
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;
}
2) Функция захвата
• Пример кода
// На основе программы для AC696. Некоторые выводы могут не поддерживать эту функцию – сначала протестируйте, потом проектируйте плату!!!!!!
#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");
// Конфигурация ввода-вывода
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); // Генерация сигнала для захвата прерывания (неточно, не ровно 1мс)
}
Руководство по выбору
- Требуется низкое энергопотребление и нет высоких требований к реальному времени → Используйте sys_timer
- Требуется точная синхронизация или быстрое реагирование, энергопотребление не критично → Используйте usr_timer (priority=1)
- Требуется однократная задержка → Используйте sys_timeout_add или usr_timeout_add
Резюме
Проще говоря, в обычном коде отдавайте предпочтение sys_timer – он не влияет на сон, энергоэффективен и покрывает большинство сценариев. Если требуется точность до миллисекунды или callback-функция должна обрабатывать срочные задачи, используйте usr_timer, но помните: при priority=1 система не может спать, что увеличивает энергопотребление – не оставляйте его включенным надолго в готовом продукте. Однократные задержки поддерживаются обеими системами – используйте ту, которая удобнее. Рекомендую опробовать оба типа таймеров на плате разработки AC696N, вывести через последовательный порт информацию о потоке callback-функции и поведении при сне – так вы интуитивно поймете разницу, и в дальнейшем выбор не будет вызывать затруднений.