Работа с большими числами. Прошу помощи.

Vadim111
Offline
Зарегистрирован: 14.01.2015

Задача следующая: необходимо включать реле (т.е. подавать высокий сигнал на пин Ардуино) в заданные моменты времени. Эти моменты времени должны соответствовать реальному суточному времени. Моментов времени много и они могут быть в разные дни. Все эти моменты определяются пользователем перед началом работы, записываются в скетч и заливаются в Ардуину. Задача Ардуины только замкнуть реле в нужное время.

Например: Нужно замкнуть реле сегодня в 22:13:54, завтра в 10:05:43, в 15:02:16 и послезавтра в 18:00:00. Сегодня пользователь прописывает все эти временные точки в скетч и в 14:18:25 заливает скетч в Ардуину. А в 14:20:00 нажимает кнопку сброса на плате. (Погрешность во времени старта на несколько секунд допустима.) Программа стартонула. Особенность в том, что каждый раз временные точки разные и каждый раз пользователь заливает скетч в разное время.

Я знаю, что существует шилд реального времени. Но не хотелось бы связываться с дополнительными устройствами. На мой взгляд логика программы и без этого шилда достаточно проста. Но я столкнулся с непонятной проблемой.

Для того, чтобы привязаться к реальному суточному времени в скетче я ввел переменную, которая содержит точное время старта Ардуины. А чтобы пользователю было удобно вводить это время, я ввел еще 3 переменных: часы, минуты, секунды. В них пользователь введет то время суток, когда он предполагает нажать кнопку сброс (и запустить программу). Все временные точки также устанавливаются путем ввода часов, минут и секунд. Затем для каждой временной точки в скетче определяется расчетная величина суточных секунд. Опираясь на эти расчетные величины и происходит замыкание реле.

Программа работала некорректно. Иногда все было верно, иногда сработка была в неправильное время, иногда и вовсе не было сработки. Помучавшись некоторое время, я решил выводить в Сериал монитор все параметры и обнаружил, что расчетные параметры временных точек не соответствуют действительности.

Что я имею ввиду: Например, пользователь ввел, время предполагаемого старта программы 23 часа, 28 мину, 15 секунд. Это соответствует 23*3600 + 28*60 + 15 = 84495 секунд с начала суток. Т.е. расчетная переменная unsigned long powerTurnOn должна равняться 84495. Однако, если я вывожу эту величину в сериал монитор, то у меня там печатается другое значение.

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

Ниже привожу урезанный скетч и картинку того, что показывает Сериал монитор. В скетче обозначена только одна временная точка, когда реле должно замкнуться, и не учитываются дни. Я использовал разные способы вывода расчетных значений в Сериал монитор. Данные одинаковые. Но для удобства создал отдельную функцию, которая разбивает числовой параметр на часы, минуты и секунды. Сразу видно, что расчетные параметры не соответствуют правильным величинам.

Прошу помочь разобраться, в чем может быть ошибка и как еще можно привязаться к реальному суточному времени без использования соотв. шилда.

int RALAY_VKL = 11; // К пину 11 подключается реле.

// Глобальные переменные.
int phase = 0; // Это фаза, в которой пребывает программа - для хождения по циклу.

unsigned long currentTime = 0; // Время для сравнения с текущим.
boolean currentTimeFlag = false; // Флаг, показывающий, установлено ли текущее время в данном разделе цикла.
boolean showSerialMonitorFlag = false; // Флаг для вывода в Сериал монитор только один раз.

//--------------------- НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ:

unsigned long dayTimePeriod = 86400; // Интервал времени, показывающий условные сутки. (указывается в секундах)
                                   // в сутках 86400 сек.

int powerTurnOnHour = 23; // Время, когда оидается подача общего напряжения на плату. Когда подключаем батарейку. (часы).
int powerTurnOnMinute = 59; // Время, когда оидается подача общего напряжения на плату. Когда подключаем батарейку. (минуты).
int powerTurnOnSecond = 30; // Время, когда оидается подача общего напряжения на плату. Когда подключаем батарейку. (секунды).

int startWorkHour = 23; // Суточное время, когда произойдет первое вкл. реле.(суточное время - часы)
int startWorkMinute = 59; // ...(суточное время - минуты)
int startWorkSecond = 33; // ...(суточное время - секунды)

////---------------------------------- Вкл Реле параметры.
float firstRelayConnectTime = 1; // Время замыкания первого реле (в сек. можно указывать десятичные значения)

//---------------------// НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ КОНЕЦ:

//---------------------// РАСЧЕТНЫЕ ПАРАМЕТРЫ:
unsigned long powerTurnOn = ((powerTurnOnHour * 60 + powerTurnOnMinute) * 60 + powerTurnOnSecond); // расчетное время, когда оидается подача общего напряжения на плату(в милисек.)
unsigned long startWork = ((startWorkHour * 60 + startWorkMinute) * 60 + startWorkSecond); // расчетное время начала работы камеры (в милисек. с начала суток)

//---------------------// РАСЧЕТНЫЕ ПАРАМЕТРЫ КОНЕЦ.

void setup() {
  // Определяем режимы работы пинов
  pinMode(RALAY_VKL,OUTPUT);  
  Serial.begin(9600);//Описуем для того чтобы открыть порт.(9600)-это скорость работы порта 9600 бит (бод)
}

void loop() {
  
  unsigned long currentMillis = millis(); //функция millis() возвращ. колич. милис. с момента включения платы.
  unsigned long realTime = (powerTurnOn + currentMillis/1000)%(dayTimePeriod); // Расчитываем суточное время (секунды с начала суток)
  int daysLeft = (int)(powerTurnOn + currentMillis/1000)/(dayTimePeriod); // Количество суток, которое прошло с момента подачи напряжения (шт.)
  
  
  //----------------------------------------------ОСНОВНОЙ ЦИКЛ ПРОГРАММЫ.
  switch (phase) {
    //--------------------------------------------Ожидание времени начала работы.  
    case 0: // Ожидаем, когда наступит время включения реле.
      if (realTime >= startWork) { //Если истекло заданное суточное время то...
        if (!showSerialMonitorFlag) { // Печатаем расчетные параметры в сериал монитор только один раз.
            printSerialMonitor("powerTurnOn", powerTurnOn);
            printSerialMonitor("startWork", startWork);
          showSerialMonitorFlag = true;
        }
        currentTime = realTime; // Приравниваем "время для сравнения" к суточному.
        printSerialMonitor("Nschalo raboty vsey sistemy", currentTime);
        phase = 1; // Идем на включение реле.
      }
      break;
    //--------------------------------------------Ожидание времени начала работы закончено. 
    
    //--------------------------------------------Включение реле.  
    case 1: 
      if (!currentTimeFlag) { // Если время для сравнения еще не увеличили на необходимый интервал, то увеличиваем его.
        currentTime += firstRelayConnectTime; // Увеличиваем "время для сравнения" на время замыкания первого реле.
        currentTimeFlag = true; // Флаг говорит, что время увеличили.
      } else {
        if (realTime >= currentTime) { // Если установленное время ИСТЕКЛО.
          digitalWrite(RALAY_VKL, LOW); // Отключаем первое реле.
          phase = 2; // Переходим к фазе 2 работы программы. (удалил)
          currentTimeFlag = false; // Время для сравнения готово к новому приращению.
        } else { // Если установленное время НЕ ИСТЕКЛО.
          digitalWrite(RALAY_VKL, HIGH); // Замыкаем первое реле.
        }
      }
      break;
  }
} // end loop

// Функция, которая выводит в сериал монитор строку описания и реальное суточное время.
void printSerialMonitor(char description[], unsigned long describrdTime)
{
  unsigned long secundy = (describrdTime)%60;
  unsigned long minuty = ((describrdTime-secundy)/60)%60;
  unsigned long chasy = (((describrdTime-secundy)/60-minuty)/60)%24;
  Serial.print(description);
  Serial.print("\t");
  Serial.print(chasy, DEC);
  Serial.print(":");
  Serial.print(minuty, DEC);
  Serial.print(":");
  Serial.print(secundy, DEC);
  Serial.print("\n");
}

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

Уровень владения английским языком: normalno :) Извините, не удержался. Транслит просто всегда убивал.

Vadim111 пишет:

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

И этого хватило. Всё правильно работает, согласно кода и типу данных. http://arduino.ru/Reference/Arithmetic

23*60 = 1380

1380+59 = 1439

1439*60 = 20804

20804+30 = 20834

20834%60 = 14 собственно ваши секунды которые в мониторе и выводятся.

Vadim111
Offline
Зарегистрирован: 14.01.2015

Очень признателен! Помогло.

Получается, что если промежуточные вычисления превышают допустимый диапазон типов данных операндов, то результирующий тип данных (более широкий) не спасает. Надо каждый из операндов объявлять более широким диапазоном.

bwn
Offline
Зарегистрирован: 25.08.2014

Vadim111 пишет:

Очень признателен! Помогло.

Получается, что если промежуточные вычисления превышают допустимый диапазон типов данных операндов, то результирующий тип данных (более широкий) не спасает. Надо каждый из операндов объявлять более широким диапазоном.

Хотя бы один из операндов справа должен быть достаточного размера. Остальные подтянутся к нему. Результирующий должен позволять принять полученное значение.

axill
Offline
Зарегистрирован: 05.09.2011

причем не обязательно входные переменные определять большого размера, достаточно в строке вычисления указать явное преобразование типов, например:

int a = 32768;
int b = 32768;
long c = (long)a * (long)b;

 

Vadim111
Offline
Зарегистрирован: 14.01.2015

bwn и axill !

Большое спасибо. Очень полезная инфа. Раньше такого не встречал, сам бы не додумался и вряд ли нашел бы.

По возможности уточните еще такой вопрос:

Будет ли некое замедление работы платы или, к примеру, нестабильность работы, если я буду использовать все переменные unsigned long вместо int? Т.е. следует ли стремиться обозначать тип данных как можно более узкого диапазона или с точки зрения стабильности работы это не имеет значения? Т.е. можно ли мне в программе оперировать мили (или даже микро) секундами или лучше поменять временные интервалы на секунды? (с учетом того, что в пределах одной итерации цикла loop вычислений временных интервалов может быть много).

bwn
Offline
Зарегистрирован: 25.08.2014

Могу ошибатся, но для целочисленных переменных вроде роли не играет, исключение представляет c плавающей точкой. С целью оптимизации оперативной памяти(весьма ограниченной), переменные лучше использовать подходящего типа и без надобности не плодить. Замена интервалов на секунды в данном случае приведет к дополнительной математической нагрузке и соответственно увеличению времени работы цикла(визуально вы это скорее всего не заметите).

Я знаю, что существует шилд реального времени. Но не хотелось бы связываться с дополнительными устройствами. На мой взгляд логика программы и без этого шилда достаточно проста.  - А вот его я поставил бы. Каждый раз заливать нужное время, а потом еще и вспоминать когда надо включить устройство - геморр. ИМХО.

axill
Offline
Зарегистрирован: 05.09.2011

Для целочисленных тоже играет, особенно операции деления и особенно для long и long long

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

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

более половины ваших переменных влезут в byte, а если непредвиденная перезагрузка ? всё собъётся....

с часами проще. а график вкл/выкл нескольких каналов - в массив, тогда график можно прописать на большое время - на месяц, на полгода....