//запись данных с термодатчика Pt100 на SD-карту //часы на базе RTC DS1302 //настройка параметров #include <LiquidCrystal_I2C.h> //библиотеки работы с LCD через I2C #include <Wire.h> #include <iarduino_RTC.h> //библиотека для работы с модулем RTC DS1302 #include <SPI.h> //библиотека для работы с шиной SPI #include <SD.h> //библиотека для работы с модулем SD-карты //Не забудь шунтировать выход от кнопок резистором 10 КОм const byte BtDownPin = 5; //пин кнопки Уменьшить const byte BtUpPin = 6; //пин кнопки Увеличить const byte BtSettingPin = 7; //пин кнопки настроек const byte Pt100Pin = A0; //аналоговый пин термодатчика Pt100 const int chipSelect = 8; //пин CS модуля SD-карты. Везде пишут для Arduino UNO пин 4, //но 8 тоже работает. Здесь пин 4 занят под часы //шина SPI для модуля SD-карты //MOSI - 11 //MISO - 12 //SCK - 13 //Объявляем объект watch для работы с RTC модулем на базе чипа DS1302, //указываем пины Arduino подключённые к выводам модуля RST, CLK, DAT. //ШИМ пинов 3 и 11 используют один внутренний таймер, поэтому если на пине 3 //уже что-то висит (например CLK RTC), не сажайте на пин 11 устройство, //требующее ШИМ и наоборот. //пины ШИМ-сигналов у Arduino UNO : 3, 5, 6, 9, 10, 11 iarduino_RTC watch(RTC_DS1302, 2, 3, 4); //пины модуля I2C LCD-дисплея : //SDA - A4 //SCL - A5 //питание от шилда boolean LastButtonSetting = LOW; //предыдущее состояние кнопки boolean CurrentButtonSetting = LOW; //текущее состояние кнопки boolean LastButtonDown = LOW; boolean CurrentButtonDown = LOW; boolean LastButtonUp = LOW; boolean CurrentButtonUp = LOW; boolean FlagSettingLoop = true; //были ли мы в цикле нажатия кнопки Настройки boolean FlagDownLoop = true; //были ли мы в цикле нажатия кнопки Меньше boolean FlagUpLoop = true; //были ли мы в цикле нажатия кнопки Больше //нужна при нажатии и удерживании кнопки ВВЕРХ unsigned long ButtonHoldTimerUp = 0; //нужна при нажатии и удерживании кнопки ВНИЗ unsigned long ButtonHoldTimerDown = 0; //по прошествии этого времени кнопка ВВЕРХ или ВНИЗ //перейдет в режим удержания, мс const long ButtonHoldIncTime = 500; boolean FlagUpHold = true; //были ли мы в цикле удержания кнопки ВВЕРХ boolean FlagDownHold = true; //были ли мы в цикле удержания кнопки ВНИЗ float Pt100_Val = 0; //значение с термодатчика Pt100 (0...1023) //значение с термодатчика Pt100, преобразованное в температуру float Pt100_Temp = 0; float Pt100_Temp_Correction = 0; //температура в град с поправкой //данные для интерполяции датчика (значения по умолчанию) //нижняя граница вольтажа с аналогового порта переведенный в цифровой вид float Min_Volt = 39; //верхняя граница вольтажа с аналогового порта переведенный в цифровой вид float Max_Volt = 546; //соответствующая нижней границе температура float Min_Temp = 0; //соответствующая верхней границе температура float Max_Temp = 100; unsigned long currentTime = 0; //нужна, чтобы убрать delay() const long intervalTime = 1000; //интервал опроса датчика в мс unsigned long currentRecTime = 0; //нужна, чтобы убрать delay() long intervalRecTime = 10000; //интервал записи данных в мс int TEMP_CORRECT=0; //поправка температуры датчика. const int TEMP_CORRECT_MAX = 100; //верхний порог const int TEMP_CORRECT_MIN = -100; //нижний порог boolean FlagSettingIn = false; //находимся ли в настройках boolean FlagRecordOn = false; //идет запись int8_t SettingNumber = 0; //пункт меню настройки File myFile; LiquidCrystal_I2C lcd(0x27,16,2); //Задаем адрес и размерность дисплея. byte degree[8] = { //рисуем пользовательский символ градуса B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000, }; void setup() //************************************************ { lcd.init(); //инициализация lcd lcd.backlight(); //включаем подсветку if (!SD.begin(chipSelect)) { lcd.setCursor(0,0); //ставим курсор в начало первой строки lcd.print("SD-card Error!!!"); lcd.setCursor(0,1); //ставим курсор в начало второй строки lcd.print("Check Connection"); return; } analogReference(INTERNAL); //устанавливаем внутреннее опорное напряжение 1,1 В pinMode (Pt100Pin, INPUT); //устанавливаем режимы входов платы (ввод или вывод) pinMode (BtSettingPin, INPUT); pinMode (BtDownPin, INPUT); pinMode (BtUpPin, INPUT); watch.begin(); //Инициируем работу с модулем RTC. lcd.init(); //инициализация lcd lcd.backlight(); //включаем подсветку lcd.setCursor(0,0); //ставим курсор в начало первой строки lcd.print(" Data "); lcd.setCursor(0,1); //ставим курсор в начало второй строки lcd.print(" Logger "); delay(2000); lcd.createChar(0, degree); //прописываем пользовательский символ градуса } void loop() //*************************************************** { //цикл замера температуры if(millis() - currentTime > intervalTime) //опрос датчика через intervalTime { currentTime = millis(); //считываем значение с датчика и усредняем Pt100_Val = expRunningAverage(analogRead (Pt100Pin)); //переводим в температуру Pt100_Temp = ((Pt100_Val - Min_Volt) * (Max_Temp - Min_Temp) / (Max_Volt - Min_Volt)) + Min_Temp; //добавляем поправку Pt100_Temp_Correction = Pt100_Temp + TEMP_CORRECT; //------------------------- Вывод на экран ------------------------------ if(FlagSettingIn == false) //если мы не в настройках { lcd.setCursor(0,0); lcd.print(watch.gettime("H:i:s")); lcd.print(" "); lcd.print(Pt100_Temp_Correction,1); lcd.write((byte)0); //рисуем пользовательский символ градуса lcd.print("C"); //если мы ввели корректировку показания датчика, //за символом градуса будет выводиться восклицательный знак if (TEMP_CORRECT != 0) { lcd.print("! "); } else { lcd.print(" "); } lcd.setCursor(0,1); lcd.print(" "); lcd.print(watch.gettime("d.m.y D")); if (FlagRecordOn == true) //если идет запись { lcd.print(" R "); } else { lcd.print(" "); } } } //if(millis() - currentTime > intervalTime) //------------------- цикл записи данных ----------------------------- if (FlagRecordOn == true) { if(millis() - currentRecTime > intervalRecTime) //запись данных через intervalRecTime { currentRecTime = millis(); //открываем файл, если его нет, то создаем. Открыть можно только один файл, //поэтому, если открыт другой, закрой его myFile = SD.open("temper.txt", FILE_WRITE); //если файл открылся норамально, то пишем в него if (myFile) { myFile.print(watch.gettime("d.m.y H:i:s ")); myFile.print(Pt100_Temp_Correction); if (TEMP_CORRECT != 0) { myFile.println("ºC !"); } else { myFile.println("ºC"); } myFile.close(); //после записи закрываем } else { //если ошибка с файлом lcd.setCursor(0,0); lcd.print("RECORD ERROR !!! "); delay(2000); } } //if(millis() - currentRecTime > intervalRecTime) } //нажата кнопка ВВЕРХ в режиме показа времени - начало записи if(digitalRead(BtUpPin) == HIGH && FlagSettingIn == false && FlagRecordOn == false) { FlagRecordOn = true; myFile = SD.open("temper.txt", FILE_WRITE); if (myFile) { myFile.print("Start Record "); myFile.println(watch.gettime("d.m.y H:i:s")); myFile.close(); lcd.setCursor(0,0); lcd.print(" START RECORD "); delay(1000); } else { lcd.setCursor(0,0); lcd.print("RECORD ERROR !!! "); delay(2000); } } //нажата кнопка ВНИЗ в режиме показа времени - остановка записи if(digitalRead(BtDownPin) == HIGH && FlagSettingIn == false && FlagRecordOn == true) { FlagRecordOn = false; myFile = SD.open("temper.txt", FILE_WRITE); if (myFile) { myFile.print("Stop Record "); myFile.println(watch.gettime("d.m.y H:i:s")); lcd.setCursor(0,0); lcd.print(" STOP RECORD "); delay(1000); } else { lcd.setCursor(0,0); lcd.print("RECORD ERROR !!! "); delay(2000); } myFile.close(); } //********************* Нажата кнопка Настройки ************************ //при каждом нажатии кнопки Настройки //перескакиваем на следующий параметр в очереди. В конце выходим из настроек //считываем состояние кнопки с устраненным дребезгом CurrentButtonSetting = debounce(LastButtonSetting, BtSettingPin); if(CurrentButtonSetting == HIGH) //кнопка нажата { if (FlagSettingLoop == true) //эта переменная нужна для однократного нажатия кнопки, //даже если ее удерживать { FlagSettingLoop = false; FlagSettingIn = true; SettingNumber = SettingNumber + 1; if (SettingNumber > 2) { SettingNumber = 0; FlagSettingIn = false; //вышли из режима настройки } } } else //кнопка отжата { FlagSettingLoop = true; } //обновляем текущее значение кнопки LastButtonSetting = CurrentButtonSetting; if (FlagSettingIn == true) //если мы в настройках { //******************* Нажата кнопка Увеличить ************************** ButtonPlus(); //функция реакции на нажатие кнопки Увеличить //******************* Нажата кнопка Уменьшить ************************** ButtonMinus(); //функция реакции на нажатие кнопки Уменьшить SettingsOnLCD(SettingNumber); //отображаем строку с настройками } } //void loop() //********************** ФУНКЦИИ ********************************* //*********** Функция отображения настроек на LCD ************* void SettingsOnLCD(int SettingItem) { lcd.setCursor(0,0); switch (SettingItem) { case 1: //интервал записи lcd.print("Time Record "); lcd.setCursor(0,1); lcd.print(intervalRecTime/1000); lcd.print(" sec "); break; case 2: //поправки температуры датчика lcd.print("Temp Correction "); lcd.setCursor(0,1); lcd.print(TEMP_CORRECT); lcd.write((byte)0); lcd.print("C "); break; } } //********* Функция кнопки Больше, Увеличить, Плюс и т.п. ************* void ButtonPlus() { CurrentButtonUp = debounce(LastButtonUp, BtUpPin); if(CurrentButtonUp == HIGH) //кнопка нажата { if(FlagUpHold == true) //отсечка времени удержания кнопки { ButtonHoldTimerUp = millis(); FlagUpHold = false; } if(millis() - ButtonHoldTimerUp > ButtonHoldIncTime) //сначала однократное нажатие, но если держать кнопку более //ButtonHoldIncTime, она переходит в режим удержания { ButtonPlusAction(SettingNumber, true); } if(FlagUpLoop == true) //однократное нажатие, даже если удерживать кнопку, //пока не прошло ButtonHoldIncTime { FlagUpLoop = false; ButtonPlusAction(SettingNumber, false); } } else //кнопка отжата { FlagUpLoop = true; FlagUpHold = true; } LastButtonUp = CurrentButtonUp; //обновляем текущее значение кнопки } //******** Функция изменения параметров при нажатии кнопки Больше ********** void ButtonPlusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //интервал записи intervalRecTime = intervalRecTime + Inc_Val_Calc(IncTrigger,3); break; case 2: //настройка поправки температуры датчика TEMP_CORRECT = TEMP_CORRECT + Inc_Val_Calc(IncTrigger,1); if(TEMP_CORRECT > TEMP_CORRECT_MAX) //верхний порог { TEMP_CORRECT = TEMP_CORRECT_MAX; } break; } } //********* Функция кнопки Меньше, Уменьшить, Минус или т.п. ************* void ButtonMinus() { CurrentButtonDown = debounce(LastButtonDown, BtDownPin); if(CurrentButtonDown == HIGH) //кнопка нажата { if(FlagDownHold == true) //отсечка времени удержания кнопки { ButtonHoldTimerDown = millis(); FlagDownHold = false; } if(millis() - ButtonHoldTimerDown > ButtonHoldIncTime) //сначала однократное нажатие, но если держать кнопку более //ButtonHoldIncTime, она переходит в режим удержания { ButtonMinusAction(SettingNumber, true); } if(FlagDownLoop == true) //однократное нажатие, даже если удерживать кнопку, //пока не прошло ButtonHoldIncTime { FlagDownLoop = false; ButtonMinusAction(SettingNumber, false); } } else //кнопка отжата { FlagDownLoop = true; FlagDownHold = true; } LastButtonDown = CurrentButtonDown; //обновляем текущее значение кнопки } //******** Функция изменения параметров при нажатии кнопки Меньше ********** void ButtonMinusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //интервал записи intervalRecTime = intervalRecTime - Inc_Val_Calc(IncTrigger,3); if(intervalRecTime < 1000) //нижний порог { intervalRecTime = 1000; } break; case 2: //настройка поправки температуры датчика TEMP_CORRECT = TEMP_CORRECT - Inc_Val_Calc(IncTrigger,1); if(TEMP_CORRECT < TEMP_CORRECT_MIN) //нижний порог { TEMP_CORRECT = TEMP_CORRECT_MIN; } break; } } //*************** Функция устранения дребезга контактов *********** boolean debounce(boolean last, int ButtonPinDebounce) { boolean current = digitalRead(ButtonPinDebounce); //считываем текущее состояние кнопки if(last != current) //если оно иное, чем предыдущее... { delay(50); //ждем 50 мс current = digitalRead(ButtonPinDebounce); //считываем состояние снова } return current; //возвращаем текущее состояние кнопки } //************* функция усреднения в точке замера *************** float expRunningAverage(float newVal) { static float filVal = 0; float k; // резкость фильтра зависит от процента отклонения от предыдущего значения if (abs((newVal - filVal)/filVal) > 0.05) k = 0.9; else k = 0.1; filVal += (newVal - filVal) * k; return filVal; } //*************** Функция разгона приращения параметра ************ float Inc_Val_Calc(boolean IncTrigger, int IncVariant) { static int count_inc = 0; //счетчик итераций //IncVariant - разные варианты разгона if (IncTrigger == true) //пришел сигнал на разгон { count_inc = count_inc + 1; switch (IncVariant) { case 1: //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 10) { return 1; } if (count_inc > 10 && count_inc <= 20) { return 2; } if (count_inc > 20) { return 3; } break; case 3: //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 30) { return 1000; } if (count_inc > 30 && count_inc <= 60) { return 2000; } if (count_inc > 60 && count_inc <= 90) { return 5000; } if (count_inc > 90) { return 10000; } break; } } else { count_inc = 0; switch (IncVariant) { case 1: return 1; //приращение по умолчанию break; case 3: return 1000; //приращение по умолчанию break; } } }