класс TTimerList.
Просматривая форумы хоббистов и фанатов Arduino я удивился, что 50% вопросов новичков - о том как избавиться от функции delay() в своих программах. По некоторому размышлению, у меня возникла идея создать список асинхронных таймеров, которые можно настроить на разные длительности (от 1 миллисекунды до почти 50 суток). Timer0, 8-разрядный аппаратный таймер настраивается на срабатывание каждую миллисекунду и вызывает метод статического класса TTimerList. Правда, для точности в 1 мс, пришлось пожертвовать точностью millis(), она теперь врёть процентов на 3. Для Atmega8, у которого Timer0 на совпадение не работает, пришлось задействовать Timer1.
Так вот, класс содержит список Items типа
struct TCallStruct // внутренняя структура для хранения КАЖДОГО таймера
{
PVoidFunc CallingFunc; // функция, которую нужно вызвать при срабатывании
long InitCounter; // заданное кол-во миллисекунд
long WorkingCounter; // рабочий счетчик. после начала счёта сюда копируется значение из InitCounter
bool Active; // и каждую миллисекунду уменьшается на 1. При достиженнии 0 вызывается функция
// CallingFunc, и снова записывается значение из InitCounter. Счёт начинается сначала. :)
};
Пробегая по списку, каждую миллисекунду класс уменьшает на 1 рабочий счетчик каждого добавленного таймера (у которого CallingFunc != NULL) и который запущен (Active==true). Если рабочий счетчик достиг нуля, вызывается функция CallingFunc описанная как
using PVoidFunc = void (*)(void); указатель на функцию без параметров ничего не возвращающую.
После этого в рабочий счетчик загружается значение из InitCounter и счет начинается сначала. Прерывания запрещаются на время перебора таймеров в списке.
Использование:
скопировать два файла TTimerList cpp и h в директорию с рабочим проектом, подключить через #include "TTimerList.h". Обязательно объявить глобально
extern TTimerList TimerList;
и можно добавлять, удалять, останавливать и снова запускать таймеры асинхронно. Cписок может содержать 16 таймеров. Если таймеров нужно больше или меньше, надо исправить строчку
#define MAXTIMERSCOUNT 16
в файле uTimerList.h на нужное количество таймеров.
THandle Add(PVoidFunc AFunc, long timeMS); // функция AFunc, которую надо вызвать через timeMS миллисекунд
функция Add добавляет таймер в список и сразу же запускает его, принимает адрес функции, которую нужно вызвать. вызываемая функция описана как void (*)(void). При добавлении таймера, функцию в Add подставляем просто по имени, без скобок. Если подставить со скобками - компилятор сделает вызов функции, если без скобок - передаст адрес функции как параметр, что и нужно по смыслу.
Add(...) отдает так называемый хэндл, идентификатор таймера, по которому его можно удалить, остановить и запустить вновь. По смыслу это обычный номер в списке, но так как добавление идёт не подряд, а на свободное место, то он может быть каким угодно, если планируется останавливать и удалять таймер, лучше этот номер запоминать.
Полностью рабочий блинк без delay() и millis() (да и без loop()) :)
#include "uTimerList.h"
extern TTimerList TimerList;
THandle hnd_blink; // хэндл для светодиода на ноге 13
THandle hnd_error; // хэндл для светодиода на ноге 10
byte normalLedPin = 13; // сюда подключим зеленый светодиод, который будет просто моргать
byte errorLedPin = 10; // сюда подключим красный светодиод, сигнализирующий, например, об ошибке
void setup()
{
pinMode(errorLedPin,OUTPUT);
pinMode(normalLedPin,OUTPUT);
hnd_blink = TimerList.AddSeconds(OKBlink,1); // добавляем таймер, задержка между морганиями 1 секунда, вызывается OKBlink
hnd_error = TimerList.Add(ErrorBlink,250); // добавляем таймер, моргаем 2 раза в секунду, вызывается ErrorBlink
}
void OKBlink(void) // функция вызывается раз в секунду
{
static bool state=true;
digitalWrite(normalLedPin, state);
state = ! state;
}
void ErrorBlink(void) // функция вызывается раз в 250 миллисекунд.
{
static bool state=true;
digitalWrite(errorLedPin, state);
state = ! state;
}
void loop()
{
// здесь даже писать ничего не надо, всё мигает отоматически
}
Даже если нужно чтоб интервалы были неравномерные, например, светодиод горит 50 миллисекунд, а не горит - 5 секунд (быстро мигает раз в 5 секунд, для индикации нормальной работы), то и это можно легко сделать одним таймером:
Объявляем глобально переменную-хэндл:
THandle hLEDBlink;
в setup() создаем таймер
hLEDBlink=TimerList.AddSeconds(tmrLEDBlink,1); // пока ставим интервал раз в секунду, потом изменим
и в функции отклика tmrLEDBlink() пишем:
void tmrLEDBlink()
{
static bool ledstate = false; // статическая переменная хранит предыдущее состояние светодиода
ledstate = !ledstate; // инвертируем состояние светодиода
digitalWrite(ledpin, ledstate); // пишем новое состояние в порт (зажигаем/гасим светодиод)
TimerList.TimerNewInterval(hLEDBlink, ledstate ? 50 : 4950); // устанавливаем новый интервал для таймера hLEDBlink
} // гореть он будет 50мс, не гореть - 4950мс.
Список таймеров удобен, например, когда нужно опрашивать датчики асинхронно, независимо друг от друга и с разными интервалами. Например, чтоб читать датчик DHT11 раз в минуту, надо создать функцию
void readDHT(void);
и добавить в список таймеров
THandle hnd_dht = TimerList.AddMinutes(readDHT, 1); // 1 раз в минуту
всё, раз в минуту будет вызываться readDHT, потом его можно остановить
TimerList.TimerStop(hnd_dht)
или удалить
TimerList.Delete(hnd_dht);
тогда readDHT() вызываться не будет.
Вот все полезные функции списка таймеров:
THandle Add(PVoidFunc AFunc, long timeMS); // функция AFunc, которую надо вызвать через timeMS миллисекунд
THandle AddSeconds(PVoidFunc AFunc, word timeSec); // то же, только интервал задается в секундах.
THandle AddMinutes(PVoidFunc AFunc, word timeMin); // то же, только интервал задается в минутах.
// исключительно для удобства создания длинных интервалов
// функции для списка в целом
inline bool CanAdd() const; // если можно добавить таймер, вернет true
inline bool IsActive() const; // true, если хоть один таймер запущен
void Delete(THandle hnd); // удалить таймер hnd
void Step(void); // раз в миллисекунду перебирает таймеры
void AllStart(); // запустить рабочий цикл перебора таймеров
void AllStop(); // выключить все таймеры, TimerList остановлен
inline byte Count() const {return count; }; // счетчик добавленных таймеров, всего. Можно не использовать, просто проверять возможность
// добавления, вызывая ф-цию CanAdd();
inline byte MaxTimersCount() const { return MAXTIMERSCOUNT; }; // максимальное число таймеров
// функции для одного конкретного таймера, заданного его номером (THandle)
void TimerPause(THandle hnd); //приостановить таймер hnd
void TimerResume(THandle hnd); // продолжить счёт для таймера hnd с остановленного места
void TimerStop(THandle hnd); // остановить отдельный таймер
bool TimerActive(THandle hnd); // запущен ли конкретный таймер
void TimerStart(THandle hnd); // запустить отдельный таймер (после остановки)
void TimerNewInterval(THandle hnd, long newinterval); // назначить таймеру hnd новый интервал
код хорошо (надеюсь) комментирован, изучайте.
Cвязаться со мной по вопросам можно по электропочте dap68@mail.ru или по аське ICQ 394705231.