//Скетч для калибровки термопреобразователя термопары типа К //Обратите внимание, если вы захотите подать на LM358 двухполярное питание, //то его отрицательную часть можно сформировать с помощью ШИМ-сигнала от //Arduino и специального модуля. Для его работы в скетче есть несколько //закомментированных строк. //опорное напряжение 1,1 В #include <EEPROM.h> //библиотека работы с памятью #include <LiquidCrystal_I2C.h> //библиотеки работы с LCD через I2C #include <Wire.h> #include <math.h> //нужна для вычисления логарифма //пин ШИМ-сигнала для формирования отрицательного напряжения //const int pwmPinNegative = 3; //Не забудь шунтировать выход от кнопок резистором 10 КОм const int BtDownPin = 5; //пин кнопки уменьшить. const int BtUpPin = 6; //пин кнопки увеличить const int BtSettingPin = 7; //пин кнопки настроек const byte SensorPin = A0; //аналоговый пин термодатчика типа К //аналоговый пин датчика температуры холодного спая М52 const byte ColdJunctionPin = A1; //пины модуля I2C LCD-дисплея : //SDA - A4 //SCL - A5 //питание от шилда int Init_Eeprom_Addr = 1000; //номер ячейки в памяти EEPROM (0-1023), куда мы запишем ключ о //самом первом запуске программы //это нужно, так как при первом запуске в памяти нет наших настроек //и нужно загрузить значения по умолчанию byte Init_Key = 241; //это ключ (0-255). //Если при пуске скетча данные из ячейки Init_Eeprom не совпадают //с Init_Key, вводим в настройки значения по умолчанию, //иначе берем настройки из памяти int SettingNumber = 0; //пункт меню настройки boolean LastButtonSetting = LOW; //предыдущее состояние кнопки Настройки boolean CurrentButtonSetting = LOW; //текущее состояние кнопки Настройки boolean LastButtonDown = LOW; // --- Меньше boolean CurrentButtonDown = LOW; // --- Меньше boolean LastButtonUp = LOW; // --- Больше boolean CurrentButtonUp = LOW; // --- Больше boolean FlagSettingLoop = true; //были ли мы в цикле нажатия кнопки Настройки boolean FlagUpLoop = true; //были ли мы в цикле нажатия кнопки Больше boolean FlagDownLoop = true; //были ли мы в цикле нажатия кнопки Меньше //нужна при нажатии и удерживании кнопки ВВЕРХ unsigned long ButtonHoldTimerUp = 0; //нужна при нажатии и удерживании кнопки ВНИЗ unsigned long ButtonHoldTimerDown = 0; //по прошествии этого времени кнопка ВВЕРХ или ВНИЗ //перейдет в режим удержания, мс const long ButtonHoldIncTime = 500; boolean FlagUpHold = true; //были ли мы в цикле удержания кнопки ВВЕРХ boolean FlagDownHold = true; //были ли мы в цикле удержания кнопки ВНИЗ unsigned long currentTime = 0; //нужна, чтобы убрать delay() const long intervalTime = 1000; //интервал опроса датчика в мс float Sensor_Val = 0 ; //значение с термодатчика (0...1023) //значение с термодатчика, преобразованное в температуру float Sensor_Temperature = 0; //значение с термодатчика холодного спая (0...1023) float ColdJunction_Val = 0; //значение с термодатчика холодного спая, преобразованное в температуру float ColdJunction_Temperature = 0; //нижняя граница вольтажа с аналогового порта переведенная в цифровой вид float Min_Volt = 151; //соответствующая нижней границе температура минус температура холодного спая float Min_Temp = 0; //верхняя граница вольтажа с аналогового порта переведенная в цифровой вид float Max_Volt = 464; //соответствующая верхней границе температура минус температура холодного спая float Max_Temp = 72.4; //номинальная температура в градусах термистора MF52 (спецификация) float MF52_To = 25; //сопротивление при номинальной температуре в Ом термистора MF52 (спецификация) float MF52_Ro = 50000; //коэф В термистора MF52 (для 50К может быть 4050 и 4150) (спецификация) float MF52_B = 3950; //сопротивление в Ом делителя напряжения (замерь перед пайкой, бери близкое к Ro) float MF52_Rdivider = 54000; float MF52_Rt = 50000; //текущее сопротивление в Ом термистора MF52 float MF52_T = 25; //текущая температура вокруг термистора MF52 (холодного спая) //конечная расчетная температура с компенсацией температуры холодного спая float Temperature_Final = 25; boolean FlagSettingIn = false; //находимся ли в настройках //скважность сигнала для источника отрицательного напряжения //int pwmValue = 127; //счетчик циклов ожидания запуска программы. //Нужно при запуске для стабилизации температуры и выхода пока идет усреднение int Start_wait = 1; LiquidCrystal_I2C lcd(0x27,16,2); //Задаем адрес и размерность дисплея. byte degree[8] = { //рисуем пользовательский символ градуса B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000, }; void setup() //***************************************************************** { analogReference(INTERNAL); //устанавливаем внутреннее опорное напряжение 1,1В pinMode (BtSettingPin, INPUT); //прописываем режим пинов pinMode (BtDownPin, INPUT); pinMode (BtUpPin, INPUT); pinMode(SensorPin, INPUT); pinMode(ColdJunctionPin, INPUT); //пин ШИМ-сигнала для формирования отрицательного напряжения //pinMode (pwmPinNegative, OUTPUT); //этот ШИМ-сигнал пойдет на модуль формирования отрицательного напряжения //analogWrite(pwmPinNegative, pwmValue); //если запись в настройки когда-то была, то читаем их if (EEPROM.read(Init_Eeprom_Addr) == Init_Key) { EEPROM.get(0,Min_Volt); EEPROM.get(4,Max_Volt); EEPROM.get(8,Min_Temp); EEPROM.get(12,Max_Temp); EEPROM.get(16,MF52_To); EEPROM.get(20,MF52_Ro); EEPROM.get(24,MF52_B); EEPROM.get(28,MF52_Rdivider); } else //если записи не было, записываем в память значения по умолчанию { EEPROM.put(0,Min_Volt); EEPROM.put(4,Max_Volt); EEPROM.put(8,Min_Temp); EEPROM.put(12,Max_Temp); EEPROM.put(16,MF52_To); EEPROM.put(20,MF52_Ro); EEPROM.put(24,MF52_B); EEPROM.put(28,MF52_Rdivider); //и ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); } lcd.init(); //Инициализация lcd lcd.backlight(); //Включаем подсветку lcd.createChar(0, degree); //прописываем пользовательский символ градуса lcd.setCursor(0,0); //рисуем первую строчку lcd.print(" T"); lcd.write((byte)0); lcd.print("C Setting "); lcd.setCursor(0,1); lcd.print(" Please Wait "); delay(2000); } void loop() //************************************************************** { // следующие действия выполняем только если прошло intervalTime if(millis() - currentTime > intervalTime) { currentTime = millis(); //считываем значение с аналогового пина и усредняем Sensor_Val = AveragingFunction(analogRead (SensorPin)); //переводим в температуру Sensor_Temperature = ((Sensor_Val - Min_Volt) * (Max_Temp - Min_Temp) / (Max_Volt - Min_Volt)) + Min_Temp; //считываем значение с аналогового пина и усредняем ColdJunction_Val = AveragingFunction2(analogRead (ColdJunctionPin)); //вычисляем сопротивление термистора MF52_Rt = MF52_Rdivider / (1024/ColdJunction_Val-1); //расчетная темпаратура в град Цельсия MF52_T = 1/(1/(MF52_To + 273.15) + logf(MF52_Rt/MF52_Ro) / MF52_B) - 273.15; Temperature_Final = Sensor_Temperature + MF52_T; //конечная температура Start_wait = Start_wait + 1; //это нужно, чтобы усреднение температуры прошло до начала работы системы if(Start_wait>4) { Start_wait = 10; //10 от балды, лишь бы была больше 4 lcd.setCursor(0,0); lcd.print("X="); //температура с датчика без компенсации lcd.print(Sensor_Temperature, 1); lcd.print(" ("); //напряжение с датчика в цифровом виде (0...1023) без компенсации lcd.print(Sensor_Val, 0); lcd.print(") "); if(FlagSettingIn == false) //если мы не в настройках { lcd.setCursor(0,1); lcd.print("T="); lcd.print(Temperature_Final, 1); //температура с датчика с компенсацией lcd.print(" ("); lcd.print(MF52_T, 1); //температура холодного спая lcd.print(") "); } } //if(Start_wait>4) } //if(millis() - currentTime > intervalTime) //************ Нажата кнопка Настройки ************************************ //при каждом нажатии кнопки перескакиваем на следующий параметр в очереди. //Дойдя до конца меню, выходим из настроек //считываем состояние кнопки с устраненным дребезгом CurrentButtonSetting = debounce(LastButtonSetting, BtSettingPin); if(CurrentButtonSetting == HIGH) //если кнопка нажата { //эта переменная нужна для однократного нажатия кнопки, даже если ее удерживать if (FlagSettingLoop == true) { FlagSettingLoop = false; FlagSettingIn = true; //вошли в режим настройки SettingNumber = SettingNumber + 1; if (SettingNumber > 11) { SettingNumber = 0; FlagSettingIn = false; //вышли из режима настройки } } } else { FlagSettingLoop = true; } LastButtonSetting = CurrentButtonSetting; //обновляем текущее значение кнопки if (FlagSettingIn == true) //если мы в настройках { //******************* Нажата кнопка Увеличить ************************** CurrentButtonUp = debounce(LastButtonUp, BtUpPin); ButtonPlus(CurrentButtonUp); //функция реакции на нажатие кнопки Увеличить LastButtonUp = CurrentButtonUp; //обновляем текущее значение кнопки //******************* Нажата кнопка Уменьшить ************************** CurrentButtonDown = debounce(LastButtonDown, BtDownPin); ButtonMinus(CurrentButtonDown); //функция реакции на нажатие кнопки Уменьшить LastButtonDown = CurrentButtonDown; //обновляем текущее значение кнопки SettingsOnLCD(SettingNumber); //отображаем строку с настройками } } //void loop() //********************** ФУНКЦИИ ********************************* //***************** функция отображения настроек на LCD ****************** void SettingsOnLCD(int SettingItem) { lcd.setCursor(0,1); switch (SettingItem) { //нижняя граница вольтажа с аналогового порта переведенная в цифровой вид case 1: lcd.print("MinVolt="); lcd.print(Min_Volt, 0); lcd.print(" "); break; //соответствующая нижней границе температура минус температура холодного спая case 2: lcd.print("MinTemp="); lcd.print(Min_Temp, 1); lcd.write((byte)0); lcd.print("C "); break; //верхняя граница вольтажа с аналогового порта переведенная в цифровой вид case 3: lcd.print("MaxVolt="); lcd.print(Max_Volt, 0); lcd.print(" "); break; //соответствующая верхней границе температура минус температура холодного спая case 4: lcd.print("MaxTemp="); lcd.print(Max_Temp, 1); lcd.write((byte)0); lcd.print("C "); break; //номинальная температура в градусах термистора MF52 (спецификация) case 5: lcd.print("To="); lcd.print(MF52_To, 0); lcd.write((byte)0); lcd.print("C "); break; //сопротивление при номинальной температуре в Ом термистора MF52 (спецификация) case 6: lcd.print("Ro="); lcd.print(MF52_Ro, 0); lcd.print(" Om "); break; //коэф В термистора MF52 (для 50К может быть 4050 и 4150) (спецификация) case 7: lcd.print("B="); lcd.print(MF52_B, 0); lcd.print(" "); break; //сопротивление в Ом делителя напряжения (замерь перед пайкой, бери близкое к Ro) case 8: lcd.print("Rdiv="); lcd.print(MF52_Rdivider, 0); lcd.print(" Om "); break; case 9: //запись настроек в EEPROM lcd.print("SaveSet. (UP) "); break; case 10: //чтение настроек из EEPROM lcd.print("ReadSet. (UP) "); break; case 11: //загрузка значений по умолчанию lcd.print("Default (UP) "); break; } } //********* Функция кнопки Больше, Увеличить, Плюс и т.п. ************* void ButtonPlus(boolean BtState) //BtState - состояние кнопки (нажата-отжата) { if(BtState == HIGH) //кнопка нажата { if (SettingNumber >= 1 && SettingNumber <= 8 ) //пункты настроек, где нужен разгон параметра в режиме удержания { 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 //пункты настроек, где нужно только однократное нажатие кнопки { if(FlagUpLoop == true) { FlagUpLoop = false; ButtonPlusAction(SettingNumber, false); } } } else //кнопка отжата { FlagUpLoop = true; FlagUpHold = true; } } //******** Функция изменения параметров при нажатии кнопки Больше ********** void ButtonPlusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { //нижняя граница вольтажа с аналогового порта переведенный в цифровой вид case 1: Min_Volt = Min_Volt + Inc_Val_Calc(IncTrigger, 1); if(Min_Volt > 1023) //не даем подняться выше 1023 { Min_Volt = 1023; } break; //соответствующая нижней границе температура минус температура холодного спая case 2: Min_Temp = Min_Temp + Inc_Val_Calc(IncTrigger, 2); break; //верхняя граница вольтажа с аналогового порта переведенный в цифровой вид case 3: Max_Volt = Max_Volt + Inc_Val_Calc(IncTrigger, 1); if(Max_Volt > 1023) { Max_Volt = 1023; } break; //соответствующая верхней границе температура минус температура холодного спая case 4: Max_Temp = Max_Temp + Inc_Val_Calc(IncTrigger, 2); break; //номинальная температура в градусах термистора MF52 (спецификация) case 5: MF52_To = MF52_To + Inc_Val_Calc(IncTrigger, 1); break; //сопротивление при номинальной температуре в Ом термистора MF52 (спецификация) case 6: MF52_Ro = MF52_Ro + Inc_Val_Calc(IncTrigger, 1); break; //коэф В термистора MF52 (для 50К может быть 4050 и 4150) (спецификация) case 7: MF52_B = MF52_B + Inc_Val_Calc(IncTrigger, 1); break; //сопротивление в Ом делителя напряжения (замерь перед пайкой, бери близкое к Ro) case 8: MF52_Rdivider = MF52_Rdivider + Inc_Val_Calc(IncTrigger, 1); break; case 9: //запись настроек в EEPROM EEPROM.put(0,Min_Volt); EEPROM.put(4,Max_Volt); EEPROM.put(8,Min_Temp); EEPROM.put(12,Max_Temp); EEPROM.put(16,MF52_To); EEPROM.put(20,MF52_Ro); EEPROM.put(24,MF52_B); EEPROM.put(28,MF52_Rdivider); //ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); lcd.setCursor(0,1); lcd.print("Saving... OK "); delay(2000); break; case 10: //чтение настроек из EEPROM EEPROM.get(0,Min_Volt); EEPROM.get(4,Max_Volt); EEPROM.get(8,Min_Temp); EEPROM.get(12,Max_Temp); EEPROM.get(16,MF52_To); EEPROM.get(20,MF52_Ro); EEPROM.get(24,MF52_B); EEPROM.get(28,MF52_Rdivider); lcd.setCursor(0,1); lcd.print("Reading... OK "); delay(2000); break; case 11: //загрузка значений по умолчанию. Берем из начала скетча Min_Volt = 151; Max_Volt = 464; Min_Temp = 0; Max_Temp = 72.4; MF52_To = 25; MF52_Ro = 50000; MF52_B = 3950; MF52_Rdivider = 54000; lcd.setCursor(0,1); lcd.print("Loading... OK "); delay(2000); break; } } //********* Функция кнопки Меньше, Уменьшить, Минус или т.п. ************* void ButtonMinus(boolean BtState) //BtState - состояние кнопки (нажата-отжата) { if(BtState == HIGH) //кнопка нажата { if (SettingNumber >= 1 && SettingNumber <= 8 ) //пункты настроек, где нужен режим удержания кнопки //для ускоренного изменения параметра { 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 //пункты настроек, где нужно только однократное нажатие кнопки { if(FlagDownLoop == true) { FlagDownLoop = false; ButtonMinusAction(SettingNumber, false); } } } else //кнопка отжата { FlagDownLoop = true; FlagDownHold = true; } } //******** Функция изменения параметров при нажатии кнопки Меньше ********** void ButtonMinusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { //нижняя граница вольтажа с аналогового порта переведенный в цифровой вид case 1: Min_Volt = Min_Volt - Inc_Val_Calc(IncTrigger, 1); if(Min_Volt < 0) //не даем опуститься ниже 0 { Min_Volt = 0; } break; //соответствующая нижней границе температура минус температура холодного спая case 2: Min_Temp = Min_Temp - Inc_Val_Calc(IncTrigger, 2); break; //верхняя граница вольтажа с аналогового порта переведенный в цифровой вид case 3: Max_Volt = Max_Volt - Inc_Val_Calc(IncTrigger, 1); if(Max_Volt < 0) { Max_Volt = 0; } break; //соответствующая верхней границе температура минус температура холодного спая case 4: Max_Temp = Max_Temp - Inc_Val_Calc(IncTrigger, 2); break; //номинальная температура в градусах термистора MF52 (спецификация) case 5: MF52_To = MF52_To - Inc_Val_Calc(IncTrigger, 1); break; //сопротивление при номинальной температуре в Ом термистора MF52 (спецификация) case 6: MF52_Ro = MF52_Ro - Inc_Val_Calc(IncTrigger, 1); if(MF52_Ro < 0) { MF52_Ro = 0; } break; //коэф В термистора MF52 (для 50К может быть 4050 и 4150) (спецификация) case 7: MF52_B = MF52_B - Inc_Val_Calc(IncTrigger, 1); if(MF52_B < 0) { MF52_B = 0; } break; //сопротивление в Ом делителя напряжения (замерь перед пайкой, бери близкое к Ro) case 8: MF52_Rdivider = MF52_Rdivider - Inc_Val_Calc(IncTrigger, 1); if(MF52_Rdivider < 0) { MF52_Rdivider = 0; } break; } } //*************** Функция устранения дребезга контактов *********** boolean debounce(boolean last, int ButtonPinDebounce) { boolean current = digitalRead(ButtonPinDebounce); //считываем текущее состояние кнопки if(last != current) //если оно иное, чем предыдущее... { delay(50); //ждем 50 мс current = digitalRead(ButtonPinDebounce); //считываем состояние снова } return current; //возвращаем текущее состояние кнопки } //****************** функция усреднения в точке замера *********************** float AveragingFunction(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 AveragingFunction2(float newVal) { static float filVal2 = 0; float k; // резкость фильтра зависит от процента отклонения от предыдущего значения if (abs((newVal - filVal2)/filVal2) > 0.05) k = 0.9; else k = 0.1; filVal2 += (newVal - filVal2) * k; return filVal2; } //*************** Функция разгона приращения параметра ************ float Inc_Val_Calc(boolean IncTrigger, int IncVariant) { static int count_inc = 0; //счетчик итераций //IncVariant - разные варианты разгона if (IncTrigger == true) //пришел сигнал на разгон { count_inc = count_inc + 1; if (IncVariant == 1) { //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 30) { return 1; } if (count_inc > 30 && count_inc <= 60) { return 2; } if (count_inc > 60 && count_inc <= 90) { return 10; } if (count_inc > 90) { return 100; } } if (IncVariant == 2) { //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 30) { return 0.1; } if (count_inc > 30 && count_inc <= 60) { return 1; } if (count_inc > 60 && count_inc <= 90) { return 2; } if (count_inc > 90) { return 10; } } } else { count_inc = 0; if (IncVariant == 1) { return 1; //приращение по умолчанию } if (IncVariant == 2) { return 0.1; //приращение по умолчанию } } }