смотрю по лайкам , все меньше и меньше людей доходят до следующих курсов , но у вас самое доходчивое и понятное обьяснение среди всех.. от души благодарю..вы лучший учитель
После этой команды идёт код, который тоже занимает время, поэтому значение last_time будет кратно этому числу, таким образом погрешность может быть ещё больше
Хорошо бы рассказать про таймер, в видео показана циклическое выполнение действия. А хотелось бы послушать про таймер - запуск таймера по событию, событие по окончанию работы таймера.
Знаю, что поздновато, но почему нельзя просто взять и в цикле прописать изменения цифры по истечению одной секунды например? Типа: пока не нажали "стоп", будем считать количество секунд и выводить их на экран, а потом, если итоговое значение меньше ожидаемого - выведем надпись: "вы слишком жирный, кэп! Бегите быстрее" ^_^
@@0neme760 а каким способом вы хотите реализовать данный таймер: внутренним таймеров Atmega328 или же функцией Delay? Не забывайте, что и без вашего участия в микроконтроллере всегда отсчитываются такты, а оно же и время. Просто вы должны научиться использовать это.
@@vladimirlee5928 Это прекрасно! Приведите пример и мы все чему-то научимся. Лично я использовал таймер Атмега, ибо, как говорилось в видео, делэй тупит все выполнение программы, на самолетиках его использовать нельзя. Ну или на каком-нибудь самодельном ИВЛ )
@@0neme760 Погодите. Здесь нам нужно определиться. Вы используете голый процессор или ардуино? Из первого вашего комментария мне было не понятно. Поэтому я и говорю, что в Ардуино уже автоматически таймер настроен который считает время millis() и micros(), а в голом микроконтроллере таймеры нужно настраивать самостоятельно уже.
Это великолепнейший цикл уроков. Теперь он всегда у меня в закладках. Все время чего нибудь да забуду. Спасибо тебе за все. А можно в тебя денюжкой что-ли кинуть, чтобы можно было с уверенностью ждать новых выпусков (на обоих каналах(но хотелось бы побольше обучалок по ардуино ))
мне кажется последний однострочный вариант содержит несколько ошибок. 1. для приведение к секундам выполняется деление на 100 (хотя голосом все правильно сказал - делить надо на 1000). 2. из-за округления код срабатывает гораздо чаще чем нужно. например если millis() вернет 6405 или 6425 после округления получится одно и то же - в результате несколько запусков в одном интервале
Она и без округления лажает. Вредная штука, если бездумно использовать. Кусок вывода в терминал вот такого кода: void setup() { Serial.begin(9600); } void loop() { if ((millis()/1000) % 2 == 0) { Serial.println ("Sovpalo"); }else{ Serial.println ("Ne sovpalo"); } } ==========ВЫВОД В ТЕРМИНАЛ========== Ne sovpalo Ne sovpalo Ne sovpalo Ne sovpalo Ne sovpalo Sovpalo Sovpalo Sovpalo Sovpalo Sovpalo Sovpalo
Хороший видео урок! Возможно, для новичков, стоило указать, что если мы не присваиваем переменной число, то в ней, как правило, по умолчанию хранится 0. Таким образом last_time , в начале, хранит 0, а по достижении millis() 5000 перезаписывается на 5000 и т.д.
Мне пришло просто сто раз пересмотреть, и тупо догадаться и представить что именно так и должно быть. Ошибка всех обучалок, автор некоторые моменты умалчивает, забывает что люди не умеют читать мысли
@@Mr.Drug. может он верил что Кто то смотрет все видео А в каком то с прошлых Он говорыл Что Переменая изначально 0 если ми ничего не указали.Хотя мог повторить мейби
if (millis() - last_time > 5000) { // что-то сделать last_time = millis(); } На 51-ом дне работы программы, значение, возвращающее функцией millis() переполнится, last_time будет иметь значение "за 5 секунд до переполнения" и блок if выполнится один раз только через 50 дней, перезаписав last_time на значение чуть больше, и так по кругу. Т.к. в нашем случае значение разности не будет превышать 5000 мсек, то проблему можно решить взяв разность по модулю (функция labs() для типа long): if (labs(millis() - last_time) > 5000) {...} Но в таком случае будет ложное срабатывание при каждом переполнении. Лучше сделать так: unsigned long nextTime = 0; void loop() { if (nextTime
2:50 не нужно ли переменной last_time присвоить изначально значение? так как при первом запуске if last_time будет использовать неизвестное значение из памяти предположительно 0 но все же. или я что-то не так понял?
Если при компиляции скетча с последним примером вылетает ошибка: "invalid operands of types 'double' and 'int' to binary 'operator%" можно записать так: "if (int (round(millis() / 300)) % 2 == 0)" PS Огромное Спасибо за уроки!!!
спс за видео Алекс👍🏻 как раз таки не понимал эти задержки. Доходчиво объяснил, успехов!!! Будет возможность сделай видос про многозадачность. Спасибо!)
Спасибо, ты помог мне с пересчетом данных в лупе датчика INA226 через 1/3 сек. А то delay(300) тормозила всю общую прогу, даже часы и те останавливались на 1/3 сек. Теперь пересчет и отображение характеристик тока отображается, как и нужно для нормального восприятия, а не бегущие цифры после запятой. (моя паяльная станция) .
Сдохнет твой кот. Имхо в кормилку из холодильника кто то тоже должен класть еду. Можно конечно пойти дклее и автоматизировать процесс достования из холодильника. Затем создаётся робот который ходит в магаз и покупает жратву коту и кормит кота. есть шанс стать миллионером ))))))
Спасибо за дельный совет от водителя такси:) А делать выводы по одному предложению у Вас очень хорошо получается... Кто то сказал что она будет полностью автоматической? если бы я мог собрать такую хрень - то уже давно был бы миллионером, а пока просто стоит задача чтоб коты могли жрать днем пока дома никого нет.
У последнего предложенного варианта с целочисленным делением минус в том, что если время срабатывания по каким-либо причинам пропустится (остальной код долго выполнялся, или в нем был delay()), то тело условия не сработает, что может вызвать ошибки. Первый вариант сработает и при опоздании.
Каков бы не был период срабатывания N (23 дня или 1 секунда), через 50 дней, когда millis() после обнуления, она уже никогда не будет больше last_time+N обнулять же last_time, когда millis() обнулится, тоже не очень, при большом N, получим 23, 46, 50, 73... дней. Не идеально, но на полтора века хватит unsigned long seconds=0; unsigned long lastTimeMillis=0; unsigned long lastTimeSeconds=0; void setup() { } void loop() { if(millis()-lastTimeMillis>=1000 || lastTimeMillis>millis()) { seconds++; lastTimeMillis=millis(); } if(seconds-lastTimeSeconds>=23){ lastTimeSeconds=seconds; // выполняем что хотели раз в 23 дня } }
Подскажите как через millis написать это: void setup() { pinMode(PIN 13, OUTPUT); } void loop() { digitalWrite(PIN 13, HIGH); delay(3000); digitalWrite(PIN 13, LOW); delay(1000); Я что то не пойму нифига(((
Нет. Проверка "millis()- last_time>0" лишняя - уже ж есть проверка "millis() - last_time > 5000" которая полностью её замещает. Другими словами если какое-то число больше 5000, то оно и подавно будет больше 0.
@@Valigozi хочу сделать измеритель потребления на счетчик на Ардуино. Там лампочка моргает, и каждые 3200 раз проходит 1квт/ч. И, измеряя, время между моргания и светодиода можно определять текущую подключенную нагрузку. фоторезистор будет и через транзистор логика. Думаю хватит скорости. Там светодиод 0.1с это 11квт, такой нагрузки не будет наверно, максимум 5 кат т.е. 0,5 с. На прерываниями делать, если то как измерить время между прерываниями? Как идея?
@@halyapin Нет. И тип переменной "last_time" и тип возвращаемого функцией "mills()" - "unsigned long" (беззнаковое целое), поэтому результат вычитания тоже будет типа "unsigned long", а число этого типа не может быть отрицательным *никогда*.
3:05 Мне эта конструкция показалась немного замудреной. Не проще ли было бы использовать деление с остатком? if((millis() % 5000) == 0){ // код для выполнени } так ненужно было бы каждый раз переменную переписывать, да и переменная вообще не нужна. Или % для Ардуино очень напряжен? (в плане ресурсов: такты, байты).
В таком случае ,если выполняется какая то полезная работа параллельно , то будут теряться миллисекунды и без остатка поделить не получится, пока делал проект , полностью это осознал.
в место delaymicroseconds() лучше использовать delay_us() на малых значениях параметров, экспериментировал на своей ардуино мега 2560 + осциллограф : delaymicroseconds(1) дает задержку около 20 микросекунд насколько я помню, а вот delay_us(1) дает задержку в 5 микросекунд ровно, даже фронты более менее в норме.Главный цикл loop не использовал, просто дергал ногой в while цикле, так быстрее всего.
спасибо alex. millis() работает. но как сделать чтоб задержка срабатывала в начале цикла loop() при включении микроконтроллера. по примеру сначала выполнение условия а потом идёт задержка то есть 1 пауза 1 пауза и т.д как сделать пауза 1 пауза 1 пауза 1 и т.д.. alex буду особенно признателен за ответ
Может кому то пригодится, скетч посылает значения в монитор порта от 160 до 0 по убыванию,а потом от 0 до 160 по возрастанию,можно применить для плавного движения серво привода,без delay() и for . byte q[2] = {255,0}; void setup() { Serial.begin(9600); } void loop() { int down = q[0]--; int up = q[1]++; down = map(down,255,0,320,0); up = map(up,0,255,0,320); if(down>160) Serial.println(up); if(down
со скетчем не поможете ? что бы серво Tower Pro 9g SG90 в 21.00 поворачивался на 180 ,а в 6.00 возвращался обратно. в наличии ( 1.Сервопривод Tower Pro 9g SG90 2.encoder 3.Дисплей 1602 с модулем I2c 4.Arduino nano 3.0 с Atmega 328p 5.Плата AT24C256 память 6.часы DS 3231).
Алекс Гайвер, скажите, почему в этой от конструкции (в низу) "kek" срабатывает через каждые 5 секунд, а не 6, при знаке >, а не >=. Если, к примеру, мы от (time=5 сек отнимем last_time=0 сек) мы запишем в last_time 5 сек, мы получим строго равно 5 сек, так само как и при (time=10 сек - last_time=5 сек.) Мы получим строго равно 5 сек. и высветиться "kek", ПОЧЕМУ? Есть должно пройти не 5 сек, а 6 сек.(как в самом начале на одну секунду больше), что бы было БОЛЬШЕ знака СТРОГО РАВНО? То есть (time=11 сек - last_time=5 сек.) мы получим ИМЕННО > 5000, а не >=. Как это объяснить? if (millis() - last_time >= 5000){ last_time = millis(); Serial.println("kek");
Можно! Где-то видел, не могу найти! Можно в библиотеке гайвера про энергосбережение и сон покапаться, там есть корректировка миллис. Но тебе то это зачем ?
Проштудировал, но там не нашел универсального решения. Там ведь все то, что было здесь в комментариях. Оба варианта не предусматривают ситуацию, когда отсчет начался перед самым достижением предельного значения (допустим за 5 секунд до обнуления millis), а отсчитать должен ровно 60 секунд. Один вариант подгонит переменную к 0 и сработает раньше времени, другой прибавит слишком много и не будет достигнут ни когда. Вариант только писать еще более изощренную логику с прибавлением времени и проверкой на переполнение, если проверка не пройдена - менять... Что-то вроде: if((nextTime 4294967295){ nextTime = nextTime-4294967295; } } Но эта конструкция может дать сбои, если nextTime будет равен 4294967295 или на пару милисекунд меньше. Тогда контроллер пропустит срабатывание...
@@yungfloppa при переполнении переменная переходит через ноль. Вариантов два. Если не критично - забить, будет просто двойное срабатывание. Если критично - проверять после присвоения значения и делать соответствующие поправки.
С 23 сутками не будет работать правильно... Сработает условие на 23й день, потом на 46й, а потом через 4 дня (на 50е сутки) сбросится миллис и пройдёт ещё 23 суток до условия, и того 27 дней до выполнения условия.
Нет, все правильно будет работать. Там другая математика, отрицательных чисел нет, смотрите урок "математические операции". Числа идут по кругу, почти как на циферблате часов. Если считать в днях, то после 50 идёт 0, и наоборот перед 0 будет 50. То есть 0-1=50! 0-2=49,...0-46=5! А 18-46=23>=23 условие выполнилось на 18 день после перехода через 0. То есть через 23 дня! Только считать надо в миллисекундах
Доброго времени. Правильно ли я тогда понимаю, что для использование в слайдере для таймлапс, (это когда фотокамера установлена на слайдере и должны происходить два действия. Камера смещается на заданное расстояние, к примеру на один или два шага двигателя, останавливается, срабатывает затвор камеры. Камера вновь перемещается и так до края слайдера.) необходимо использовать millis()?
@@ArduNotes я начал писать на атоме вот это действительно стоящая замена, конечно тяжело привыкнуть к постоянному цыклу, и иногда по привычке засовываю ещё один хотя можно реальзовать с помощью маяка. Спасибо вашим урокам уже за 2 недели, в принципе понимаю все, у вас подход к урокам как у моего ментора, что очень круто, и очень неплохо приводите примеры из жизни что тоже очень помогает новичкам понимать что и зачем, я когда начинал в 2015 то для меня это был темный лес, от переменных и функций до побитовых операторов
@@ArduNotes и ещё вопрос я то думаю что я уже на него ответил но все же, я так понимаю что ATOM с platformIO, при создании проекта, инициализирует костяк библиотеки ? тоесть если реальзовать внутри main.cpp класс, то потом можно перенести в библиотеки и использовать уже через include
Расскажи как выводить информацию на сдвоенный (x3, x4 итд) семисегментный индикатор и чем отличается способ с одиночными индикаторами и сдвиговыми регистрами. И как на SD-флешке создавать файл с логами. Не обязательно в ближайшем видео, а вообще когда-нибудь
А если счётчик переполнится? И наш Last_time будет равен 4.294.966.000 допустим то код будет выглядеть так, millis опять стал 0 и будет (0 - last_time(4.294.966.000) > 1000) , так получается что условие больше не выполнится или будет выполнять я раз в 50 суток? И надо будет перезапускать МК? Или как это работает?
Как сделать так, чтобы ограничить действие например: Нажать кнопку можно только раз в час и действие будет производиться только раз в час! Например: Наливаем воду в стакан, нажал кнопку стакан наполнился, но при повторном нажатии оно реагировать не будет пока не пройдет час.
Alex, подскажи а как с помощью "millis()" реализовать моргание надписи на дисплее с настраиваемыми временами. Например: 1 сек. индикации, 1 сек. паузы? Ну типа аналог стандартного блинка, только без использования "delay". Спасибо.
Так как время срабатывает на миллисекунду позже, я сделал if(millis() - last_time > 5000 - 1), хотя наверное как сказано ниже будет лучше if(millis() - last_time > 4999) чтобы была меньше нагрузка на процессор микроконтроллера. А функция micros() вызывает какие-то перевернутые вопросы, уже больше получаса не могу исправить, код с micros настолько же прост как с millis
памяти одинаково будет жрать, да и процессорного времени одинаково будет отнимать обе эти операции. В итоге компилятор скомпилирует в JE если для случая ==0, или JNE для случая !=0, для процессора и первое и второе одна команда, то есть "скушает" один квант времени на обработку
Alexei Belousov А я думал, что процессор для случая if(exp ==0) сначала обработает равенство нулю, а потом проверит условной конструкцией. А для случая if(!exp) проведет простую инверсию, а потом проверит условной конструкцией. Но спасибо за ответ.
Mexahoid BW11 кстати эта же операция будет происходить с типом булево, ведь процессор реально делает сравнение к нулю, при операции a == false, она равнозначна a==0. А вот если сравниваются произвольные числа, не 0 и 1, то будет уже 2 команды процессора первая CMP - сама операция сравнения, и потом уже операция условного перехода jl, jb, jnl, jnb и другие.. то есть когда сравниваем произвольные числа понадобятся минимум (тут все зависит от типа переменной числа) 2 команды процессора или 1 команда в случае сравнения булевых типов или 0 или 1. В любом случае операции отрицания и сравнения равнозначны для процессора с точки зрения используемых ресурсов
Алекс а могли бы Вы снять видио с разбором где в одном скейче выполняются две операции например открываем серво привод по температуре и закрываем соответсвено по ней, и также по времени к примеру два раза в день включалось рэле минут на 5-10 . и соотвествено я так понимаю тут уже нужны часы реального времени.
Подскажите пожалуйста. Гавер в видосе объявил переменную last_time типа unsigned long, но не указал значение. Дальше ты вроде бы как не присваивал её значения. Как понять чему равна переменная last_time. 2:02
Typical ,он не указал значение ,он просто объявил last_time как переменную(можно сказать равна нулю),Millis- время после включения ардуино.Теперь условие : когда время после включения - 0 станет > 5000, то выполняется условие и мы перезаписываем last_time ( last_time= Millis=5000)и когда получается что last_time=5000, то не выполняется условие и мы ждём когда пройдёт ещё 5секунд (тогда будет Millis будет равен 10000 и будет снова выполнятся условие)и так бесконечно и при этом остальной код тоже выполняется
не могу понять как работает переменная в круглых скобках unsigned long в операторе if (millis() - last_time > (unsigned long)23 * 24 *60 *69 100 ) для чего она там нужна ??
Здравствуйте Алекс, я начинающий поэтому не понял немного, пересмотрел видео раз двадцать наверное и все равно догнать никак не могу !!! вы создали переменную ласт тайм но ведь вы не указали ей никакого значения, а как тогда milis отнимет значение ласт тайм если его нет ? или как там вообще это работает объясните по подробнее каждый шаг и цикл пожалуйста
как сделать таймер на миллис что бы несколько светодиодов мигали каждый по своему времени ? покажите нужно для программы у меня там будет порядка 15 -20 задержек на миллс
@@ArduNotes Интересно, как это так что ты почти сразу отвечаешь на мои комменты??? В третий раз уже. Это ты получается заходишь посмотреть коменты и из последних мой комент попадается? Или как?) Кстати я сначало начал учиться по курсу Радиолюбителя TV, потому что у него больше видео и проще. Но вскоре я понял что ничего не понял и изучаю теперь твои уроки вроде все понимаю хорошо. Надо было сразу так)
Сидел игрался с шим приходит сообщение о новом видио. Попробовал использовать вместо delay(10); Вот что получилось void loop() { int x = 1; for (int i = 0; i > -1; i = i + x) //В цикле увеличиваем значение i на величину х { if (millis()-lfst_tim >32000); // Это вместо задержки delay(10) { lfst_tim=millis(); analogWrite(LedPin3, i); // Функция analogWrite(,) зажигает диод со значением i if (i == 255) x = -1; // переключение управления на максимуме //delay(10); //Эту команду не выполняем имхо заремлено } } } Таймер вставляем внутрь цикла иначе у вас будер задержка перед работой цикла
Вопрос к зубрам Ардуиномании : не будет ли конфликта драйверов ch340 если к компьютеру кроме Ардуинки подключается 3D принтер(тоже реализован на Ардуино) ?
Чего? Delay это задержка, а Millis() - last_Tim... переодическое выполнение кода. Это разные действия, т. е delay этими millis не заменешь. И в таком случае эти милис тоже не лучший выбор,потому что перед ней может идти функция которая выполняется 20 минут и тогда только после этих 20 минут выполнится условие,есть шанс что период будет больше 5 секунд . Правильно, это прерывания от таймера получать и результат 100%
не понял одно сегодня Использовал этот участок кода.. поставил для датчиков температуры и атмосферного давления. через каждые 10 минут .. Что будет при переполнении счетчика millis
на millis() -ах луче код работает т.к. когда я взял на % ардуина как бешеная просчитывает, на примере вкл. выкл. светодиода она истерично промигивалась пропускала / глючила ( ну код постоянно ломился через условие == 0 , а когда millis() и знак > но верняк всё чётко стабильно , спс . И го побольше простых гайдов без библиотек , по возможности , что бы можно было понять суть кода? я пытался изложить мысль как смог. Как по датчику ардуину будить, и по этому же дачику ( света) ложить спать ?
смотрю по лайкам , все меньше и меньше людей доходят до следующих курсов , но у вас самое доходчивое и понятное обьяснение среди всех.. от души благодарю..вы лучший учитель
Качество уроков на высоте , приятно слушать и не засыпаешь , вот только без практики это всё забудется
Хорошее видео.
Замечу лишь, что кормить животных раз в 23 дня чересчур расточительно)
@Тарас Атавин Jl9 ну нах крокодила кормить
@Тарас Атавин Гениально)
2:17
Прошу заметить, что
выражение: if(millis() - last_time > 5000){}
Срабатывает на 1 миллисекунду позже указанного, поэтому правильнее ставить ">="
Мля , на миллисекунду полив позже сработал, цветы уже завяли)))
дельное замечание, спасибо
После этой команды идёт код, который тоже занимает время, поэтому значение last_time будет кратно этому числу, таким образом погрешность может быть ещё больше
Сколько миллисекунд ты писал этот коммент?)
@@yeru6427 хороший вопрос)
очень хорошо объясняете! пишите примеры,хорошо подготавливаетесь,прям сразу хочется все это слушать! Все записываю в блокнот,от души спасибо! :)
Хорошо бы рассказать про таймер, в видео показана циклическое выполнение действия. А хотелось бы послушать про таймер - запуск таймера по событию, событие по окончанию работы таймера.
Знаю, что поздновато, но почему нельзя просто взять и в цикле прописать изменения цифры по истечению одной секунды например? Типа: пока не нажали "стоп", будем считать количество секунд и выводить их на экран, а потом, если итоговое значение меньше ожидаемого - выведем надпись: "вы слишком жирный, кэп! Бегите быстрее" ^_^
@@0neme760 а каким способом вы хотите реализовать данный таймер: внутренним таймеров Atmega328 или же функцией Delay?
Не забывайте, что и без вашего участия в микроконтроллере всегда отсчитываются такты, а оно же и время. Просто вы должны научиться использовать это.
@@vladimirlee5928 Это прекрасно! Приведите пример и мы все чему-то научимся. Лично я использовал таймер Атмега, ибо, как говорилось в видео, делэй тупит все выполнение программы, на самолетиках его использовать нельзя. Ну или на каком-нибудь самодельном ИВЛ )
@@0neme760 Погодите. Здесь нам нужно определиться. Вы используете голый процессор или ардуино? Из первого вашего комментария мне было не понятно. Поэтому я и говорю, что в Ардуино уже автоматически таймер настроен который считает время millis() и micros(), а в голом микроконтроллере таймеры нужно настраивать самостоятельно уже.
@@vladimirlee5928 с голыми контролерами я почти не работал. В данном случае ардуино
Спасибо большое . Я не программист но Ваш урок мне очень помог создать скетч для управления пиролизным котлом.
КРУТО!!!!!!!!!!!!! Спасибо!!! И как всегда что то новенькое, там где казалось и так все понятно. У тебя талант всякие вкусности выдавать!!!
а в чём проблема молодой человек?
Спасибо большое за информацию. Практически все легко и приятно глазу. Я раньше только знал как папку создать а сейчас чуть больше чем ранее 🤗
Это великолепнейший цикл уроков. Теперь он всегда у меня в закладках. Все время чего нибудь да забуду. Спасибо тебе за все. А можно в тебя денюжкой что-ли кинуть, чтобы можно было с уверенностью ждать новых выпусков (на обоих каналах(но хотелось бы побольше обучалок по ардуино ))
мне кажется последний однострочный вариант содержит несколько ошибок.
1. для приведение к секундам выполняется деление на 100 (хотя голосом все правильно сказал - делить надо на 1000).
2. из-за округления код срабатывает гораздо чаще чем нужно. например если millis() вернет 6405 или 6425 после округления получится одно и то же - в результате несколько запусков в одном интервале
все верно. Округляем при выводе на экран ) Serial.println(round(last_time / 1000));
@Тарас Атавин
А по-русски: в реальном условии - не округляем.
Она и без округления лажает.
Вредная штука, если бездумно использовать.
Кусок вывода в терминал вот такого кода:
void setup()
{
Serial.begin(9600);
}
void loop()
{
if ((millis()/1000) % 2 == 0) {
Serial.println ("Sovpalo");
}else{
Serial.println ("Ne sovpalo");
}
}
==========ВЫВОД В ТЕРМИНАЛ==========
Ne sovpalo
Ne sovpalo
Ne sovpalo
Ne sovpalo
Ne sovpalo
Sovpalo
Sovpalo
Sovpalo
Sovpalo
Sovpalo
Sovpalo
@Тарас Атавин
Если надо объяснять - то не надо объяснять.
@Тарас Атавин
Вам ближе по-украински.
Спс большое за уроки. Только побольше бы:) Самые понятные уроки из тех что я видел.
Круто! Самый лучший канал по ардуино
Хороший видео урок! Возможно, для новичков, стоило указать, что если мы не присваиваем переменной число, то в ней, как правило, по умолчанию хранится 0. Таким образом last_time , в начале, хранит 0, а по достижении millis() 5000 перезаписывается на 5000 и т.д.
да, как раз на этом комментарии вопрос и отпал :)
Мне пришло просто сто раз пересмотреть, и тупо догадаться и представить что именно так и должно быть.
Ошибка всех обучалок, автор некоторые моменты умалчивает, забывает что люди не умеют читать мысли
@@Mr.Drug. может он верил что Кто то смотрет все видео А в каком то с прошлых Он говорыл Что Переменая изначально 0 если ми ничего не указали.Хотя мог повторить мейби
if (millis() - last_time > 5000) {
// что-то сделать
last_time = millis();
}
На 51-ом дне работы программы, значение, возвращающее функцией millis() переполнится, last_time будет иметь значение "за 5 секунд до переполнения" и блок if выполнится один раз только через 50 дней, перезаписав last_time на значение чуть больше, и так по кругу.
Т.к. в нашем случае значение разности не будет превышать 5000 мсек, то проблему можно решить взяв разность по модулю (функция labs() для типа long):
if (labs(millis() - last_time) > 5000) {...}
Но в таком случае будет ложное срабатывание при каждом переполнении. Лучше сделать так:
unsigned long nextTime = 0;
void loop() {
if (nextTime
Спасибо, поправил
+WakeUp4L1fe спасибо! Добавлю в методичку
В таком случае, когда nextTime переполнится, условие if (nextTime
Какая разность по модулю? У вас же тип unsigned ! Значение разности всегда будет положительно. Все что показано в видео будет работать хорошо.
Да, я был не прав, работать и так будет.
2:50 Вот оно!!!! то, не зная чего я не смог перейти на ардуино с промышленных контроллеров! Теперь можно продолжить экезекуцию. Спасибо!
Очень полезное видео! спасибо за труды!!!
Спасибо, жду видео с уходом дуины в спящий режим)
Дождался
Спасибо вам за столь полезные видео!!!
Ура, новый выпуск!
Алекс, у тебя получаются очень лаконичные уроки. Все четко и по делу: Круто!
PS: по-моему round() лишний. Ты же целое число на целое делишь.
Отлично ведешь уроки.
Продолжай!!!!
2:50 не нужно ли переменной last_time присвоить изначально значение? так как при первом запуске if last_time будет использовать неизвестное значение из памяти предположительно 0 но все же. или я что-то не так понял?
Если при компиляции скетча с последним примером вылетает ошибка: "invalid operands of types 'double' and 'int' to binary 'operator%" можно записать так: "if (int (round(millis() / 300)) % 2 == 0)"
PS Огромное Спасибо за уроки!!!
Про кратность подумал когда поставил видосик на паузу и разбирал, а потом ты сам продемонстрировал этот вариант)
Спасибо большое!!! Очень познавательное видео!
спс за видео Алекс👍🏻 как раз таки не понимал эти задержки. Доходчиво объяснил, успехов!!! Будет возможность сделай видос про многозадачность. Спасибо!)
побыстрее бы следующие видео, очень понравилось
Спасибо, ты помог мне с пересчетом данных в лупе датчика INA226 через 1/3 сек. А то delay(300) тормозила всю общую прогу, даже часы и те останавливались на 1/3 сек. Теперь пересчет и отображение характеристик тока отображается, как и нужно для нормального восприятия, а не бегущие цифры после запятой. (моя паяльная станция) .
По мере увеличения числа в миллис, контроллер не начинает тормозить?
нет. это отдельный блок в микроконтроллере.
Спасибо тебе огромное! Очень полезные видео) Начав смотреть твой канал тоже решил себе заказать ардуино)) И организовать на нем кормилку для котов)
Сдохнет твой кот. Имхо в кормилку из холодильника кто то тоже должен класть еду. Можно конечно пойти дклее и автоматизировать процесс достования из холодильника. Затем создаётся робот который ходит в магаз и покупает жратву коту и кормит кота. есть шанс стать миллионером ))))))
Спасибо за дельный совет от водителя такси:) А делать выводы по одному предложению у Вас очень хорошо получается... Кто то сказал что она будет полностью автоматической? если бы я мог собрать такую хрень - то уже давно был бы миллионером, а пока просто стоит задача чтоб коты могли жрать днем пока дома никого нет.
Привіт друже, ремарка long та int займать однакових 4 байта.А от long long 8 байт)Дякую тобі за твою титанічну роботу. Удачі:)
так просто и так гениально!! ты меня выручил)))
У последнего предложенного варианта с целочисленным делением минус в том, что если время срабатывания по каким-либо причинам пропустится (остальной код долго выполнялся, или в нем был delay()), то тело условия не сработает, что может вызвать ошибки. Первый вариант сработает и при опоздании.
Эх, жаль нельзя сразу два лайка поставить :) То что нужно и как раз вовремя для моей программы :)
Каков бы не был период срабатывания N (23 дня или 1 секунда), через 50 дней, когда millis() после обнуления, она уже никогда не будет больше last_time+N
обнулять же last_time, когда millis() обнулится, тоже не очень, при большом N, получим 23, 46, 50, 73... дней.
Не идеально, но на полтора века хватит
unsigned long seconds=0;
unsigned long lastTimeMillis=0;
unsigned long lastTimeSeconds=0;
void setup() {
}
void loop() {
if(millis()-lastTimeMillis>=1000 || lastTimeMillis>millis()) {
seconds++;
lastTimeMillis=millis();
}
if(seconds-lastTimeSeconds>=23){
lastTimeSeconds=seconds;
// выполняем что хотели раз в 23 дня
}
}
Подскажите как через millis написать это:
void setup() {
pinMode(PIN 13, OUTPUT);
}
void loop() {
digitalWrite(PIN 13, HIGH);
delay(3000);
digitalWrite(PIN 13, LOW);
delay(1000);
Я что то не пойму нифига(((
Спасибо за подсказку как сделать многозадачность.
Классный ролик получился!
Для понимания почему нужно использовать именно unsigned long , полезно было бы объяснить, что происходит при переполнении переменной.
Ты так внятно объясняешь за 5 минут, не же ли нудные лекции училки 45мин.))))
даешь урок о прерываниях!
Спасибо за видео, оч помогло для моего проекта)
А этот код по идее корректно работать будет около 50 суток ведь так? По идее надо еще проверку поставит что
millis()- last_time>0
Нет. Проверка "millis()- last_time>0" лишняя - уже ж есть проверка "millis() - last_time > 5000" которая полностью её замещает. Другими словами если какое-то число больше 5000, то оно и подавно будет больше 0.
@@Valigozi привет
@@ИванИванов-х1х9й Привет! :)
@@Valigozi хочу сделать измеритель потребления на счетчик на Ардуино. Там лампочка моргает, и каждые 3200 раз проходит 1квт/ч. И, измеряя, время между моргания и светодиода можно определять текущую подключенную нагрузку. фоторезистор будет и через транзистор логика. Думаю хватит скорости. Там светодиод 0.1с это 11квт, такой нагрузки не будет наверно, максимум 5 кат т.е. 0,5 с. На прерываниями делать, если то как измерить время между прерываниями? Как идея?
@@halyapin Нет. И тип переменной "last_time" и тип возвращаемого функцией "mills()" - "unsigned long" (беззнаковое целое), поэтому результат вычитания тоже будет типа "unsigned long", а число этого типа не может быть отрицательным *никогда*.
"переполнение millis" - это значит он в итоге остановится или в 0 уйдет? его перезапустить можно без рестарта?
Уйдет в 0, но конструкция с таймером продолжит отрабатывать интервалы. Максимально подробно написано тут alexgyver.ru/lessons/time/
@@ArduNotes благодарствую
3:05 Мне эта конструкция показалась немного замудреной. Не проще ли было бы использовать деление с остатком?
if((millis() % 5000) == 0){
// код для выполнени
}
так ненужно было бы каждый раз переменную переписывать, да и переменная вообще не нужна. Или % для Ардуино очень напряжен? (в плане ресурсов: такты, байты).
А разве millis это не число с плавающей точкой?
В таком случае ,если выполняется какая то полезная работа параллельно , то будут теряться миллисекунды и без остатка поделить не получится, пока делал проект , полностью это осознал.
Спасибо, мне очень нужно описание работы millis
как поступить, если нам нужно, чтобы часть программы срабатывала раз в 5 сек? Ведь часть, которая ниже if(millis)...может выполняться больше 5 сек
Ещё не посмотрел, а лайк поставил)
Спасибо за видос. Лайк!
в место delaymicroseconds() лучше использовать delay_us() на малых значениях параметров, экспериментировал на своей ардуино мега 2560 + осциллограф : delaymicroseconds(1) дает задержку около 20 микросекунд насколько я помню, а вот delay_us(1) дает задержку в 5 микросекунд ровно, даже фронты более менее в норме.Главный цикл loop не использовал, просто дергал ногой в while цикле, так быстрее всего.
+Alexei Belousov спасибо, добавлю в пособие
@@ArduNotes в PDF версии 1.1 (от 15.01.2019) нет ничего про delay_us - почему? не работает? забыли?
Вот теперь точно понятно))) Хоть один человек смог нормально объяснить.
спасибо alex. millis() работает. но как сделать чтоб задержка срабатывала в начале цикла loop() при включении микроконтроллера. по примеру сначала выполнение условия а потом идёт задержка то есть 1 пауза 1 пауза и т.д
как сделать пауза 1 пауза 1 пауза 1 и т.д.. alex буду особенно признателен за ответ
Последний способ просто класс!! Блин лет 5 на си пишу - никогда подобную конструкцию не использовал. Мне стыдно...очень((
Может кому то пригодится, скетч посылает значения в монитор порта от 160 до 0 по убыванию,а потом от 0 до 160 по возрастанию,можно применить для плавного движения серво привода,без delay() и for .
byte q[2] = {255,0};
void setup()
{ Serial.begin(9600); }
void loop()
{
int down = q[0]--;
int up = q[1]++;
down = map(down,255,0,320,0);
up = map(up,0,255,0,320);
if(down>160)
Serial.println(up);
if(down
со скетчем не поможете ? что бы серво Tower Pro 9g SG90 в 21.00 поворачивался на 180 ,а в 6.00 возвращался обратно.
в наличии (
1.Сервопривод Tower Pro 9g SG90
2.encoder
3.Дисплей 1602 с модулем I2c
4.Arduino nano 3.0 с Atmega 328p
5.Плата AT24C256 память
6.часы DS 3231).
Алекс Гайвер, скажите, почему в этой от конструкции (в низу) "kek" срабатывает через каждые 5 секунд, а не 6, при знаке >, а не >=. Если, к примеру, мы от (time=5 сек отнимем last_time=0 сек) мы запишем в last_time 5 сек, мы получим строго равно 5 сек, так само как и при (time=10 сек - last_time=5 сек.) Мы получим строго равно 5 сек. и высветиться "kek", ПОЧЕМУ? Есть должно пройти не 5 сек, а 6 сек.(как в самом начале на одну секунду больше), что бы было БОЛЬШЕ знака СТРОГО РАВНО? То есть (time=11 сек - last_time=5 сек.) мы получим ИМЕННО > 5000, а не >=. Как это объяснить?
if (millis() - last_time >= 5000){
last_time = millis();
Serial.println("kek");
А можно к фукции millis присвоить своё значение? Иногда нужно ускорить или пропустить счет millis.
Можно! Где-то видел, не могу найти! Можно в библиотеке гайвера про энергосбережение и сон покапаться, там есть корректировка миллис. Но тебе то это зачем ?
Очень бы хотелось увидеть урок 4.1 "борьба с переполнением переменной / обновлением millis"...
+Вячеслав Бард в пособии в пдф все написано
Пошел изучать. Спасибо!
Проштудировал, но там не нашел универсального решения.
Там ведь все то, что было здесь в комментариях.
Оба варианта не предусматривают ситуацию, когда отсчет начался перед самым достижением предельного значения (допустим за 5 секунд до обнуления millis), а отсчитать должен ровно 60 секунд. Один вариант подгонит переменную к 0 и сработает раньше времени, другой прибавит слишком много и не будет достигнут ни когда.
Вариант только писать еще более изощренную логику с прибавлением времени и проверкой на переполнение, если проверка не пройдена - менять...
Что-то вроде:
if((nextTime 4294967295){
nextTime = nextTime-4294967295;
}
}
Но эта конструкция может дать сбои, если nextTime будет равен 4294967295 или на пару милисекунд меньше. Тогда контроллер пропустит срабатывание...
@@Werbard Нашли способ решения проблемы? :)
@@yungfloppa при переполнении переменная переходит через ноль. Вариантов два. Если не критично - забить, будет просто двойное срабатывание. Если критично - проверять после присвоения значения и делать соответствующие поправки.
спасибо большое очень помогло
Сделай видео о том как подключить камеру к ардуино
С 23 сутками не будет работать правильно... Сработает условие на 23й день, потом на 46й, а потом через 4 дня (на 50е сутки) сбросится миллис и пройдёт ещё 23 суток до условия, и того 27 дней до выполнения условия.
А что если добавить проверку переполнения переменной и в случае такового события присваивать переменной значение равное пройденным 4 дням?
Нет, все правильно будет работать. Там другая математика, отрицательных чисел нет, смотрите урок "математические операции". Числа идут по кругу, почти как на циферблате часов. Если считать в днях, то после 50 идёт 0, и наоборот перед 0 будет 50. То есть 0-1=50! 0-2=49,...0-46=5! А 18-46=23>=23 условие выполнилось на 18 день после перехода через 0. То есть через 23 дня! Только считать надо в миллисекундах
познавательно спасибо, не знал такого
Доброго времени. Правильно ли я тогда понимаю, что для использование в слайдере для таймлапс, (это когда фотокамера установлена на слайдере и должны происходить два действия. Камера смещается на заданное расстояние, к примеру на один или два шага двигателя, останавливается, срабатывает затвор камеры. Камера вновь перемещается и так до края слайдера.) необходимо использовать millis()?
не могу найти видео со спящим режимом. или такого выпуска еще не было?
А почему вы не упоминаете тернарный оператор? или вы не пользуетесь, очень удобно, как по мне
Сложноват для новичков. На сайте в уроках всё есть
@@ArduNotes а ок, тогда проставлю лайк) и пойду читать с сайта, так все же код чище
Код чище, но иногда хуже читается. Я сам то постоянно использую
@@ArduNotes я начал писать на атоме вот это действительно стоящая замена, конечно тяжело привыкнуть к постоянному цыклу, и иногда по привычке засовываю ещё один хотя можно реальзовать с помощью маяка. Спасибо вашим урокам уже за 2 недели, в принципе понимаю все, у вас подход к урокам как у моего ментора, что очень круто, и очень неплохо приводите примеры из жизни что тоже очень помогает новичкам понимать что и зачем, я когда начинал в 2015 то для меня это был темный лес, от переменных и функций до побитовых операторов
@@ArduNotes и ещё вопрос я то думаю что я уже на него ответил но все же, я так понимаю что ATOM с platformIO, при создании проекта, инициализирует костяк библиотеки ? тоесть если реальзовать внутри main.cpp класс, то потом можно перенести в библиотеки и использовать уже через include
с какого именно момента начинается работа счетчика? с первой строчки в void setup?
Отлично. Спасибо.
Расскажи как выводить информацию на сдвоенный (x3, x4 итд) семисегментный индикатор и чем отличается способ с одиночными индикаторами и сдвиговыми регистрами. И как на SD-флешке создавать файл с логами. Не обязательно в ближайшем видео, а вообще когда-нибудь
А если счётчик переполнится? И наш Last_time будет равен 4.294.966.000 допустим то код будет выглядеть так, millis опять стал 0 и будет
(0 - last_time(4.294.966.000) > 1000) , так получается что условие больше не выполнится или будет выполнять я раз в 50 суток? И надо будет перезапускать МК? Или как это работает?
Как сделать так, чтобы ограничить действие например: Нажать кнопку можно только раз в час и действие будет производиться только раз в час! Например: Наливаем воду в стакан, нажал кнопку стакан наполнился, но при повторном нажатии оно реагировать не будет пока не пройдет час.
Alex, подскажи а как с помощью "millis()" реализовать моргание надписи на дисплее с настраиваемыми временами. Например: 1 сек. индикации, 1 сек. паузы? Ну типа аналог стандартного блинка, только без использования "delay". Спасибо.
я правильно понял, что по умолчанию переменной last_time присваивается значение 0 ?
а то, что-то как-то непонятно :(
да да ... сам спросил, сам ответил - да 0
@@Helg1002 Хорош)))
Так как время срабатывает на миллисекунду позже, я сделал if(millis() - last_time > 5000 - 1), хотя наверное как сказано ниже будет лучше if(millis() - last_time > 4999) чтобы была меньше нагрузка на процессор микроконтроллера.
А функция micros() вызывает какие-то перевернутые вопросы, уже больше получаса не могу исправить, код с micros настолько же прост как с millis
Интересно, что будет жрать меньше памяти в последнем примере - ==0 или ! перед выражением?
памяти одинаково будет жрать, да и процессорного времени одинаково будет отнимать обе эти операции. В итоге компилятор скомпилирует в JE если для случая ==0, или JNE для случая !=0, для процессора и первое и второе одна команда, то есть "скушает" один квант времени на обработку
Alexei Belousov А я думал, что процессор для случая if(exp ==0) сначала обработает равенство нулю, а потом проверит условной конструкцией. А для случая if(!exp) проведет простую инверсию, а потом проверит условной конструкцией. Но спасибо за ответ.
Mexahoid BW11 кстати эта же операция будет происходить с типом булево, ведь процессор реально делает сравнение к нулю, при операции a == false, она равнозначна a==0. А вот если сравниваются произвольные числа, не 0 и 1, то будет уже 2 команды процессора первая CMP - сама операция сравнения, и потом уже операция условного перехода jl, jb, jnl, jnb и другие.. то есть когда сравниваем произвольные числа понадобятся минимум (тут все зависит от типа переменной числа) 2 команды процессора или 1 команда в случае сравнения булевых типов или 0 или 1. В любом случае операции отрицания и сравнения равнозначны для процессора с точки зрения используемых ресурсов
А существуют ли переменные, для которых можно задать числовое значение, допусти kek== 1, kek ==2, для того, чтобы считать, например минуты
Алекс а могли бы Вы снять видио с разбором где в одном скейче выполняются две операции например открываем серво привод по температуре и закрываем соответсвено по ней, и также по времени к примеру два раза в день включалось рэле минут на 5-10 . и соотвествено я так понимаю тут уже нужны часы реального времени.
Спасибо за видео и попутного ветра
Сделай урок по прерываниям
конструкция мне пригодилась
if (millis() - last_time > 1000) {
last_time = millis();
//что делать);
Спасибо!
А зачем перед last_time ставить "-" ? Автор не объяснил
Алекс а в примере с "дебонсом" во втором if такую строчку " && millis() - last_press > 100 " тоже надо ?
нет, если кнопка отпущена, то она отпущена, дребезга при отпускании не будет
совсем заработался я , этот вопрос задавал для 6 урока . Главное , что ты понял .
Низкий поклон тебе за науку !
+Заметки Ардуинщика
Ну не знаю, у меня кнопка при отпускании срабатывает
А можно с помощью millis мигалку сделать?
3:24 кормить кота раз в 21 день)))
@Тарас Атавин Ебать! Я прозрел!
при первом заходе в if не понятно чему будет равен last_time. его нужно где-то определить
он при объявлении автоматически инициализируется нулём
Подскажите пожалуйста. Гавер в видосе объявил переменную last_time типа unsigned long, но не указал значение. Дальше ты вроде бы как не присваивал её значения. Как понять чему равна переменная last_time. 2:02
Typical ,он не указал значение ,он просто объявил last_time как переменную(можно сказать равна нулю),Millis- время после включения ардуино.Теперь условие : когда время после включения - 0 станет > 5000, то выполняется условие и мы перезаписываем last_time ( last_time= Millis=5000)и когда получается что last_time=5000, то не выполняется условие и мы ждём когда пройдёт ещё 5секунд (тогда будет Millis будет равен 10000 и будет снова выполнятся условие)и так бесконечно и при этом остальной код тоже выполняется
Почему в последнем примере в связке if else отсутствуют фигурные скобки?
не могу понять как работает переменная в круглых скобках unsigned long в операторе if (millis() - last_time > (unsigned long)23 * 24 *60 *69 100 ) для чего она там нужна ??
Здравствуйте Алекс, я начинающий поэтому не понял немного, пересмотрел видео раз двадцать наверное и все равно догнать никак не могу !!! вы создали переменную ласт тайм но ведь вы не указали ей никакого значения, а как тогда milis отнимет значение ласт тайм если его нет ? или как там вообще это работает объясните по подробнее каждый шаг и цикл пожалуйста
дык укажи))
как сделать таймер на миллис что бы несколько светодиодов мигали каждый по своему времени ? покажите нужно для программы у меня там будет порядка 15 -20 задержек на миллс
жаль что часто алекс читает комменты по типу: классное видео, круто. А реальные вопросы к видео остаются незамеченными...
к сожалению в ютубе нет такого фильтра на коменты и читать приходится всё =) на реальные вопросы к видео в 90% случаев ответ находится в видео
@@ArduNotes Интересно, как это так что ты почти сразу отвечаешь на мои комменты??? В третий раз уже. Это ты получается заходишь посмотреть коменты и из последних мой комент попадается? Или как?) Кстати я сначало начал учиться по курсу Радиолюбителя TV, потому что у него больше видео и проще. Но вскоре я понял что ничего не понял и изучаю теперь твои уроки вроде все понимаю хорошо. Надо было сразу так)
Сидел игрался с шим приходит сообщение о новом видио. Попробовал использовать вместо delay(10);
Вот что получилось
void loop()
{
int x = 1;
for (int i = 0; i > -1; i = i + x) //В цикле увеличиваем значение i на величину х
{
if (millis()-lfst_tim >32000); // Это вместо задержки delay(10)
{
lfst_tim=millis();
analogWrite(LedPin3, i); // Функция analogWrite(,) зажигает диод со значением i
if (i == 255) x = -1; // переключение управления на максимуме
//delay(10); //Эту команду не выполняем имхо заремлено
}
}
}
Таймер вставляем внутрь цикла иначе у вас будер задержка перед работой цикла
Вопрос к зубрам Ардуиномании : не будет ли конфликта драйверов ch340 если к компьютеру кроме Ардуинки подключается 3D принтер(тоже реализован на Ардуино) ?
Нет, просто в системе будут два дополнительных COM-порта, а не один.
TheSquareIronBox спасибо
Чего? Delay это задержка, а Millis() - last_Tim... переодическое выполнение кода. Это разные действия, т. е delay этими millis не заменешь. И в таком случае эти милис тоже не лучший выбор,потому что перед ней может идти функция которая выполняется 20 минут и тогда только после этих 20 минут выполнится условие,есть шанс что период будет больше 5 секунд . Правильно, это прерывания от таймера получать и результат 100%
пример с остатком от деления бредота - частота выполнения не задается, она будет рандомной,
Для чего в строке if (millis()- last_time>1000){ стоит знак -
я тоже не могу понять
Это знак минус, вычисляет разницу милис и ласттайм, чтобы сравнить с задержкой
А можно миллис использовать один раз? То есть вместо делей в разных местах
То есть не каждые 5 сек а через 5 сек и всё
не понял одно сегодня Использовал этот участок кода.. поставил для датчиков температуры и атмосферного давления. через каждые 10 минут .. Что будет при переполнении счетчика millis
Ничего
класс, только редко выходят видео...
+Алексей Салей зато на основном канале часто))
Заметки Ардуинщика а какой основной канал, а то я тут новичок. Только подписался...
AlexGyver
Уже нашёл, но там про ардуино ничего нет, а интересна эта тема, а не любые видео лишь бы часто...
а там и не любые, там всё очень круто
на millis() -ах луче код работает т.к. когда я взял на % ардуина как бешеная просчитывает, на примере вкл. выкл. светодиода она истерично промигивалась пропускала / глючила ( ну код постоянно ломился через условие == 0 , а когда millis() и знак > но верняк всё чётко стабильно , спс . И го побольше простых гайдов без библиотек , по возможности , что бы можно было понять суть кода? я пытался изложить мысль как смог. Как по датчику ардуину будить, и по этому же дачику ( света) ложить спать ?
Эта конструкция за миллисекунду два-пять раз выполняется:
void loop() {
if ((last_time = millis()) % 1000 == 0) {
Serial.println(last_time);
}
}
А вот это работает как надо:
void loop() {
if (millis() == (last_time+1000)) {
last_time = millis();
Serial.println(last_time);
}
}
Либо так:
void loop() {
if ((last_time = millis()) % 1000 == 0) {
Serial.println(last_time);
}
delay(1);
}
И еще, почему-то условие if (millis() % 1000 == 0) {} выполняется при mills=1000,1001;2000,2001;3000;3001;и т.д....
т.е 1001 % 1000 == 0. Почему так?