//регулировка температуры от термодатчика Pt100 с помощью PID-регулятора //настройка параметров //эти настройки можно записать в энергонезависимую память //на дисплей выводятся следующие символы : // ! - введена поправка температуры //А - сработала сигнализация // (отображается даже если сигнализация на внешнее реле отключена) //U - клапан дошел до верхнего конечника //D - клапан дошел до верхнего конечника //стрелками показано направление движения клапана //привод клапана шаговым двигателем 28BYJ-48 на драйвере ULN2003 //Внимание! На клапане, которым управляет этот регулятор, //должны стоять конечные выключатели. //здесь они подключены к пинам LimitSwitchUp и LimitSwitchDown #include <EEPROM.h> //библиотека работы с памятью #include <LiquidCrystal_I2C.h> //библиотеки работы с LCD через I2C #include <Wire.h> //Не забудь шунтировать выход от кнопок и конечников резистором 10 КОм const byte LimitSwitchUp = 13; //пин конечника движения клапана вверх const byte LimitSwitchDown = 2; //пин конечника движения клапана вниз const byte MotorPin1 = 3; //пин подключения драйвера ULN2003 (пин IN1) const byte MotorPin2 = 4; // ---- (пин IN2) const byte MotorPin3 = 5; // ---- (пин IN3) const byte MotorPin4 = 6; // ---- (пин IN4) const byte BtSettingPin = 7; //пин кнопки настроек. const byte BtDownPin = 8; //пин кнопки Уменьшить const byte BtUpPin = 9; //пин кнопки Увеличить const byte BtExitPin = 10; //пин кнопки Выход const byte BtManualPin = 11; //пин кнопки Ручной режим const byte AlarmPin = 12; //пин реле сигнализации const byte Pt100Pin = A0; //аналоговый пин термодатчика Pt100 //питание шагового двигателя (драйвера) только от шилда (1А) //пины модуля I2C LCD-дисплея : //SDA - A4 //SCL - A5 //питание от шилда int Init_Eeprom_Addr = 1000; //номер ячейки в памяти EEPROM (0-1023), куда мы запишем ключ о //самом первом запуске программы //это нужно, так как при первом запуске в памяти нет наших настроек //и нужно загрузить значения по умолчанию byte Init_Key = 249; //это ключ (0-255). //Если при пуске скетча данные из ячейки Init_Eeprom не совпадают //с Init_Key, вводим в настройки значения по умолчанию, //иначе берем настройки из памяти boolean LastButtonSetting = LOW; //предыдущее состояние кнопки boolean CurrentButtonSetting = LOW; //текущее состояние кнопки boolean LastButtonDown = LOW; boolean CurrentButtonDown = LOW; boolean LastButtonUp = LOW; boolean CurrentButtonUp = LOW; boolean LastButtonExit = LOW; boolean CurrentButtonExit = LOW; boolean LastButtonManual = LOW; boolean CurrentButtonManual = LOW; boolean FlagSettingLoop = true; //были ли мы в цикле нажатия кнопки Настройки boolean FlagDownLoop = true; //были ли мы в цикле нажатия кнопки Меньше boolean FlagUpLoop = true; //были ли мы в цикле нажатия кнопки Больше boolean FlagExitLoop = true; //были ли мы в цикле нажатия кнопки Exit boolean FlagManualLoop = 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() в замерах температуры int INTERVAL_TIME = 2000; //интервал опроса датчика в мс const int INTERVAL_TIME_MAX = 10000; //верхний порог const int INTERVAL_TIME_MIN = 100; //нижний порог int VALVE_TIME = 200; //время открытия клапана в сек. от 0 до полного //замеряем секундомером или берем из мануала const int VALVE_TIME_MAX = 6000; //верхний порог const int VALVE_TIME_MIN = 1; //нижний порог int TOTAL_SPAN = 55; //диапазон регулирования (total span) в град., //т.е. максимальное изменение температуры от закрытого клапана до //полностью открытого. Замеряем или задаем на глаз. const int TOTAL_SPAN_MAX = 1000; //верхний порог const int TOTAL_SPAN_MIN = 1; //нижний порог int P_FACTOR = 100; //относительный диапазон (proportional band в %) const int P_FACTOR_MAX = 10000; //верхний порог const int P_FACTOR_MIN = 0; //нижний порог int I_FACTOR = 240; //время интегрирования (integral time) в сек. const int I_FACTOR_MAX = 10000; //верхний порог const int I_FACTOR_MIN = 1; //нижний порог int D_FACTOR = 60; //время дифференцирования (derivative time) в сек. //(обычно D_FACTOR=I_FACTOR/4...5) const int D_FACTOR_MAX = 10000; //верхний порог const int D_FACTOR_MIN = 1; //нижний порог float P_part = 0; //пропорциональная составляющая выходного сигнала в % float I_part = 0; //интегральная составляющая выходного сигнала в % float D_part = 0; //дифференциальная составляющая выходного сигнала в % float PID_output = 0; //выход с регулятора в % от полного времени //открытия клапана VALVE_TIME float PID_output_old = 0; //выход с регулятора старый float PID_error = 0; //ошибка регулятора (PV-SV) в % от диапазона //регулирования (TOTAL_SPAN) float PID_error_old = 0; //предыдущая ошибка регулятора int PID_SETPOINT = 24; //заданная температура в град. const int PID_SETPOINT_MAX = 1000; //верхний порог const int PID_SETPOINT_MIN = 0; //нижний порог int ALARM_MIN=20; //сигнализация по низкой температуре const int ALARM_MIN_MAX = 1000; //верхний порог const int ALARM_MIN_MIN = 1; //нижний порог int ALARM_MAX=30; //сигнализация по высокой температуре const int ALARM_MAX_MAX = 1000; //верхний порог const int ALARM_MAX_MIN = 1; //нижний порог int ALARM_DIFF=1; //диффиренциал сигнализации const int ALARM_DIFF_MAX = 100; //верхний порог const int ALARM_DIFF_MIN = 0; //нижний порог int TEMP_CORRECT=0; //поправка температуры датчика. const int TEMP_CORRECT_MAX = 100; //верхний порог const int TEMP_CORRECT_MIN = -100; //нижний порог boolean AlarmControlOn = true; //включена или нет сигнализация по температуре boolean ManualModeOn = false; //включен или нет ручной режим boolean FlagSettingIn = false; //находимся ли в настройках int SettingNumber = 0; //пункт меню настройки //счетчик циклов ожидания запуска программы. //Нужно при запуске для стабилизации температуры и выхода пока идет усреднение int Start_wait = 1; //номер информации для вывода во второй строке по кнопке Exit int LCD_second_string = 1; //количество шагов на оборот выходного вала мотор-редуктора. Здесь для 28BYJ-48-5V //64 х 63,68395, где 64 - число шагов самого электродвигателя в полушаговом режиме, //а 63,68395 - понижающий коэффициент редуктора (часто в интернете округляют до 64) int StepsInMotor = 4075; float time_valve_move = 0; //время открытия/закрытия клапана в мс boolean FlagBrake = false; //оставить ли мотор под током после остановки boolean Direction = true; //направление вращения : true - по часовой, //false - против часовой int Steps = 0; //один из 8 шагов обмоток электромотора unsigned long currentTimeMotor; //чтобы убрать delay в движении мотора //---------------- Уставки скорости ----------------------- //время одного шага мотора в микросекундах. //На самом деле так задается скорость мотора unsigned long TimeForStep = 10000; //уставка скорости выходного вала мотор-редуктора в об/мин //она будет в настройках для удобства, но на самом деле //она нужна для расчета TimeForStep = 60000000 / (SpeedRevMin * StepsInMotor) //подберите эту скорость исходя из допустимых пределов //крутящего момента, скорости вращения и нагрева мотора (см. спецификацию) float SpeedRevMin = 1.4; LiquidCrystal_I2C lcd(0x27,16,2); //Задаем адрес и размерность дисплея. byte degree[8] = { //рисуем пользовательский символ градуса B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000, }; byte arrow_up[8] = { //рисуем пользовательский символ стрелки вверх B00100, B01110, B10101, B00100, B00100, B00100, B00100, B00100, }; byte arrow_down[8] = { //рисуем пользовательский символ стрелки вниз B00100, B00100, B00100, B00100, B00100, B10101, B01110, B00100, }; void setup() //************************************************ { analogReference(INTERNAL); //устанавливаем внутреннее опорное напряжение 1,1 В pinMode (Pt100Pin, INPUT); //устанавливаем режимы входов платы (ввод или вывод) pinMode (AlarmPin, OUTPUT); pinMode (BtSettingPin, INPUT); pinMode (BtDownPin, INPUT); pinMode (BtUpPin, INPUT); pinMode (BtExitPin, INPUT); pinMode (BtManualPin, INPUT); pinMode (MotorPin1, OUTPUT); pinMode (MotorPin2, OUTPUT); pinMode (MotorPin3, OUTPUT); pinMode (MotorPin4, OUTPUT); pinMode (LimitSwitchUp, INPUT); pinMode (LimitSwitchDown, INPUT); //если запись в настройки когда-то была, то читаем их if (EEPROM.read(Init_Eeprom_Addr) == Init_Key) { EEPROM.get(0,TEMP_CORRECT); //для типа int нужны 2 ячейки памяти EEPROM.get(2,ALARM_MAX); EEPROM.get(4,ALARM_MIN); EEPROM.get(6,ALARM_DIFF); EEPROM.get(8,INTERVAL_TIME); EEPROM.get(10,VALVE_TIME); EEPROM.get(12,TOTAL_SPAN); EEPROM.get(14,P_FACTOR); EEPROM.get(16,I_FACTOR); EEPROM.get(18,D_FACTOR); EEPROM.get(20,PID_SETPOINT); EEPROM.get(24,Min_Volt); //для типа float нужны 4 ячейки памяти EEPROM.get(28,Min_Temp); EEPROM.get(32,Max_Volt); EEPROM.get(36,Max_Temp); EEPROM.get(40,StepsInMotor); EEPROM.get(42,SpeedRevMin); EEPROM.get(46,FlagBrake); } else //если записи не было, записываем в память значения по умолчанию { EEPROM.put(0,TEMP_CORRECT); //для типа int нужны 2 ячейки памяти EEPROM.put(2,ALARM_MAX); EEPROM.put(4,ALARM_MIN); EEPROM.put(6,ALARM_DIFF); EEPROM.put(8,INTERVAL_TIME); EEPROM.put(10,VALVE_TIME); EEPROM.put(12,TOTAL_SPAN); EEPROM.put(14,P_FACTOR); EEPROM.put(16,I_FACTOR); EEPROM.put(18,D_FACTOR); EEPROM.put(20,PID_SETPOINT); EEPROM.put(24,Min_Volt); //для типа float нужны 4 ячейки памяти EEPROM.put(28,Min_Temp); EEPROM.put(32,Max_Volt); EEPROM.put(36,Max_Temp); EEPROM.put(40,StepsInMotor); EEPROM.put(42,SpeedRevMin); EEPROM.put(46,FlagBrake); //и ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); } lcd.init(); //инициализация lcd lcd.backlight(); //включаем подсветку lcd.setCursor(0,0); //ставим курсор в начало первой строки lcd.print(" Temperature"); lcd.setCursor(0,1); //ставим курсор в начало второй строки lcd.print(" Monitor "); delay(1500); //пауза в мс lcd.createChar(0, degree); //прописываем пользовательский символ градуса lcd.createChar(1, arrow_up); //прописываем пользовательский символ стрелки вверх lcd.createChar(2, arrow_down); //прописываем пользовательский символ стрелки вниз lcd.setCursor(0,0); lcd.print(" Please Wait "); lcd.setCursor(0,1); lcd.print(" "); lcd.setCursor(0,1); lcd.print(" "); } void loop() //*************************************************** { //************************** Замеряем температуру ****************************** if(millis() - currentTime > INTERVAL_TIME) //опрос датчика через INTERVAL_TIME { 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(Start_wait < 4) { lcd.print(">>>"); //для красоты } //это нужно, чтобы стабилизация температуры прошла до начала работы системы Start_wait = Start_wait + 1; if(Start_wait > 4) { Start_wait = 10; //10 от балды, лиш бы была больше 4 //******** Расчет ошибки (отклонения от заданного) и выхода с регулятора *********** //ошибка в процентах от диапазона регулирования TOTAL_SPAN PID_error = 100*(PID_SETPOINT-Pt100_Temp_Correction)/TOTAL_SPAN; //считаем ПИД-составляющие в % от времени полного хода клапана VALVE_TIME P_part = PID_error*100/P_FACTOR; I_part = I_part + INTERVAL_TIME*(PID_error+PID_error_old)/(1000*2*I_FACTOR); //ограничиваем интегральную составляющую. Это может понадобиться, например, //при отключенной подаче теплоносителя, когда I_part будет бесконечно расти if(I_part > 150) { I_part = 150; } if(I_part < -150) { I_part = -150; } D_part = 1000*D_FACTOR*(PID_error-PID_error_old)/INTERVAL_TIME; //итоговая реакция регулятора с дополнительным усреднением //в % от времени полного хода клапана VALVE_TIME PID_output = P_part + I_part + D_part; //это классическая формула //это более сложная формула //PID_output = expRunningAverage2(P_part + 100*I_part/float(P_FACTOR) + 100*D_part/float(P_FACTOR)); PID_error_old = PID_error; } //if(Start_wait>4) } //if(millis() - currentTime > INTERVAL_TIME) //это нужно, чтобы стабилизация температуры прошла до начала работы регулятора if(Start_wait > 4) { //********** Включаем или выключаем реле сигнализации ********************** if (AlarmControlOn == true) //если сигнализация включена { if (Pt100_Temp_Correction<=ALARM_MIN || Pt100_Temp_Correction>=ALARM_MAX) { digitalWrite(AlarmPin, HIGH); } if (Pt100_Temp_Correction>ALARM_MIN+ALARM_DIFF && Pt100_Temp_Correction<ALARM_MAX-ALARM_DIFF) { digitalWrite(AlarmPin, LOW); } } else { digitalWrite(AlarmPin, LOW); //если сигнализация отключена } //**************** Рисуем вторую строку ************************* SecondStringDraw(); //****************** Вывод первой строки ****************************** //--------------- Текущая температура ---------------------- if(FlagSettingIn == false) //если мы не в настройках { lcd.setCursor(0,0); //прописываем первую строку lcd.print("PV="); //текущая температура lcd.print(Pt100_Temp_Correction,1); lcd.write((byte)0); //рисуем пользовательский символ градуса lcd.print("C "); //если мы ввели корректировку показания датчика, //за символом градуса будет выводиться восклицательный знак if (TEMP_CORRECT != 0) { lcd.print("!"); } } //---------------- Сигнализация на LCD --------------------- //если сработала сигнализация, выведется буква А (alarm) //даже, если сигнализация на реле отключена //на LCD сигналка выводится без диффиренциала if (Pt100_Temp_Correction<=ALARM_MIN || Pt100_Temp_Correction>=ALARM_MAX) { lcd.print("A"); } //---------------- отработка конечника --------------------- if (digitalRead(LimitSwitchDown) == HIGH) { lcd.print("D"); //DOWN - нижний конечник } if (digitalRead(LimitSwitchUp) == HIGH) { lcd.print("U"); //UP - нижний конечник } //************** Открытие/закрытие клапанов *************************** //****************** ручной режим ************************ if(ManualModeOn == true && FlagSettingIn == false) { //-------------------- закрываем клапан ------------------------ if(digitalRead(BtDownPin) == HIGH && digitalRead(BtUpPin) == LOW) { TimeForStep = long(60000000 / (SpeedRevMin * StepsInMotor)); //время одного шага в мкс Direction = true; //кнопка ВНИЗ - вращение по часовой - закрытие lcd.write((byte)2); //рисуем пользовательский символ стрелки вниз lcd.print(" "); while (digitalRead(BtDownPin) == HIGH) { if (digitalRead(LimitSwitchDown) == HIGH) { MotorFullStop(); return; } //даем сигнал мотору на один шаг через интервалы TimeForStep if(micros() - currentTimeMotor >= TimeForStep) { //функция перемещения мотора на один шаг в заданной последовательности stepper(); currentTimeMotor = micros(); } } } //----------------------- открываем клапан ----------------------- if(digitalRead(BtUpPin) == HIGH && digitalRead(BtDownPin) == LOW) { TimeForStep = long(60000000 / (SpeedRevMin * StepsInMotor)); //время одного шага в мкс Direction = false; //кнопка ВВЕРХ - вращение против часовой - открытие lcd.write((byte)1); //рисуем пользовательский символ стрелки вверх lcd.print(" "); while (digitalRead(BtUpPin) == HIGH) { if (digitalRead(LimitSwitchUp) == HIGH) { MotorFullStop(); return; } //даем сигнал мотору на один шаг через интервалы TimeForStep if(micros() - currentTimeMotor >= TimeForStep) { //функция перемещения мотора на один шаг в заданной последовательности stepper(); currentTimeMotor = micros(); } } } //------------- останавливаем клапан (обе кнопки отжаты) ------------- if(digitalRead(BtUpPin) == LOW && digitalRead(BtDownPin) == LOW) { MotorFullStop(); //полная остановка мотора lcd.print(" "); } //--------------- обе кнопки нажаты, защита от дурака ----------------- if(digitalRead(BtUpPin) == HIGH && digitalRead(BtDownPin) == HIGH) { MotorFullStop(); //полная остановка мотора lcd.print(" "); } } //****************** автоматический режим *************************** if(ManualModeOn == false && FlagSettingIn == false) { //------------ Расчет времени движения клапана --------------------- //если разница между старым и новым выходом больше 1 сек. //в процентах от времени полного хода клапана. //Это нужно, чтобы исключить слишком короткие включения //хотя вы можете убрать это условие вообще. Для шагового двигателя это не принципиально //здесь 10 = 1000мс / 100% if(abs(10*VALVE_TIME*(PID_output-PID_output_old)) > 1000) { //время открытия клапана в мс. //здесь 10 = 1000мс / 100% time_valve_move = 10*VALVE_TIME*(PID_output-PID_output_old); //чтобы исключить слишком долгого движения клапана if(abs(time_valve_move)>INTERVAL_TIME) { if (time_valve_move > INTERVAL_TIME) {time_valve_move = INTERVAL_TIME;} if (time_valve_move < -INTERVAL_TIME) {time_valve_move = -INTERVAL_TIME;} //здесь 0.1 = 100% / 1000мс PID_output_old = PID_output_old + 0.1*time_valve_move/VALVE_TIME; } else { PID_output_old = PID_output; } //------------- расчет движения клапана в шагах --------------------- TimeForStep = long(60000000 / (SpeedRevMin * StepsInMotor)); //время одного шага в мкс long SetSteps; //уставка количества шагов для перемещения SetSteps = long(abs(1000 * time_valve_move / TimeForStep)); //------------------ определяем направление вращения -------------- if (time_valve_move > 0) { Direction = false; //вращение против часовой, клапан открываем lcd.write((byte)1); //рисуем пользовательский символ стрелки вверх } if (time_valve_move < 0) { Direction = true; //вращение по часовой, клапан закрываем lcd.write((byte)2); //рисуем пользовательский символ стрелки вниз } lcd.print(" "); //---------- движемся заданное количество шагов или до конечников ---------- while(SetSteps > 0) { if (digitalRead(LimitSwitchUp) == HIGH && Direction == false) { MotorFullStop(); lcd.print(" "); return; } if (digitalRead(LimitSwitchDown) == HIGH && Direction == true) { MotorFullStop(); lcd.print(" "); return; } //даем сигнал мотору на один шаг через интервалы TimeForStep if(micros() - currentTimeMotor >= TimeForStep) { //функция перемещения мотора на один шаг в заданной последовательности stepper(); currentTimeMotor = micros(); SetSteps = SetSteps - 1; } //так как цикл while блокирует программу, //реакцию на кнопки вставляем в тело while ManualButtonFunction(); //ожидаем нажатие кнопки Manual ExitButtonFunction(); //ожидаем нажатие кнопки Exit SettingButtonFunction(); //ожидаем нажатие кнопки Настройки //ожидаем нажатие кнопок Вверх / Вниз CurrentButtonUp = debounce(LastButtonUp, BtUpPin); CurrentButtonDown = debounce(LastButtonDown, BtDownPin); while (CurrentButtonUp == HIGH || CurrentButtonDown == HIGH) { SValueSetting(); //в автоматическом режиме по кнопками вверх/вниз //можно задать температуру SecondStringDraw(); } if (FlagSettingIn == true || ManualModeOn == true) { MotorFullStop(); return; } } if (SetSteps == 0) //движение закончилось { MotorFullStop(); //полная остановка мотора lcd.print(" "); } } else { lcd.print(" "); } } //*** в автоматическом режиме кнопками вверх/вниз можно задать температуру ***** SValueSetting(); //********************* Нажата кнопка Настройки ************************ SettingButtonFunction(); if (FlagSettingIn == true) //если мы в настройках { //----------------- Нажата кнопка Увеличить ---------------------- ButtonPlus(); //функция реакции на нажатие кнопки Увеличить //---------------- Нажата кнопка Уменьшить ----------------------- ButtonMinus(); //функция реакции на нажатие кнопки Уменьшить SettingsOnLCD(SettingNumber); //отображаем строку с настройками } //************* Нажата кнопка Manual (Ручной Режим) *********************** ManualButtonFunction(); //**************** Нажата кнопка Exit ************************* ExitButtonFunction(); } //if(Start_wait>4) } //void loop() //********************** ФУНКЦИИ ********************************* //*********** Функция отображения настроек на LCD ************* void SettingsOnLCD(int SettingItem) { lcd.setCursor(0,0); switch (SettingItem) { case 1: //уставка температуры lcd.print("1 Setpoint (SV) "); lcd.setCursor(0,1); lcd.print(PID_SETPOINT); lcd.write((byte)0); lcd.print("C "); break; case 2: //коэф пропорциональности (proportional band в %) lcd.print("2 P-factor "); lcd.setCursor(0,1); lcd.print(P_FACTOR); lcd.print(" % "); break; case 3: //время интегрирования (integral time) в сек. lcd.print("3 I-factor "); lcd.setCursor(0,1); lcd.print(I_FACTOR); lcd.print(" sec "); break; case 4: //время дифференцирования (derivative time) в сек. lcd.print("4 D-factor "); lcd.setCursor(0,1); lcd.print(D_FACTOR); lcd.print(" sec "); break; case 5: //поправки температуры датчика lcd.print("5 Temp Correct. "); lcd.setCursor(0,1); lcd.print(TEMP_CORRECT); lcd.write((byte)0); lcd.print("C "); break; case 6: //сигнализация по максимальной температуре lcd.print("6 Alarm Max Temp"); lcd.setCursor(0,1); lcd.print(ALARM_MAX); lcd.write((byte)0); lcd.print("C "); break; case 7: //сигнализация по минимальной температуре lcd.print("7 Alarm Min Temp"); lcd.setCursor(0,1); lcd.print(ALARM_MIN); lcd.write((byte)0); lcd.print("C "); break; case 8: //диффиренциал сигнализации lcd.print("8 Alarm Dif Temp"); lcd.setCursor(0,1); lcd.print(ALARM_DIFF); lcd.write((byte)0); lcd.print("C "); break; case 9: //интервал опроса датчика в мс lcd.print("9 Sensor Time "); lcd.setCursor(0,1); lcd.print(INTERVAL_TIME); lcd.print(" ms "); break; case 10: //время открытия клапана в сек. от 0 до полного lcd.print("10 Valve Time "); lcd.setCursor(0,1); lcd.print(VALVE_TIME); lcd.print(" sec "); break; case 11: //диапазон регулирования lcd.print("11 Total Span "); lcd.setCursor(0,1); lcd.print(TOTAL_SPAN); lcd.write((byte)0); lcd.print("C "); break; //данные для калибровки датчика Pt100 //нижняя граница вольтажа с аналогового порта переведенный в цифровой вид case 12: lcd.print("12 Min_Volt "); lcd.setCursor(0,1); lcd.print(Min_Volt,0); lcd.print(" "); break; case 13: //соответствующая нижней границе температура lcd.print("13 MinPt "); lcd.setCursor(0,1); lcd.print(Min_Temp, 1); lcd.write((byte)0); lcd.print("C "); break; //верхняя граница вольтажа с аналогового порта переведенный в цифровой вид case 14: lcd.print("14 Max_Volt "); lcd.setCursor(0,1); lcd.print(Max_Volt,0); lcd.print(" "); break; case 15: //соответствующая верхней границе температура lcd.print("15 MaxPt "); lcd.setCursor(0,1); lcd.print(Max_Temp, 1); lcd.write((byte)0); lcd.print("C "); break; case 16: //количество шагов на оборот выходного вала мотор-редуктора //эта величина постоянная для данного мотор-редуктора lcd.print("16 Steps in Rev "); lcd.setCursor(0,1); lcd.print(StepsInMotor); lcd.print(" "); break; case 17: //скорость выходного вала мотор-редуктора в об/мин lcd.print("17 Speed rev/min "); lcd.setCursor(0,1); lcd.print(SpeedRevMin,3); lcd.print(" "); break; case 18: //оставлять ли мотор под током после остановки lcd.print("18 Brake Current "); lcd.setCursor(0,1); if(FlagBrake == true) { lcd.print("ON "); } else { lcd.print("OFF "); } break; case 19: //включение-выключение сигнализации по температуре lcd.print("19 Alarm ON/OFF "); lcd.setCursor(0,1); if(AlarmControlOn == true) { lcd.print("Alarm ON "); } else { lcd.print("Alarm OFF "); } break; case 20: //чтение настроек из EEPROM lcd.print("20 Read Settings"); lcd.setCursor(0,1); lcd.print("Press ("); lcd.write((byte)1); lcd.print(") button"); break; case 21: //запись настроек в EEPROM lcd.print("21 Save Settings"); lcd.setCursor(0,1); lcd.print("Press ("); lcd.write((byte)1); lcd.print(") button"); break; case 22: //загрузка значений по умолчанию lcd.print("22 Load Default "); lcd.setCursor(0,1); lcd.print("Press ("); lcd.write((byte)1); lcd.print(") button"); break; } } //********* Функция кнопки Больше, Увеличить, Плюс и т.п. ************* void ButtonPlus() { CurrentButtonUp = debounce(LastButtonUp, BtUpPin); if(CurrentButtonUp == HIGH) //кнопка нажата { if (SettingNumber >= 1 && SettingNumber <= 17 ) //пункты настроек, где нужен разгон параметра в режиме удержания { 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; } LastButtonUp = CurrentButtonUp; //обновляем текущее значение кнопки } //******** Функция изменения параметров при нажатии кнопки Больше ********** void ButtonPlusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //уставка температуры PID_SETPOINT = PID_SETPOINT + Inc_Val_Calc(IncTrigger,1); if(PID_SETPOINT > PID_SETPOINT_MAX) //верхний порог { PID_SETPOINT = PID_SETPOINT_MAX; } /*//при изменении уставки обнуляем интегральную часть и выход с регулятора, //чтобы "память" о старой уставке не мешала регулировать температуру I_part = 0; PID_output_old = 0; PID_output = 0;*/ break; case 2: //коэф пропорциональности (proportional band в %) P_FACTOR = P_FACTOR + Inc_Val_Calc(IncTrigger,1); if(P_FACTOR > P_FACTOR_MAX) //верхний порог { P_FACTOR = P_FACTOR_MAX; } break; case 3: //время интегрирования (integral time) в сек. I_FACTOR = I_FACTOR + Inc_Val_Calc(IncTrigger,1); if(I_FACTOR > I_FACTOR_MAX) //верхний порог { I_FACTOR = I_FACTOR_MAX; } break; case 4: //время дифференцирования (derivative time) в сек. D_FACTOR = D_FACTOR + Inc_Val_Calc(IncTrigger,1); if(D_FACTOR > D_FACTOR_MAX) //верхний порог { D_FACTOR = D_FACTOR_MAX; } break; case 5: //поправки температуры датчика TEMP_CORRECT = TEMP_CORRECT + Inc_Val_Calc(IncTrigger,1); if(TEMP_CORRECT > TEMP_CORRECT_MAX) //верхний порог { TEMP_CORRECT = TEMP_CORRECT_MAX; } break; case 6: //сигнализация по максимальной температуре ALARM_MAX = ALARM_MAX + Inc_Val_Calc(IncTrigger,1); if(ALARM_MAX > ALARM_MAX_MAX) //верхний порог { ALARM_MAX = ALARM_MAX_MAX; } break; case 7: //сигнализация по минимальной температуре ALARM_MIN = ALARM_MIN + Inc_Val_Calc(IncTrigger,1); if(ALARM_MIN > ALARM_MIN_MAX) //верхний порог { ALARM_MIN = ALARM_MIN_MAX; } break; case 8: //диффиренциал сигнализации ALARM_DIFF = ALARM_DIFF + Inc_Val_Calc(IncTrigger,1); if(ALARM_DIFF > ALARM_DIFF_MAX) //верхний порог { ALARM_DIFF = ALARM_DIFF_MAX; } break; case 9: //интервал опроса датчика в мс INTERVAL_TIME = INTERVAL_TIME + Inc_Val_Calc(IncTrigger,1); if(INTERVAL_TIME > INTERVAL_TIME_MAX) //верхний порог { INTERVAL_TIME = INTERVAL_TIME_MAX; } break; case 10: //время открытия клапана в сек. от 0 до полного VALVE_TIME = VALVE_TIME + Inc_Val_Calc(IncTrigger,1); if(VALVE_TIME > VALVE_TIME_MAX) //верхний порог { VALVE_TIME = VALVE_TIME_MAX; } break; case 11: //диапазон регулирования TOTAL_SPAN = TOTAL_SPAN + Inc_Val_Calc(IncTrigger,1); if(TOTAL_SPAN > TOTAL_SPAN_MAX) //верхний порог { TOTAL_SPAN = TOTAL_SPAN_MAX; } break; //данные для калибровки датчика Pt100 //нижняя граница вольтажа с аналогового порта переведенный в цифровой вид case 12: Min_Volt = Min_Volt + Inc_Val_Calc(IncTrigger,1); if (Min_Volt >= Max_Volt) { Min_Volt = Min_Volt - Inc_Val_Calc(IncTrigger,1); lcd.setCursor(0,1); lcd.print("Min must < Max "); //выводим подсказку delay(2000); } break; case 13: //соответствующая нижней границе температура Min_Temp = Min_Temp + Inc_Val_Calc(IncTrigger,2); if (Min_Temp > 1000) { Min_Temp = 1000; } break; //верхняя граница вольтажа с аналогового порта переведенный в цифровой вид case 14: Max_Volt = Max_Volt + Inc_Val_Calc(IncTrigger,1); if (Max_Volt > 1023) { Max_Volt = 1023; } break; case 15: //соответствующая верхней границе температура Max_Temp = Max_Temp + Inc_Val_Calc(IncTrigger,2); if (Max_Temp > 1000) { Max_Temp = 1000; } break; case 16: //количество шагов на оборот выходного вала мотор-редуктора StepsInMotor = StepsInMotor + Inc_Val_Calc(IncTrigger,1); break; case 17: //скорость выходного вала мотор-редуктора в об/мин SpeedRevMin = SpeedRevMin + Inc_Val_Calc(IncTrigger,3); break; case 18: //оставлять ли мотор под током после остановки if(FlagBrake == true) { FlagBrake = false; } else { FlagBrake = true; } break; case 19: //включение-выключение сигнализации по температуре if(AlarmControlOn == true) { AlarmControlOn = false; } else { AlarmControlOn = true; } break; case 20: //чтение настроек из EEPROM EEPROM.get(0,TEMP_CORRECT); //для типа int нужны 2 ячейки памяти EEPROM.get(2,ALARM_MAX); EEPROM.get(4,ALARM_MIN); EEPROM.get(6,ALARM_DIFF); EEPROM.get(8,INTERVAL_TIME); EEPROM.get(10,VALVE_TIME); EEPROM.get(12,TOTAL_SPAN); EEPROM.get(14,P_FACTOR); EEPROM.get(16,I_FACTOR); EEPROM.get(18,D_FACTOR); EEPROM.get(20,PID_SETPOINT); EEPROM.get(24,Min_Volt); //для типа float нужны 4 ячейки памяти EEPROM.get(28,Min_Temp); EEPROM.get(32,Max_Volt); EEPROM.get(36,Max_Temp); EEPROM.get(40,StepsInMotor); EEPROM.get(42,SpeedRevMin); EEPROM.get(46,FlagBrake); lcd.setCursor(0,1); lcd.print("Reading... OK "); delay(1500); break; case 21: //запись настроек в EEPROM EEPROM.put(0,TEMP_CORRECT); //для типа int нужны 2 ячейки памяти EEPROM.put(2,ALARM_MAX); EEPROM.put(4,ALARM_MIN); EEPROM.put(6,ALARM_DIFF); EEPROM.put(8,INTERVAL_TIME); EEPROM.put(10,VALVE_TIME); EEPROM.put(12,TOTAL_SPAN); EEPROM.put(14,P_FACTOR); EEPROM.put(16,I_FACTOR); EEPROM.put(18,D_FACTOR); EEPROM.put(20,PID_SETPOINT); EEPROM.put(24,Min_Volt); //для типа float нужны 4 ячейки памяти EEPROM.put(28,Min_Temp); EEPROM.put(32,Max_Volt); EEPROM.put(36,Max_Temp); EEPROM.put(40,StepsInMotor); EEPROM.put(42,SpeedRevMin); EEPROM.put(46,FlagBrake); //ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); lcd.setCursor(0,1); lcd.print("Saving... OK "); delay(1500); break; case 22: //загрузка значений по умолчанию TEMP_CORRECT = 0; ALARM_MAX = 30; ALARM_MIN = 20; ALARM_DIFF = 1; INTERVAL_TIME = 2000; VALVE_TIME = 200; TOTAL_SPAN = 55; P_FACTOR = 100; I_FACTOR = 240; D_FACTOR = 60; PID_SETPOINT = 24; Min_Volt = 39; Min_Temp = 0; Max_Volt = 546; Max_Temp = 100; StepsInMotor = 4075; SpeedRevMin = 1.4; FlagBrake = false; lcd.setCursor(0,1); lcd.print("Loading... OK "); delay(1500); break; } } //********* Функция кнопки Меньше, Уменьшить, Минус или т.п. ************* void ButtonMinus() { CurrentButtonDown = debounce(LastButtonDown, BtDownPin); if(CurrentButtonDown == HIGH) //кнопка нажата { if (SettingNumber >= 1 && SettingNumber <= 17 ) //пункты настроек, где нужен разгон параметра в режиме удержания { 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; } LastButtonDown = CurrentButtonDown; //обновляем текущее значение кнопки } //******** Функция изменения параметров при нажатии кнопки Меньше ********** void ButtonMinusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //уставка температуры PID_SETPOINT = PID_SETPOINT - Inc_Val_Calc(IncTrigger,1); if(PID_SETPOINT < PID_SETPOINT_MIN) //нижний порог { PID_SETPOINT = PID_SETPOINT_MIN; } //при изменении уставки обнуляем интегральную часть и выход с регулятора, //чтобы "память" о старой уставке не мешала регулировать температуру I_part = 0; PID_output_old = 0; PID_output = 0; break; case 2: //коэф пропорциональности (proportional band в %) P_FACTOR = P_FACTOR - Inc_Val_Calc(IncTrigger,1); if(P_FACTOR < P_FACTOR_MIN) //нижний порог { P_FACTOR = P_FACTOR_MIN; } break; case 3: //время интегрирования (integral time) в сек. I_FACTOR = I_FACTOR - Inc_Val_Calc(IncTrigger,1); if(I_FACTOR < I_FACTOR_MIN) //нижний порог { I_FACTOR = I_FACTOR_MIN; } break; case 4: //время дифференцирования (derivative time) в сек. D_FACTOR = D_FACTOR - Inc_Val_Calc(IncTrigger,1); if(D_FACTOR < D_FACTOR_MIN) //нижний порог { D_FACTOR = D_FACTOR_MIN; } break; case 5: //поправки температуры датчика TEMP_CORRECT = TEMP_CORRECT - Inc_Val_Calc(IncTrigger,1); if(TEMP_CORRECT < TEMP_CORRECT_MIN) //нижний порог { TEMP_CORRECT = TEMP_CORRECT_MIN; } break; case 6: //сигнализация по максимальной температуре ALARM_MAX = ALARM_MAX - Inc_Val_Calc(IncTrigger,1); if(ALARM_MAX < ALARM_MAX_MIN) //нижний порог { ALARM_MAX = ALARM_MAX_MIN; } break; case 7: //сигнализация по минимальной температуре ALARM_MIN = ALARM_MIN - Inc_Val_Calc(IncTrigger,1); if(ALARM_MIN < ALARM_MIN_MIN) //нижний порог { ALARM_MIN = ALARM_MIN_MIN; } break; case 8: //диффиренциал сигнализации ALARM_DIFF = ALARM_DIFF - Inc_Val_Calc(IncTrigger,1); if(ALARM_DIFF < ALARM_DIFF_MIN) //нижний порог { ALARM_DIFF = ALARM_DIFF_MIN; } break; case 9: //интервал опроса датчика в мс INTERVAL_TIME = INTERVAL_TIME - Inc_Val_Calc(IncTrigger,1); if(INTERVAL_TIME < INTERVAL_TIME_MIN) //нижний порог { INTERVAL_TIME = INTERVAL_TIME_MIN; } break; case 10: //время открытия клапана в сек. от 0 до полного VALVE_TIME = VALVE_TIME - Inc_Val_Calc(IncTrigger,1); if(VALVE_TIME < VALVE_TIME_MIN) //нижний порог { VALVE_TIME = VALVE_TIME_MIN; } break; case 11: //диапазон регулирования TOTAL_SPAN = TOTAL_SPAN - Inc_Val_Calc(IncTrigger,1); if(TOTAL_SPAN < TOTAL_SPAN_MIN) //нижний порог { TOTAL_SPAN = TOTAL_SPAN_MIN; } break; //данные для калибровки датчика Pt100 //нижняя граница вольтажа с аналогового порта переведенный в цифровой вид case 12: Min_Volt = Min_Volt - Inc_Val_Calc(IncTrigger,1); if (Min_Volt < 0) { Min_Volt = 0; } break; case 13: //соответствующая нижней границе температура Min_Temp = Min_Temp - Inc_Val_Calc(IncTrigger,2); if (Min_Temp < 0) { Min_Temp = 0; } break; //верхняя граница вольтажа с аналогового порта переведенный в цифровой вид case 14: Max_Volt = Max_Volt - Inc_Val_Calc(IncTrigger,1); if (Max_Volt <= Min_Volt) { Max_Volt = Max_Volt + Inc_Val_Calc(IncTrigger,1); lcd.setCursor(0,1); lcd.print("Max must > Min "); //выводим подсказку delay(2000); } break; case 15: //соответствующая верхней границе температура Max_Temp = Max_Temp - Inc_Val_Calc(IncTrigger,2); if (Max_Temp < 0) { Max_Temp = 0; } break; case 16: //количество шагов на оборот выходного вала мотор-редуктора StepsInMotor = StepsInMotor - Inc_Val_Calc(IncTrigger,1); if(StepsInMotor < 1) { StepsInMotor = 1; } break; case 17: //скорость выходного вала мотор-редуктора в об/мин SpeedRevMin = SpeedRevMin - Inc_Val_Calc(IncTrigger,3); if(SpeedRevMin < 0.001) { SpeedRevMin = 0.001; } break; case 18: //оставлять ли мотор под током после остановки if(FlagBrake == true) { FlagBrake = false; } else { FlagBrake = true; } break; case 19: if(AlarmControlOn == true) { AlarmControlOn = false; } else { AlarmControlOn = true; } break; } } //**** в автоматическом режиме кнопками вверх/вниз можно задать температуру ***** void SValueSetting() { int sn = SettingNumber; //запоминаем текущий пункт меню настроек //чтобы к нему вернуться if(ManualModeOn == false) { //если мы не в настройках и видим уставку температуры во второй строке if(FlagSettingIn == false && LCD_second_string == 1) { SettingNumber = 1; ButtonMinus(); //функция реакции на нажатие кнопки Уменьшить ButtonPlus(); //функция реакции на нажатие кнопки Уменьшить SettingNumber = sn; ZeroAgain(); //обнуление выходов и интегральной части } } } //**************** функция рисования второй строки ************************* void SecondStringDraw() { if(FlagSettingIn == false) //если мы не в настройках { lcd.setCursor(0,1); //прописываем вторую строку if(ManualModeOn == true) //если включен ручной режим { lcd.print("Manual Mode "); } else { switch (LCD_second_string) { case 1: lcd.print("SV="); lcd.print(PID_SETPOINT); lcd.write((byte)0); //рисуем пользовательский символ градуса lcd.print("C "); break; case 2: lcd.print("Output="); lcd.print(PID_output); lcd.print("% "); break; case 3: lcd.print("P_part="); lcd.print(P_part); lcd.print("% "); break; case 4: lcd.print("I_part="); lcd.print(I_part); lcd.print("% "); break; case 5: lcd.print("D_part="); lcd.print(D_part); lcd.print("% "); break; } } } } void SettingExit() { //это нужно, чтобы остаться на том же пункте меню настроек SettingNumber = SettingNumber - 1; if(SettingNumber < 0) {SettingNumber = 0;} ZeroAgain(); //обнуление выходов и интегральной части FlagSettingIn = false; } //*************** Функция устранения дребезга контактов *********** 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 expRunningAverage2(float newVal2) { static float filVal2 = 0; float k; // резкость фильтра зависит от процента отклонения от предыдущего значения if (abs((newVal2 - filVal2)/filVal2) > 0.05) k = 0.9; else k = 0.1; filVal2 += (newVal2 - filVal2) * k; return filVal2; } //*************** Функция разгона приращения параметра ************ float Inc_Val_Calc(boolean IncTrigger, int IncVariant) { static int count_inc = 0; //счетчик итераций 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 5; } if (count_inc > 90 && count_inc <= 120) { return 10; } if (count_inc > 120) { 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 5; } if (count_inc > 90) { return 10; } } if (IncVariant == 3) { //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 30) { return 0.001; } if (count_inc > 30 && count_inc <= 60) { return 0.002; } if (count_inc > 60 && count_inc <= 90) { return 0.005; } if (count_inc > 90 && count_inc <= 120) { return 0.1; } if (count_inc > 120 && count_inc <= 150) { return 0.2; } if (count_inc > 150 && count_inc <= 180) { return 0.5; } if (count_inc > 180) { return 1; } } } else { count_inc = 0; if (IncVariant == 1) { return 1; //приращение по умолчанию } if (IncVariant == 2) { return 0.1; //приращение по умолчанию } if (IncVariant == 3) { return 0.001; } } } //*************** функция переключения полюсов **************** //порядок переключения берется из спецификации конкретного мотора //есть два режима : шаговый и полушаговый //здесь представлен полушаговый режим void stepper() { switch(Steps) { case 0: digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, HIGH); break; case 1: digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, HIGH); digitalWrite(MotorPin4, HIGH); break; case 2: digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, HIGH); digitalWrite(MotorPin4, LOW); break; case 3: digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, HIGH); digitalWrite(MotorPin3, HIGH); digitalWrite(MotorPin4, LOW); break; case 4: digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, HIGH); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, LOW); break; case 5: digitalWrite(MotorPin1, HIGH); digitalWrite(MotorPin2, HIGH); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, LOW); break; case 6: digitalWrite(MotorPin1, HIGH); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, LOW); break; case 7: digitalWrite(MotorPin1, HIGH); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, HIGH); break; default: digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, LOW); break; } SetDirection(); } //************** функция смены направления вращения **************** void SetDirection() { if(Direction == true){ Steps = Steps + 1;} if(Direction == false){ Steps = Steps - 1;} if(Steps > 7){Steps = 0;} if(Steps < 0){Steps = 7;} } //******************** полная остановка мотора ********************* void MotorFullStop() { if (FlagBrake == false) //оставить ли мотор под током после остановки { digitalWrite(MotorPin1, LOW); digitalWrite(MotorPin2, LOW); digitalWrite(MotorPin3, LOW); digitalWrite(MotorPin4, LOW); } } //************* Функция кнопки Manual (Ручной Режим) *********************** void ManualButtonFunction() { //считываем состояние кнопки с устраненным дребезгом CurrentButtonManual = debounce(LastButtonManual, BtManualPin); if(CurrentButtonManual == HIGH && FlagManualLoop == true) //если кнопка нажата { //входим или выходим из ручного режима if(ManualModeOn == false) { ManualModeOn = true; } else { ManualModeOn = false; ZeroAgain(); //обнуление выходов и интегральной части } if(FlagSettingIn == true) //если были в настройках, то выходим из них { SettingExit(); } FlagManualLoop = false; } if(CurrentButtonManual == LOW) {FlagManualLoop = true;} LastButtonManual = CurrentButtonManual; //обновляем текущее значение кнопки } //**************** Функция кнопки Exit ************************* void ExitButtonFunction() { //считываем состояние кнопки с устраненным дребезгом CurrentButtonExit = debounce(LastButtonExit, BtExitPin); if(CurrentButtonExit == HIGH && FlagExitLoop == true) //если кнопка нажата { if(FlagSettingIn == false) //если мы не в настройках { if(ManualModeOn == false) //если мы не в ручном режиме { LCD_second_string = LCD_second_string+1; if(LCD_second_string > 5) {LCD_second_string = 1;} SecondStringDraw(); } } else //если мы в настройках, то выходим из настроек { SettingExit(); } FlagExitLoop = false; } if(CurrentButtonExit == LOW) {FlagExitLoop = true;} LastButtonExit = CurrentButtonExit; //обновляем текущее значение кнопки } //********************* Функция кнопки Настройки ************************ void SettingButtonFunction() { //при каждом нажатии кнопки Настройки //перескакиваем на следующий параметр в очереди. И так по кругу //считываем состояние кнопки с устраненным дребезгом CurrentButtonSetting = debounce(LastButtonSetting, BtSettingPin); if(CurrentButtonSetting == HIGH) //кнопка нажата { if (FlagSettingLoop == true) //эта переменная нужна для однократного нажатия кнопки, //даже если ее удерживать { FlagSettingLoop = false; FlagSettingIn = true; SettingNumber = SettingNumber + 1; if (SettingNumber > 22) {SettingNumber = 1;} } } else //кнопка отжата { FlagSettingLoop = true; } //обновляем текущее значение кнопки LastButtonSetting = CurrentButtonSetting; } //------------------- обнуление ------------------------- void ZeroAgain() { //после настроек обнуляем интегральную часть и выход с регулятора, //чтобы "память" о старой уставке не мешала регулировать температуру I_part = 0; PID_output_old = 0; PID_output = 0; }