Исходный код библиотеки
Для наглядности я объединил хедер и сишный файл.
//состояния таймера - неработающий, активный, отработавший
enum StateTimer {IDLE, ACTIVE, DONE};
//структура программного таймера
typedef struct{
unsigned int time; //через какое время запустить
unsigned int period; //период повторения
enum StateTimer state; //текущее состояние
void (*pFunc)(void); //указатель на функцию
}SoftTimer;
//максимальное число таймеров
#define MAX_TIMERS 4
//число созданных таймеров
unsigned char AmountTimers = 0;
//массив указателей на таймеры
SoftTimer* SoftTimers[MAX_TIMERS];
//функция создания программного таймера
void CreateTimer(SoftTimer *CurSoftTimer, unsigned int time, unsigned int period , enum StateTimer state, void (*pFunc)(void)){
SoftTimers[AmountTimers] = CurSoftTimer;
CurSoftTimer->time = time;
CurSoftTimer->period = period;
CurSoftTimer->state = state;
CurSoftTimer->pFunc = pFunc;
AmountTimers++;
}
//функция проверки таймеров
void CheckTimer(void){
for(unsigned char i = 0; i < AmountTimers; i++){
if (SoftTimers[i]->state == ACTIVE){
if (SoftTimers[i]->time == 0){
SoftTimers[i]->pFunc();
if (SoftTimers[i]->period != 0) SoftTimers[i]->time = (SoftTimers[i]->period-1);
else SoftTimers[i]->state = DONE;
}
else(SoftTimers[i]->time)--;
}
}
}
Пояснение к коду
- уметь запускаться сразу или через заданное время
- работать в режиме однократного или переодического запуска
- сигнализировать об окончании счета
На основании этих требований определяем структуру SoftTimer.
//состояния таймера - неработающий, активный, отработавший
enum StateTimer {IDLE, ACTIVE, DONE};
//структура программного таймера
typedef struct{
unsigned int time; //через какое время запустить
unsigned int period; //период повторения
enum StateTimer state; //текущее состояние
void (*pFunc)(void); //указатель на функцию
}SoftTimer;
Массив указателей на таймеры
//максимальное число таймеров
#define MAX_TIMERS 4
//число созданных таймеров
unsigned char AmountTimers = 0;
//массив указателей на таймеры
SoftTimer* SoftTimers[MAX_TIMERS];
Создание таймера
void CreateTimer(SoftTimer *CurSoftTimer, unsigned int time, unsigned int period , enum StateTimer state, void (*pFunc)(void)){
SoftTimers[AmountTimers] = CurSoftTimer;
CurSoftTimer->time = time;
CurSoftTimer->period = period;
CurSoftTimer->state = state;
CurSoftTimer->pFunc = pFunc;
AmountTimers++;
}
Функция опроса таймеров
Функция опроса таймеров запускается в прерывании аппаратного таймера/счетчика
//функция проверки таймеров
void CheckTimer(void){
for(unsigned char i = 0; i < AmountTimers; i++){
if (SoftTimers[i]->state == ACTIVE){
if (SoftTimers[i]->time == 0){
SoftTimers[i]->pFunc();
if (SoftTimers[i]->period != 0) SoftTimers[i]->time = (SoftTimers[i]->period-1);
else SoftTimers[i]->state = DONE;
}
else(SoftTimers[i]->time)--;
}
}
}
Тестовый проект
//программирование микроконтроллеров AVR на Си
//тестовый проект с программными таймерами
//Pashgan ChipEnable.ru
#include <ioavr.h>
#include <intrinsics.h>
#include "Timers.h"
#include "bits_macros.h"
//объявляем переменные типа SoftTimer
SoftTimer timer1;
SoftTimer timer2;
SoftTimer timer3;
SoftTimer timer4;
//расписываем функции программных таймеров
void Clk1(void){
InvBit(PORTB, 0);
}
void Clk2(void){
InvBit(PORTB, 1);
}
void Clk3(void){
InvBit(PORTB, 2);
}
void Clk4(void){
InvBit(PORTB, 3);
}
int main( void )
{
//инициализация порта
PORTB = 0x00;
DDRB = 0xff;
//инициализация таймера Т0 - прерывания каждую ms
TIMSK = (1<<OCIE0);
TCCR0=(1<<WGM01)|(0<<WGM00)|(0<<COM01)|(0<<COM00)|(0<<CS02)|(1<<CS01)|(1<<CS00);
TCNT0 = 0;
OCR0 = 0x7d;
//инициализируем таймеры
CreateTimer(&timer1, 0, 1, ACTIVE, Clk1);
CreateTimer(&timer2, 0, 2, ACTIVE, Clk2);
CreateTimer(&timer3, 0, 3, ACTIVE, Clk3);
CreateTimer(&timer4, 0, 4, ACTIVE, Clk4);
//разрешаем прерывания и тупим в цикле
__enable_interrupt();
while(1);
return 0;
}
__interrupt void Timer0CompVect(void)
{
CheckTimer();
}
Заключение
Алгоритм, конечно, расточительный. Во-первых в прерывании запускается функция. Во-вторых перебор таймеров происходит последовательно - чем больше программных таймеров, тем больше времени это займет. В-третьих функции программных таймеров тоже запускаются в прерывании. Как можно оптимизировать? Не использовать много таймеров. Сделать функцию CheckTimer встраиваемой. Период работы аппаратного таймера выбрать большим. Функции программных таймеров сделать короткими.
Файлы
Проект для IARа. Проект для WINAVR.
Comments
гораздо эффективней при такой организации сделать просто "Системное время" скажем 32байтное, учитывающее каждый такт процессора. И относительно него отслеживать все процессы.
в целом не очень, даже для новичков.
Существует намного более удобный способ и основан он на обычных мат операциях.
Допустим, у нас имеется 16-битный счётчик, не останавливающий ся при переполнении. Известно, что 0x0000 - UINT16MAX = 1.
Как только мы решили запустить счётчик, инициализируем соответствующую переменную его значением в текущий момент времени.
Дальше в прерывании таймера или в периодически вызываемой функции просто пробегаемся по массиву наших переменных и вычитаем из текущего значения счётчика их величины.
Пример релазии для STM32, период таймера 0,5 мс:
#define CNT16_GET() (uint16_t)(TIM- >CNT) /*0.5 ms */
#define CNT16_EVENT(_CN T, _MS) ((uint16_t)(CNT 16_GET() - (uint16_t)(_CNT )) > (uint16_t)(2*_M S))
#define CNT16_UPDATE(_C NT) _CNT = CNT16_GET()
#define CNT16_DIFF(_CNT 0, _CNT1) (uint16_t)(((ui nt16_t)_CNT0 - (uint16_t)_CNT1 ) >> 1) /*1 ms */
#define CNT16_DIFF_CURR (_CNT) CNT16_DIFF(CNT1 6_GET(), _CNT)
CNT16_EVENT - флаг срабатывания таймера,
CNT16_UPDATE - обновление переменной,
остальное не важно.
1) повышенный расход ресурсов (инкремент + прерывания);
2) не всегда высокая точность, т.к. прерывание не очень-то хочется делать слишком частыми. Если в качестве таймера использовать таймер RTOS - разрешающая способность выходит ну явно не лучше 1 мс, а скорее даже 5-10. Приведённые расслуждения о точности справедливы только для случая программировани я методом шагового автомата, при этом время для каждой задачи должно быть явно не больше требуемой точности.
Достоинства приведённого мною метода:
1) потребление ресурсов на таймер минимально, особенности если используется каскад таймеров с различными периодами в пределах одной функции-проверк и срабатывания одних таймеров можно легко выполнять при срабатывании других.
2) создание таймера - определение одной (счётчик) или двух переменных (счётчик и период, если T = 0, считаем, что таймер не активен; как вариант ещё - счётчик и флаг) в любой точке шагового автомата.
Недостатки:
1) для задач предельно жесткого реального времени данный метод не даём существенных преимуществ, т.к. всё равно надо работать в прерывании.
2) можно пропустить момент срабатывания таймера при малых периодах таймера и сложных, не поддающихся переписыванию на шаговый автомат.
Кстати, callback-функци и, вызываемые из прерывания - не очень хорошая идея, т.к. подходят только для очень быстрых функций.
Quote: Тогда я подсмотрел это решение в демо программе платы AVR Butterfly, только там не использовалась структура.
RSS feed for comments to this post