Железо - 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"); }
У тебя же в коде явно прописано - выводить на дисплей только после 6000 мс: Код (C++): if (millis() - displayWriteCounter > 6000) { сами же вставили костыль Да, он работает не так как задумано, а так, как написано Я бы сделал так: переменная-флаг, отвечающая за перерисовку дисплея. Все обработчики (сканирование датчиков, кнопка и пр.) при необходимости ее изменяют, а дисплей обновляется только если состояние этой переменной изменилось. Код (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; // устанавливаем флаг необходимости обновления дисплея // тут код чтения датчиков } }
Огромное спасибо Действительно простое решение, но от другого костыля теперь избавиться не могу - как опрашивать сенсоры после подачи питания? Пока пользуюсь все тем же костылем Код (C++): unsigned long sensorReadCounter = 5101; Но явно можно как-то проще... Еще вот какой вопрос. Раньше помню была библиотека для DS1307, которая формировала сразу строку с нужным форматом даты и времени, но эта библиотека не дружила с другими устройствами подвешенными на I2C-шине. Сейчас вроде-бы как самая удачная либа - это RTClib, но она совсем не умеет в строку. Я уже столько страниц гугла и яндекса перерыл в поисках способа компактно сформировать строку формата Код (C++): %d/%m/%Y %H:%M ибо уходит очень много строк, чтобы это все выкинуть на LCD в аккуратном формате и я чую, что можно сделать все компактней. Буду очень благодарен, если с этим поможете
Вопрос непонятен. Есть константа 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);
Код (C++): "%d.%02d.%02d %d:D02d" Увы, не работает А вот так вот в самый раз - Код (C++): "%02d.%02d.%04d %02d:%02d" Единственное, что я не понимаю - откуда берется 22 в строке buff[22]
А, ну да Спасибо большое! Код (C++): sprintf(timeString, "%02d.%02d.%04d %02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute()); Так правильно же будет? чтобы год был в формате YYYY нужно %04d добавить
Да, правильно. Только зачем для первых чисел ведущий ноль? "01.12.2017 09:06" - некрасиво же, вот так приятнее: "1.12.2017 9:06".
Цитата из даташита: Вот кусок кода из одной библиотеки по установке формата 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(); }