В прошлой статье Делаем несколько дел одновременно-периодически мы посмотрели как можно делать несколько переодически дел одновременно.Но неужели нам каждый раз нужно делать столько "ручной работы" (заводить переменные, возможно отдельный функции)?
В принципе - да. Тем более что завести одну перемененную, одно условие и одно присваивание для каждого случая "нужно периодическое действие" - не такая уж великая работа. Но... программисты народ ленивый, все что можно - стараются автоматизировать, поэтому мы вверху скетча можем разместим такой "магический макрос"
к сожалению обсуждение написание макросов выходит за рамки данной статьи. фактически этот макрос - изложение прошлой статьи на языке понятном компилятору. что-бы "черновую работу" - он делал за нас
Размещаем вверху скетча что-то такое:
После этого мы можем писать в нашем скетче периодически действия в таком виде:
Все. Дальше компилятор сам "развернет" DO_EVERY в объявление переменной, проверку "наступило-ли время" и т.п.
Наш пример из прошлой статьи можно переписать в таком виде:
В принципе - да. Тем более что завести одну перемененную, одно условие и одно присваивание для каждого случая "нужно периодическое действие" - не такая уж великая работа. Но... программисты народ ленивый, все что можно - стараются автоматизировать, поэтому мы вверху скетча можем разместим такой "магический макрос"
к сожалению обсуждение написание макросов выходит за рамки данной статьи. фактически этот макрос - изложение прошлой статьи на языке понятном компилятору. что-бы "черновую работу" - он делал за нас
Размещаем вверху скетча что-то такое:
#define DO_EVERY_V(varname,interval,action) \ static unsigned long __lastDoTime_##varname = 0; \ if( (millis()-__lastDoTime_##varname>interval )){ \ {action;}\ __lastDoTime_##varname=millis();\ }\ #define DO_EVERY_V_1(varname,interval,action) DO_EVERY_V(varname,interval,action) #define DO_EVERY(interval,action) DO_EVERY_V_1(__LINE__,interval,action)
После этого мы можем писать в нашем скетче периодически действия в таком виде:
DO_EVERY(ИНТЕРВАЛ_ДЕЙСТВИЯ_В_МИЛЛИСЕКУНДАХ, ЧТО_НУЖНО_СДЕЛАТЬ);или, если нам нужно выполнить несколько действий в таком
DO_EVERY(ИНТЕРВАЛ_ДЕЙСТВИЯ_В_МИЛЛИСЕКУНДАХ,{ ЧТО_НУЖНО_СДЕЛАТЬ1; ЧТО_НУЖНО_СДЕЛАТЬ2; .... });
Все. Дальше компилятор сам "развернет" DO_EVERY в объявление переменной, проверку "наступило-ли время" и т.п.
Наш пример из прошлой статьи можно переписать в таком виде:
/* Blink without Delay 2013 by alxarduino@gmail.com http://alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html */ #define LED_PIN 13 // номер выхода,подключенного к светодиоду #define BLINK_INTERVAL 5000UL // интервал между включение/выключением светодиода (5 секунд) #define PRINT_INTERVAL 1000UL // периодичность вывода времени в Serial (1 cекунда) #define SERIAL_SPEED 9600 // скорость работы Serial #define DO_EVERY_V(varname,interval,action) \ static unsigned long __lastDoTime_##varname = 0; \ if( (millis()-__lastDoTime_##varname>interval )){ \ {action;}\ __lastDoTime_##varname=millis();\ }\ #define DO_EVERY_V_1(varname,interval,action) DO_EVERY_V(varname,interval,action) #define DO_EVERY(interval,action) DO_EVERY_V_1(__LINE__,interval,action) void setup() { // задаем режим выхода для порта, подключенного к светодиоду pinMode(LED_PIN, OUTPUT); // задаем скорость работы ком-порта Serial.begin(SERIAL_SPEED); } void loop() { // мигаем диодом (периодически переключаем его состоянии) DO_EVERY(BLINK_INTERVAL,digitalWrite(LED_PIN,!digitalRead(LED_PIN))); // периодически выводим время в Serial DO_EVERY(PRINT_INTERVAL,{ Serial.print("Current time:"); Serial.println(millis()); }); }
Все хорошо, но... что-то меня пугает
loop() у нас стал гораздо более лаконичным. Фактически в нем осталось только "что нам нужно делать", без "технической кухни", но все-таки, вот эта "магия в начале скетча" выглядит несколько "ошарашивающе". Захламляет саму суть скетча. Кухня осталась, просто переехала в другое место.
А можно ли как-то обойтись без вставки этого макроса? К сожалению - нет. Должен же компилятор как-то узнать "как обеспечивать переодичность". Но, хорошая новость, мы можем "спрятать" все это "за кулисы". Что-бы оно было, но не мозолило глаза.
Я оформил этот макрос в виде библиотеки (хотя, конечно "библиотека" - это громко сказано :)
Cкачиваем библиотеку отсюда https://bitbucket.org/alxarduino/leshakutils
Теперь , что-бы наша магия заработала, нам, вверху скетча нужно добавить всего одну строку (вместо того "страшного прицепа"):- Скачиваем "свежую версию"
- Распаковываем в папку [путь к вашей ArduinoIDE]\libraries\Распаковываем вместе с именем папки.
- Переименовываем распакованную папку из имени вида "alxarduino-leshakutils-3ef5efa1498a" в "LeshakUtils"
- В результате все файлы должны попасть в папку [путь к вашей ArduinoIDE]\libraries\LeshakUtils\
- Перезапускаем ArduinoIDE (!!! Важно, без этого на найдет новую библиотеку)
#define <TimeHelpers.h>
Теперь наш пример выглядит так:
/* Blink without Delay 2013 by alxarduino@gmail.com http://alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html */ #include <TimeHelpers.h> #define LED_PIN 13 // номер выхода,подключенного к светодиоду #define BLINK_INTERVAL _SEC_(5) // интервал между включение/выключением светодиода (5 секунд) #define PRINT_INTERVAL _SEC_(1) // переодичность вывода времени в Serial (1 cекунда) #define SERIAL_SPEED 9600 // скорость работы Serial void setup() { // задаем режим выхода для порта, подключенного к светодиоду pinMode(LED_PIN, OUTPUT); // задаем скорость работы ком-порта Serial.begin(SERIAL_SPEED); } void loop() { // мигаем диодом (переодически переключаем его состоние) DO_EVERY(BLINK_INTERVAL,digitalWrite(LED_PIN,!digitalRead(LED_PIN))); // переодически выводим время в Serial DO_EVERY(PRINT_INTERVAL,{ Serial.print("Current time:"); Serial.println(millis()); }); }
Все. Теперь у нас все чисто и опрятно.
А что это за _SEC_()?
Если вы внимательно смотрели на прошлый скетч, то заметили что у нас произошло еще одно изменение: интервалы я теперь задал в секундах, а не в миллисекундах.
Это стало возможно, потому что в библиотеку я добавил еще "макросы помогалки" переводящие секунды/минуты/часы/дни в миллисекунды (ну лениво мне каждый раз высчитывать сколько миллисекунд в часе : и считать количество нулей в длинной цифре типа 60000):
#define _SEC_(sec) 1000UL*sec #define _MIN_(mn) _SEC_(60*mn) #define _HR_(h) _MIN_(60*h) #define _DAY_(d) _HR(24*d)
Так что теперь, если мне нужно, скажем, что-бы диод мигал раз 10-ть минут, а время выводилось в serial раз в два часа - я могу прямо так и написать:
Но это - по желанию. Если привыкли все писать в миллисекундах - это возможность сохранилась. Просто "пишем цифру" как и раньше
#define BLINK_INTERVAL _MIN_(10) #define PRINT_INTERVAL _HR_(2)
Но это - по желанию. Если привыкли все писать в миллисекундах - это возможность сохранилась. Просто "пишем цифру" как и раньше
#define PRINT_INTEVAL 7200000UL // 2 часа
А в чем преимущество перед другими библиотеками?
Действительно, для выполнения переодических для ардуины до меня написана далеко не одна библиотека (который используют прерывания таймеров, или как и наша работает через millis())
Скажем библиотека Arduino Playground - SimpleTimer Library ("идеологически" - она очень близка к нашему подходу. внутри у нее -тоже millis() используется).
В первую очередь - мне просто хотелось написать "свою" :) Во вторых - я не буду отговаривать вас использовать SimpleTimer. Она умеет больше чем моя "библиотека" (моя, пока умеет только периодическое действие выполнять). Но... за счет того что моя библиотека сделана с помощью макросов, а не объектов - само ее подключение не приводит к дополнительному расходу памяти. Она более "легковестная". У меня расход памяти будет в точности таким же как если бы мы "все писали руками".
Вообщем под разные ситуации - разные библиотеки будут более удобными. В каких-то случаях SimpleTimer предпочтительней, в каких-то - моя.
UPD1:
Раз в коментариях возник вопрос, попробую объяснить "почему тут не будет переполнения".
Оно будет. Но будет "два раза". После того как millis() перепрыгнет границу и начнет опять отсчет с нуля можно заподозрить что выражение if(millis()-lastDoTime>inteval) больше никогда не сработает. Так как разница (millis()-lastDoTime) - будет отрицательной (пока millis() опять не станет боьлше lastDoTime). И соответственно всегда меньше любого интервала.
Но.... это в математике. А тут нам нужно еще помнить про типы. И то что время мы храним в unsigned long. Следовательно отрицательных чисел - у нас просто не бывает. Попытка сохранить отрицательное числов в беззнаковую переменную, опять вызовет переполнение-переход. Только "в обратную сторону". И интервал (millis()-lastDoTime) - все-таки будет правильным. Несмотря на переполнение.
Что-бы это все "легче представить" (и не путать мозг "большими числами"). Представим что у нас время у нас вообще хранится в byte ( тоже безнаковый). Он "переполнится" уже на числах больше 255.
void setup(){ Serial.begin(9600); byte interval=10; byte lastDoTime=250; // две "эмуляции millis()" после переполнения. Обе - меньше lastDoTime byte millis1=2; // не прошло еще время выполнение byte millis2=20; // прошло byte diff1=millis1-lastDoTime; byte diff2=millis2-lastDoTime; Serial.println(diff1); // нет, тут не -253, а 8-мь, отрицательное число тоже "переполнит", Serial.println(diff2); // а тут у нас 26 Serial.println(diff1>interval); // false - еще не прошло достаточно времени Serial.println(diff2>interval); // true - а вот теперь прошло. } void loop(){}
Если все равно, не понятно как происходит "переполнение в обратную сторону" и откуда там взялись 8 и 26, то можно рассмотреть такой пример.
Предположим нам нужно "прикинуть", что сохранится в переменную в таком случае
byte diff3=5-10;
Давайте эту -10, разобьем на слагаемые.
diff3=5-(5+1+4)
Откроем скобки
diff3=5-5-1-4 = (0 - 1) -4
Вот в это 0-1, в случае byte, дает нам в результате не -1, максимальное число 255.
У нас числовая ось, как-бы "закольцована" (в обе стороны).
diff3=(0-1)-4= 255 -4 = 251;
Поэтому сделав println(diff3), мы увидим выводе 251, а не -5
Предположим нам нужно "прикинуть", что сохранится в переменную в таком случае
byte diff3=5-10;
Давайте эту -10, разобьем на слагаемые.
diff3=5-(5+1+4)
Откроем скобки
diff3=5-5-1-4 = (0 - 1) -4
Вот в это 0-1, в случае byte, дает нам в результате не -1, максимальное число 255.
У нас числовая ось, как-бы "закольцована" (в обе стороны).
diff3=(0-1)-4= 255 -4 = 251;
Поэтому сделав println(diff3), мы увидим выводе 251, а не -5