Синхронизация времени NTP + DS3231

Тема в разделе "Arduino & Shields", создана пользователем Александр Данилюк, 22 июн 2016.

  1. Помогите оптимизировать код.
    В NTP нужно избавится от delay. А в идеале все впихнуть в 1 или 2(запрос-ответ) функции.
    Через millis не получается а с delay часы начинают отставать и непонятно лагать.
    Код (C++):
    #include <SPI.h>
    #include <Ethernet.h>
    #include <EthernetUdp.h>
    EthernetUDP Udp;

    byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
    IPAddress ip(192, 168, 137, 2);
    IPAddress myDns(192, 168, 137, 1);
    IPAddress gateway(192, 168, 137, 1);
    IPAddress subnet(255, 255, 255, 0);
    unsigned int localPort = 8888;
    char timeServer[] = "time.nist.gov";
    const int NTP_PACKET_SIZE = 48;
    byte packetBuffer[NTP_PACKET_SIZE];


    void ntp_t(char* address) {
      memset(packetBuffer, 0, NTP_PACKET_SIZE);
      packetBuffer[0] = 0b11100011;
      packetBuffer[1] = 0;
      packetBuffer[2] = 6;
      packetBuffer[3] = 0xEC;
      packetBuffer[12]  = 49;
      packetBuffer[13]  = 0x4E;
      packetBuffer[14]  = 49;
      packetBuffer[15]  = 52;

      Udp.beginPacket(address, 123);
      Udp.write(packetBuffer, NTP_PACKET_SIZE);
      Udp.endPacket();
    }


    void ntp_r() {
      if (Udp.parsePacket()) {
        Udp.read(packetBuffer, NTP_PACKET_SIZE);
        unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
        unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
        unsigned long secsSince1900 = highWord << 16 | lowWord;
        const unsigned long seventyYears = 2208988800UL;
        unsigned long epoch = secsSince1900 - seventyYears;
        Ethernet.maintain();
      }
    }

    void setup() {
      Serial.begin(9600);
      Ethernet.begin(mac, ip, myDns, gateway, subnet);
      Udp.begin(localPort);
      pinMode(4, OUTPUT);
      digitalWrite(4, HIGH);
    }

    void loop() {
      ntp_t(timeServer);
      delay(1000);
      ntp_r();
      delay(10000);
    }
    Кодом для часов еще не занимался но там проблем поменьше будет.
    Код (C++):
    #include <Wire.h>

    unsigned long previousMillis = 0;

    byte decToBcd(byte val) {
      return ( (val / 10 * 16) + (val % 10) );
    }

    byte bcdToDec(byte val) {
      return ( (val / 16 * 10) + (val % 16) );
    }


    void set_time(byte second,
                  byte minute,
                  byte hour,
                  byte dayOfWeek,
                  byte dayOfMonth,
                  byte month,
                  byte year)
    {
      Wire.beginTransmission(0x68);
      Wire.write(0);
      Wire.write(decToBcd(second));
      Wire.write(decToBcd(minute));
      Wire.write(decToBcd(hour));
      Wire.write(decToBcd(dayOfWeek));
      Wire.write(decToBcd(dayOfMonth));
      Wire.write(decToBcd(month));
      Wire.write(decToBcd(year));
      Wire.endTransmission();
    }


    void get_time(byte *second,
                  byte *minute,
                  byte *hour,
                  byte *dayOfWeek,
                  byte *dayOfMonth,
                  byte *month,
                  byte *year)
    {

      Wire.beginTransmission(0x68);
      Wire.write(0);
      Wire.endTransmission();
      Wire.requestFrom(0x68, 7);

      *second     = bcdToDec(Wire.read() & 0x7f);
      *minute     = bcdToDec(Wire.read());
      *hour       = bcdToDec(Wire.read() & 0x3f);
      *dayOfWeek  = bcdToDec(Wire.read());
      *dayOfMonth = bcdToDec(Wire.read());
      *month      = bcdToDec(Wire.read());
      *year       = bcdToDec(Wire.read());
    }


    void serial_set_time() {
      if (Serial.available()) {
        if (Serial.read() == 84) {
          byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
          second        = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          minute        = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          hour          = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          dayOfWeek     = (byte)  (Serial.read() - 48);
          dayOfMonth    = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          month         = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          year          = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          Wire.beginTransmission(0x68);
          Wire.write(0x00);
          Wire.write(decToBcd(second));
          Wire.write(decToBcd(minute));
          Wire.write(decToBcd(hour));
          Wire.write(decToBcd(dayOfWeek));
          Wire.write(decToBcd(dayOfMonth));
          Wire.write(decToBcd(month));
          Wire.write(decToBcd(year));
          Wire.endTransmission();
          Serial.println("Time updated successfully");
        }
      }
    }


    void setup() {
      Wire.begin();
      Serial.begin(9600);
    }


    void loop() {
      unsigned long currentMillis = millis();
      if (currentMillis - previousMillis >= 1000) {
        previousMillis = currentMillis;
        byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
        get_time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

        if (hour < 10)
        {
          Serial.print("0");
        }
        Serial.print(hour, DEC);
        Serial.print(":");
        if (minute < 10)
        {
          Serial.print("0");
        }
        Serial.print(minute, DEC);
        Serial.print(":");
        if (second < 10)
        {
          Serial.print("0");
        }
        Serial.print(second, DEC);
        Serial.print(" ");
        Serial.print(dayOfMonth, DEC);
        Serial.print("/");
        Serial.print(month, DEC);
        Serial.print("/");
        Serial.print(year + 2000, DEC);
        Serial.println();
        serial_set_time();
      }
    }
     
     
  2. fogary

    fogary Гик

    Если я правильно понял алгоритм работы Вашей программы: отправить запрос к серверу NTP (функция ntp_t); подождать секунду; проверить ответ сервера (функция ntp_r); если ответ получен, то получить значение времени; подождать 10 секунд и повторить все с начало. Если признак получения ответа от сервера - это проверка Udp.parsePacket(), то, как вариант, можно попробовать так.
    Убрать проверку из функции ntp_r:
    Код (C++):
    void ntp_r() {
        Udp.read(packetBuffer, NTP_PACKET_SIZE);
        unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
        unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
        unsigned long secsSince1900 = highWord << 16 | lowWord;
        const unsigned long seventyYears = 2208988800UL;
        unsigned long epoch = secsSince1900 - seventyYears;
        Ethernet.maintain();
    }
    Функцию loop() переписать так:
    Код (C++):
    void loop() {
      ntp_t(timeServer);
      while(!Udp.parsePacket())
        ;
      ntp_r();
      delay(10000);
    }
    По поводу второго скетча, логичнее записать функцию serial_set_time() так:
    Код (C++):
    void serial_set_time() {
      if (Serial.available()) {
        if (Serial.read() == 84) {
          byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
          second        = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          minute        = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          hour          = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          dayOfWeek     = (byte)  (Serial.read() - 48);
          dayOfMonth    = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          month         = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          year          = (byte) ((Serial.read() - 48) * 10 +  (Serial.read() - 48));
          set_time(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
          Serial.println("Time updated successfully");
        }
      }
    }
     
  3. Благодарю за поправки и за то что отписались. Что делает Udp.parsePacket() понятия не имею пример взят из стандартных библиотек. Ваш loop() не прокатил. Но у меня сейчас родилась идея запихать все в case и переменную взять для него var которая будет увеличиваться на единицу через функцию millis() и обнуляться в последнем кейсе.
     
  4. Думаю дальше. Через case пока не получилось так как пока он активен функция в нем вызывается непрерывно. За 1 секунду я опросил сервер NTP 119 раз =)
     
  5. Вот такое вот решение для реализации цыкла буду пробовать в своей программе. Может кому пригодится или может кто даст идею получше для тайм менеджмента. В конечной программе будет куча функций у которых разные задержки по времени. Для примера тот же DS18B20 нужно спросить затем подождать затем забрать ответ. Подумайте как это сделать не используя delay(); или тот же опрос NTP сервера там все тоже самое только зачем синхронизировать RTC часы каждую секунду, максимум раз в сутки а еще лучше если время с NTP и RTC не совпадает то провести синхронизацию.
    Код (C++):
    unsigned long LastUpdateTime = 0;
    int q;

    void setup() {
      Serial.begin (9600);
    }

    void loop() {
      if (millis() - LastUpdateTime > 5000 && q == 0) {
        void_1();
      }
      if (millis() - LastUpdateTime > 5000 && q == 1) {
        void_2();
      }
      if (millis() - LastUpdateTime > 5000 && q == 2) {
        void_3();
      }
    }

    void void_1() {
      LastUpdateTime = millis();
      q++;
      Serial.println("void_1");
    }

    void void_2() {
      LastUpdateTime = millis();
      q++;
      Serial.println("void_2");
    }

    void void_3() {
      LastUpdateTime = millis();
      q = 0;
      Serial.println("void_3");
    }
     
  6. Vetrinus

    Vetrinus Гик

    Arduino Mega Server умеет корректно получать время с NTP из коробки. Можете там посмотреть реализацию
     
  7. Спасибо. По началу не обращал внимания на данный проект так как обычно в коммерческих больших проектах в коде программы может разобраться только тот кто ее создал (и то не факт). Благодаря Вам посмотрел и был сильно удивлен. Для меня там целый клондайк полезной информации в которой можно что-то понять. Жаль только комментариев не так много ну и еще я предпочитаю не использовать библиотеки (да и зачем их использовать для тех же DS18B20 или DS1307/DS3231)
     
  8. Vetrinus

    Vetrinus Гик

    В принципе, по общим вопросам смогу вам помочь в скайпе.
     
  9. Смотрю по АМС даже документацию делают. Начну пожалуй с нее. Спасибо, если будут вопросы обязательно обращусь. Хотя один главный вопрос который сразу у меня вылез "а как это все вместе работает". Можно функции писать на новых вкладках в IDE? Как тогда они связываются ведь в конце мы имеем 2 скетча только 1 главный другой тот что на вкладке. Или можно кучу подпрограмм на вкладках сделать а в основном скетче попросту обращаться к подпрограммам? Я впервые вижу подобную разработку. Даже когда заказывали коммерческий проект там все было в куче и программа в скетче на 5000 строк.