Баг в коде, не могу увидеть причину

Тема в разделе "Arduino & Shields", создана пользователем arssev1, 13 янв 2018.

  1. arssev1

    arssev1 Гик

    Железо - Arduino, LCD1602 I2C, BMP280, DS3231, DHT11, кнопка
    Данные с сенсоров и часов приходят на Arduino, она выводит их на LCD. Режима всего два: в верхней строке всегда отображается дата и время, в нижней либо температура и влажность, либо давление. Отображаемое меняется в зависимости от нажатия кнопки.
    К слову, я ни разу не технарь по образованию, кодить в Arduino IDE учился по наборам Амперки, пожалуйста не кидайтесь тапками.
    Теперь про баг - на дисплее после запуска первые 6000мс ничего не отображается, а вторые 6000мс нажатия с кнопки не читаются, по прошествии этих 12 секунд все ок. Еще если вы укажете, где можно было реализовать функционал проще - буду благодарен
    Код (C++):
    //Последние изменения в коде 13.01.2018

    #include <SPI.h> //подключаем библиотеку для работы по SPI
    #include <Wire.h> //подключаем библиотеку для работы по I²C
    #include <LiquidCrystal_I2C.h> //подключаем библиотеку для ЖКИ для работы по I²C
    #include <RTClib.h> //подключаем библиотеку для DS3231
    #include <DHT.h> //подключаем библиотеку для DHT11
    #include <Adafruit_Sensor.h> //для нормальной работы библиотеки, что ниже
    #include <Adafruit_BMP280.h> //библиотека для BMP280

    LiquidCrystal_I2C lcd(0x3F, 16, 2); //Подключаем ЖКИ 16x2 c I²C адресом 0x3F
    RTC_DS1307 rtc; //DS1307 и DS3231 одинаковы в работе, так что можно подключать и то и то
    DHT dht(4, DHT11); //сигнальный пин DHT11 подключаем к 4 пину Arduino
    Adafruit_BMP280 bmp(10); //BMP280 подключен через аппаратный SPI и CS к 10 пину Arduino

    #define BUTTONPIN 9 //кнопка подключена через подягивающий резистор к 9 пину

    int temperature = 24; //сюда пишем температуру
    int humidity = 45; //сюда - влажность
    float pressure = 760.00; //сюда - давление
    byte whatToDisplay = 0;
    unsigned long buttonReadCounter;
    unsigned long sensorReadCounter;
    unsigned long displayWriteCounter;
    int currentButtonState;
    int previousButtonState;

    byte waterdrop[] = { //символ капли (иконка влажности)
      0x04,
      0x04,
      0x0A,
      0x0A,
      0x11,
      0x11,
      0x11,
      0x0E
    };

    byte degree[] = { //символ градуса
      0x08,
      0x14,
      0x08,
      0x00,
      0x00,
      0x00,
      0x00,
      0x00
    };

    byte thermometer[] = { //символ термометра
      0x04,
      0x0A,
      0x0A,
      0x0E,
      0x0E,
      0x1F,
      0x1F,
      0x0E
    };

    byte barometer[] = { //символ давления
      0x04,
      0x04,
      0x04,
      0x15,
      0x0E,
      0x04,
      0x11,
      0x0E
    };

    void setup() {
      lcd.begin(); //инициализируем ЖКИ
      lcd.backlight(); //включаем подсветку ЖКИ
      rtc.begin(); //инициализируем часы
      dht.begin(); //инициализируем DHT11
      bmp.begin(); //инициализируем BMP280
     
      lcd.createChar(0, thermometer); //записываем в память ЖКИ символ термометра
      lcd.createChar(1, degree); //записываем в память ЖКИ символ градуса
      lcd.createChar(2, waterdrop); //записываем в память ЖКИ символ капли
      lcd.createChar(3, barometer); //записываем в память ЖКИ символ давления
    }

    void loop() {
      if (millis() - sensorReadCounter > 5100) {
        temperature = dht.readTemperature(); //читаем из DHT11 температуру
        humidity = dht.readHumidity(); //читаем из DHT11 влажность
        pressure = bmp.readPressure() / 133.3224; //рассчитываем давление (переводим из Па в мм рт.ст.)
        sensorReadCounter = millis();
      }
     
      currentButtonState = digitalRead(BUTTONPIN); //читаем кнопку
      if (currentButtonState == HIGH && previousButtonState == LOW && millis() - buttonReadCounter > 200) {
        whatToDisplay++; //меняем то, что будем изображать
        if (whatToDisplay > 1) whatToDisplay = 0; //если досичтали до 2 - сбрасываем на 0
        displayWriteCounter = 6001; //КОСТЫЛЬ! дабы при нажатии кнопки сразу менять изображение на ЖКИ
        buttonReadCounter = millis();
      }

      if (millis() - displayWriteCounter > 6000) {
        lcd.clear(); //очищаем ЖКИ
        displayDateTime(); //отображаем время и дату
        if (whatToDisplay == 0) displayTempHum(); //отображаем температуру и влажность
        else if (whatToDisplay == 1) displayPressure(); //отображаем давление
        displayWriteCounter = millis();
      }
     
      previousButtonState = currentButtonState;
    }

    void displayDateTime() { //функция отображения даты и времени
      DateTime now = rtc.now(); //читаем из часов дату и время
     
      if (now.day() <= 9) { //если день - однозначное число
        lcd.setCursor(0, 0);
        lcd.print("0"); //пририсовываем перед ним нолик
        lcd.print(now.day());
      }
      else if (now.day() >= 10) { //иначе
        lcd.setCursor(0, 0);
        lcd.print(now.day()); //просто отображаем
      }
      lcd.print("."); //рисуем точку
     
      if (now.month() <= 9) { //если месяц - однозначное число
        lcd.setCursor(3, 0);
        lcd.print("0"); //пририсовываем перед ним нолик
        lcd.print(now.month());
      }
      else if (now.month() >= 10) { //иначе
        lcd.setCursor(3, 0);
        lcd.print(now.month()); //просто отображаем
      }
      lcd.print("."); //рисуем точку
     
      lcd.setCursor(6, 0);
      lcd.print(now.year()); //отображаем год

      if (now.hour() <= 9) { //все те же манипуляции с часом
        lcd.setCursor(11, 0);
        lcd.print("0");
        lcd.print(now.hour());
      }
      else if (now.hour() >= 10) {
        lcd.setCursor(11, 0);
        lcd.print(now.hour());
      }
      lcd.print(":");
     
      if (now.minute() <= 9) { //и все те же манипуляции с минутой
        lcd.setCursor(14, 0);
        lcd.print("0");
        lcd.print(now.minute());
      }
      else if (now.minute() >= 10) {
        lcd.setCursor(14, 0);
        lcd.print(now.minute());
      }
    }

    void displayTempHum() {
      lcd.setCursor(1, 1); //в нижней строке
      lcd.write(0); //рисуем символ градусника
      lcd.print("=");
      lcd.print(temperature); //отображаем температуру
      lcd.write(1); //символ градуса
      lcd.print("C"); //и букву "С"

      lcd.setCursor(10, 1);
      lcd.write(2); //рисуем символ влажности
      lcd.print("=");
      lcd.print(humidity); //отображаем влажность
      lcd.print("%"); //рисуем знак процента
    }

    void displayPressure() {
      lcd.setCursor(1, 1); //в нижней строке
      lcd.write(3); //рисуем символ давления
      lcd.print("=");
      lcd.print(pressure); //отображаем давление
      lcd.print(" mm Hg");
    }
     
  2. Tomasina

    Tomasina Сушитель лампочек Модератор

    У тебя же в коде явно прописано - выводить на дисплей только после 6000 мс:
    Код (C++):
    if (millis() - displayWriteCounter > 6000) {

    сами же вставили костыль :) Да, он работает не так как задумано, а так, как написано :p

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

    Код (C++):
    #define BUTTONPIN           9
    #define SENSOR_UPDATE_TIME  5000
    #define DEBOUNCE_TIME       200
    #define BUTTON_PRESSED      (digitalRead(BUTTONPIN) && previousButtonState == LOW && millis() - buttonReadCounter > DEBOUNCE_TIME)) //читаем кнопку
    boolean needUpdateDisplay = true;

    void setup()
    {
      ///
    }

    void loop()
    {
      checkButton(); // читаем кнопку
      readSensors(); // читаем датчики

      if (needUpdateDisplay) // отрисовка при необходимости
      {
        //.. тут код вывода показаний на дисплей)
        needUpdateDisplay = false; // сбрасываем флаг обновления дисплея
      }
    }


    void checkButton() // обработчик кнопки
    {
      if (BUTTON_PRESSED)
      {
        needUpdateDisplay = true; // устанавливаем флаг необходимости обновления дисплея
        // тут код для действий по нажатию кнопки (без костылей!)
      }
    }


    void readSensors() // обработчик датчиков
    {
      if (millis() - sensorReadCounter > SENSOR_UPDATE_TIME)
      {
        needUpdateDisplay = true; // устанавливаем флаг необходимости обновления дисплея
        // тут код чтения датчиков
      }
    }
     
    arssev1 и arkadyf нравится это.
  3. arssev1

    arssev1 Гик

    Огромное спасибо ;)
    Действительно простое решение, но от другого костыля теперь избавиться не могу - как опрашивать сенсоры после подачи питания? Пока пользуюсь все тем же костылем
    Код (C++):
    unsigned long sensorReadCounter = 5101;
    Но явно можно как-то проще...
    Еще вот какой вопрос. Раньше помню была библиотека для DS1307, которая формировала сразу строку с нужным форматом даты и времени, но эта библиотека не дружила с другими устройствами подвешенными на I2C-шине. Сейчас вроде-бы как самая удачная либа - это RTClib, но она совсем не умеет в строку. Я уже столько страниц гугла и яндекса перерыл в поисках способа компактно сформировать строку формата
    Код (C++):
    %d/%m/%Y %H:%M
    ибо уходит очень много строк, чтобы это все выкинуть на LCD в аккуратном формате и я чую, что можно сделать все компактней. Буду очень благодарен, если с этим поможете :)
     
  4. Tomasina

    Tomasina Сушитель лампочек Модератор

    Вопрос непонятен.
    Есть константа SENSOR_UPDATE_TIME - это интервал между опросом сенсоров.
    После подачи питания сенсоры опрашиваются сразу, затем через каждый интервал.

    Во-первых, если код используется в обеих случаях (число меньше десяти, либо больше), то зачем его дважды прописывать?
    Вместо
    Код (C++):
    if(now.month()<=9){//если месяц - однозначное число
        lcd.setCursor(3, 0);
        lcd.print("0");//пририсовываем перед ним нолик
        lcd.print(now.month());
    }
    elseif(now.month()>=10){//иначе
        lcd.setCursor(3, 0);
        lcd.print(now.month());//просто отображаем
    }
    Проще так:
    Код (C++):
        lcd.setCursor(3, 0);
        if(now.month()<10 {lcd.print("0");}
        lcd.print(now.month());
    Результат тот же, а код понятней.

    Либо те же самое, но через макрос:
    Код (C++):
    // макрос вставляем в начало скетча
    #define DIG(value)   ((value < 10) ? "0" : "") // макрос для умной вставки лидирующего нуля

        lcd.setCursor(3, 0);
        lcd.print(DIG(now.month()));

    Во-вторых, стремление к предформатированию - это идеологически правильно, хотя и немного усложняет читабельность кода.
    Код (C++):
    char buff[22]; // переменная для сбора выводимой строки

    // собираем строку.  // ВАЖНО! sprintf не работает с типом float
    sprintf(buff, "%d.%02d.%02d %d:D02d", now.day(), now.month(), now.year()-2000, now.hour(), now.minute());
    lcd.setCursor(3, 0);
    lcd.print(buff);
     
    Последнее редактирование: 13 янв 2018
  5. arssev1

    arssev1 Гик

    Код (C++):
    "%d.%02d.%02d %d:D02d"
    Увы, не работает :(
    А вот так вот в самый раз -
    Код (C++):
    "%02d.%02d.%04d %02d:%02d"
    Единственное, что я не понимаю - откуда берется 22 в строке buff[22]
     
    Последнее редактирование: 13 янв 2018
  6. Tomasina

    Tomasina Сушитель лампочек Модератор

    Это длина строки на экране + еще одно знакоместо (резерв).
     
    arssev1 нравится это.
  7. arssev1

    arssev1 Гик

    Так тогда 17 получается
     
  8. Tomasina

    Tomasina Сушитель лампочек Модератор

    ну я под свой дисплей писал ;)
     
    arssev1 нравится это.
  9. arssev1

    arssev1 Гик

    А, ну да :) Спасибо большое!
    Код (C++):
    sprintf(timeString, "%02d.%02d.%04d %02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute());
    Так правильно же будет? чтобы год был в формате YYYY нужно %04d добавить
     
    Последнее редактирование: 13 янв 2018
  10. Tomasina

    Tomasina Сушитель лампочек Модератор

    Да, правильно.
    Только зачем для первых чисел ведущий ноль?
    "01.12.2017 09:06" - некрасиво же, вот так приятнее: "1.12.2017 9:06".
     
  11. arssev1

    arssev1 Гик

    Мне так наоборот приятнее читать, кстати, можно заставить выдавать ds3231 12-часовой режим
     
  12. DIYMan

    DIYMan Guest

    Цитата из даташита:
    Вот кусок кода из одной библиотеки по установке формата 12/24:
    Код (C++):
    void DS3231::setClockMode(bool h12) {
        // sets the mode to 12-hour (true) or 24-hour (false).
        // One thing that bothers me about how I've written this is that
        // if the read and right happen at the right hourly millisecnd,
        // the clock will be set back an hour. Not sure how to do it better,
        // though, and as long as one doesn't set the mode frequently it's
        // a very minimal risk.
        // It's zero risk if you call this BEFORE setting the hour, since
        // the setHour() function doesn't change this mode.
       
        byte temp_buffer;

        // Start by reading byte 0x02.
        Wire.beginTransmission(CLOCK_ADDRESS);
        Wire.write(0x02);
        Wire.endTransmission();
        Wire.requestFrom(CLOCK_ADDRESS, 1);
        temp_buffer = Wire.read();

        // Set the flag to the requested value:
        if (h12) {
            temp_buffer = temp_buffer | 0b01000000;
        } else {
            temp_buffer = temp_buffer & 0b10111111;
        }

        // Write the byte
        Wire.beginTransmission(CLOCK_ADDRESS);
        Wire.write(0x02);
        Wire.write(temp_buffer);
        Wire.endTransmission();
    }