Проверить прошел ли час от прошлого действия

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

Чевой-то я тупею по весне...

Надо ежечасно записывать некоторые данные в EEPROM. Корректен ли этот код, или есть более изящное решение?

loop()
{
// ...
checkTime();
// ...
}

void checkTime()
{
  static unsigned long prevMillisTime;
  if((millis() - prevMillisTime) > 60 * 60 * 1000) // если с последней записи прошло больше часа
  {
    EEPROM_Save_Data();                    // записываем текущие показания в EEPROM
    prevMillisTime = millis();
  }
}

 

leshak
Offline
Зарегистрирован: 29.09.2011

Трудно придратся. На первый взгляд "идеально". Но мы трудных задач не боимся ;)

1. Откройте виндовый калькулятор. Умножте 60*60*1000, отодвинте в угол экрана так что-бы видеть перед собой когда откроете ArduinoIDE

2. Загрузите этот скетч.

void setup(){
  Serial.begin(57600);
  unsigned long val=60*60*1000;
  Serial.println(val);
    
}

void loop(){
}

3. Откройте Serial монитор и посмотрите на цифру которую вам выведет Serial.println.
4. Перевидете взгляд на калькулятор, обратно на Serial. Протрите глаза. Воскликните "да как же так...!!!!"
5. Прекратите нервно смеятся
6. Идите читать Целочисленные константы | Аппаратная платформа Arduino

Если просветление не наступит в течении часа - обращайтесь :)

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

я ж говорю - тупеююю, даже причины такого понять не могу, вроде размерности хватает с запасом.

leshak
Offline
Зарегистрирован: 29.09.2011

Tomasina пишет:

я ж говорю - тупеююю, даже причины такого понять не могу, вроде размерности хватает с запасом.

Я вам дал ссылку. Идите читать. Много раз. Час еще не прошел ;)   ДАО должно прийти :)

P.S. Вы не тупеете - грабли действительно "подлые". Даже зная про них - легко наступить. Поэтому я хочу "что-бы запомнили" :)  Ну и если "сами разгадаете" - вдвойне вам приятно будет.

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

15 минут мне хватило, чтобы понять, что это дает корректный ответ:

unsigned long val=60UL*60UL*1000UL; 

Но почему так по-разному считает компилятор, мне озарения не снизошло.

И ведь во многих скетчах (даже на этом форуме) такая запись, как в первом посте, прокатывала успешно, нареканий не было :) 

leshak
Offline
Зарегистрирован: 29.09.2011

Ну ok. Давайте по шагам повторим действие компилятора.

Вначале он смотрит на правую часть. Там у нас 60*60*1000

Вначале он умножит первые два числа.

60*60

Вопрос1: с точки зрения компилятора какой тип имеют аргументы операции умножения?
Вопрос2: какой тип будет у результат?
Вопрос3: какой будет результат? ;)

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

1. int
2. int
3. 3600

слева же явно указано, что результат должен быть беззнаковый long.

P.S. я понимаю, что вы будете ржать, но меня час назад реально забанили в Google. Всегда думалось, что это анекдот. А ведь всего лишь сделано несколько невинных запросов по C++, никакой Украины.

leshak
Offline
Зарегистрирован: 29.09.2011

Tomasina пишет:

1. int
2. int
3. 3600

Едем дальше. 

Умножаем 3600*1000

Три вопроса:

Вопрос1: с точки зрения компилятора какой тип имеют аргументы операции умножения?
Вопрос2: какой тип будет у результат?
Вопрос3: какой будет результат? ;)

Tomasina пишет:

слева же явно указано, что результат должен быть беззнаковый long.

А что нам "лево"? Мы туда еще не добрались. Мы про существование "лево" еще ничего не знаем. Может его и не будет вовсе (так тоже бывает). Динамической типизации у нас нет. Вывода типов - по большому счету тоже. Так что "по тупому, по шагам едем".

Какой тип у результата будет? Если перемножаем два int-та?

>я понимаю, что вы будете ржать, 

Не буду. Может конечно "глюк". Может на одном из серверов SSL-сертификат обновить забыли. А может и "все по серьезному". Действительно какую-то заразу подцепили. Или ваш провайдер стал слишком любопытным и пытается снифать ВЕСЬ трафиик.

P.S. Если конечно вы сами, не занимаетесь скажем Web-разработкой и не забыли выключить что-то типа Fiddler2 после того как отлаживали. Но думаю, в этом случае вы бы уже сами понимали "что это может означать".

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

1. оба int
2. т.к. размерность результата превышает умолчания (int), то надо взять следующую размерность, т.е. long.
Или компилятор не может выйти за пределы умолчаний и опять исчисляет результат как int с переполнением? В этом косяк? Тогда это тупой компилятор, рулить космическими кораблями не дадим.
3. 3600000 по моей логике :)

leshak
Offline
Зарегистрирован: 29.09.2011

>Или компилятор не может выйти за пределы умолчаний и опять исчисляет результат как int с переполнением? 

Бинго.

> Тогда это тупой компилятор, рулить космическими кораблями не дадим

Наоборот. Все под вашим контролем (и за все отвечаете вы). К тому же - это частный случай. Это C++, куча способов "выстрелить себе в ногу". Обратная сторона - предсказуемость и эффективность.

В первых есть куча ситуаций когда "переполнение" используют сознательно. Где оно позволяет написать более изящный и эффективный код.

Во вторых, предположим что компилятор ведет себя так как вы хотели. Теоретически это возможно. Он видит все числа. Может перемножить их на этапе компиляции (во время выполнение программы - умножение каждый раз не делается, раз результат известен). Посмотреть на результат и выбрать более подходящий тип.
Но....  предположим вы решили заменить одну константу на переменную. И написали int sec=60;, а его потом застивили sec*60*1000

Все. Заранее высчитать он не может. Только в процессе выполнения. Значит есть два варианта:
1. Делать переполнение 
2. ВСЕГДА все умножения, делать в виде LONG. А потом... 

Первый вариант - фигня выходит. При использовании константы и переменной с тем же значением - разные результаты. Отлавливать такой баг - это будет "подлось в квадрате". В такой космический корабль - точно лучше не садится.

Второй вариант - тоже фигня. Это какой же сумашедший оверхед по производительности будет. Все числа умножать как long-ги (это если опустить проблемы что у нас еще бывают знаковы и беззнаковые). Да еще на 8-ми битных контроллерах. К тому же, а что потом делать когда мы доберемся "направо присваивать"? Откидывать лишнии биты? Или переполнение делать? Неоднозначно.

И если "покопатся". То можно будет найти с десяток неоднозначностей. И неочевидностей которые один програмер "интуитивно" поймет в одну сторону, а другой - в другую.

leshak
Offline
Зарегистрирован: 29.09.2011

Кстати, если вы действительно "познали ДАО". То придумайте, как этот решение:

unsigned long val=60UL*60UL*1000UL;

можно сократить на 4-ре буквы ;)

 

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

Стесняюсь спросить: а зачем такой хитровывернутый мазохизм вместо одноразового рассчета величины таймаута? Вселенную погреть захотелось?

leshak
Offline
Зарегистрирован: 29.09.2011

Andrey_Y_Ostanovsky пишет:

Стесняюсь спросить: а зачем такой хитровывернутый мазохизм вместо одноразового рассчета величины таймаута? Вселенную погреть захотелось?

Полностью согласен с вами. Мозг - потребляет до 50% энергии. Греет вселенную. Может ошибатся. Может очепятатся. Потом опять будет греть вселенную, когда будет читать код. А не дай бог, нужно будет поменять с 60-ти минут на 43. Это же опять приближение тепловой смерти вселенной. Вспомнить "что на что мы там умножали", да потом еще умножить в уме 43*60

Поэтому, действительно, лучше расчитать это ОДИН раз. Только не мозгом, а компьютером. В момент компиляции.

Чисто из любопытсва. Cкопиляйте два варианта. Один "val=60UL*60UL*1000UL" и второй "val=36000UL" и сравните размеры получившихся бинарников.

P.S. Не, компиляторы все-таки не дураки пишут ;)

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

ладно, с циферками разобрались, теперь с буковками.

if (millis() - prevMillisTime) > x

Тут никаких подвохов не будет? Если m < p (при переполнении), если m > p?

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

Tomasina пишет:

ладно, с циферками разобрались, теперь с буковками.

if (millis() - prevMillisTime) > x

Тут никаких подвохов не будет? Если m < p (при переполнении), если m > p?

Потенциально - будет: если разность окажется равной X... Поэтому, обычно, рекомендут использовать выражения "меньше или равно" или "больше или равно"...

Ну и, по классике, пишется if( условие в скобках ) { действие по условию }

зы: Кстати, когда-то некто, похожий на leshak, грозился показать код для циклической записи в eeprom, чтобы увеличить время жизни одной ячейки памяти.

Тогда-же говорилось, что имеет смысл перед записью прочитать значение, и, если оно не изменилось, ничего не записывать...

leshak
Offline
Зарегистрирован: 29.09.2011

>Кстати, когда-то некто, похожий на leshak, грозился показать код для циклической записи в eeprom

Где грозился, покажите, может и покажем.... Честно - не помню.

Хотя, в данном случае - тоже думал, но решил что не стоит заморачиватся и заострять на это. При записи раз в час... ну сами посчитайте сколько времени хватит ресурса.

>Тут никаких подвохов не будет?
Не, не будет. Ну разве что сработает на какою-то миллисекунду, другую позже чем вы расчитываете. Но "точно час" у вас все равно через такой механизм не получить. Вообщем тут скорее дело вкуса. Хоти-те, как подсказал 
Andrey_Y_Ostanovsky можете поставить >= (лично я именно так и предпочитаю), хотите - забейте. На глаз вы разницы увидеть все равно не сможете.

>Если m < p (при переполнении), если m > p?

У вас ардуина есть?  Спросите ее. Привыкайте "быстро накидал тестовый скетчик". Пара переменных, пара принтов - и посмотрел что выходит.

Понятно, что "ждать сорок девять дней, что-бы увидеть переполнение" - не вариант. Ну так сделай "имитацию ситуации". Заведите пару переменных Unsigned long. Запихните в нужные вам значения. Вычтите одно из другого. Положите в третью. Выведите и посмотрите.

Или, если уж хочется "в режиме реального времени". Ну напишите свою millis() из будущего.

unsigned long myMillis(){
  return millis()+БОЛЬШОЕ_ЧИСЛО;
}

И пользуйтесь в коде ей. Если не вместо большого числа поставите 0- ничем от обычной миллис отличатся не будет. Чем больше число подставите - тем больше она "сдвинется в будущие".

Ну или по форуму... не раз этот вопрос обсуждался/объяснялся.

Кажись даже где-то в блоге меня с ним "догнали". Ага. вот http://alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html В конце там дописывал про это.

 

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

leshak пишет:
 решил что не стоит заморачиватся и заострять на это. При записи раз в час... ну сами посчитайте сколько времени хватит ресурса.

Не стоит заморачиваться, ибо там еще хитрее - запись раз в час, но в ячейку, соответствующую текущему часу. Т.е. по факту в одну и ту же ячейку повторная запись раз в сутки.

Цитата:
Ну разве что сработает на какою-то миллисекунду, другую позже чем вы расчитываете. Но "точно час" у вас все равно через такой механизм не получить. Вообщем тут скорее дело вкуса. Хоти-те, как подсказал Andrey_Y_Ostanovsky можете поставить >= 

Да хоть с задержкой в пару минут, некритично для моих целей. Я оставлю ">" по двум причинам - глаз не спотыкается и обработка на несколько тактов короче, чем ">=" :) 

Цитата:
грозился показать код для циклической записи в eeprom

Было такое, и, возможно, даже какие-то примеры приводились, но гугл меня игнорирует, а яша не слишком часто сюда заходит.