Контроллер муфельной печи.
- Войдите на сайт для отправки комментариев
Была у меня старая муфельная печь со сгоревшим блоком управления. Пришли две дочки и со слезами на глазах вывалили на стол всякие поделки из глины, и говорят - обжечь надо-бы. Стал я думать гадать, и вспомнил про старую печь. Ну тут и понеслось...
Потратил всего 23.7$
Arduino R3 UNO - 7$
LCD 1602 Keypad shield - 3.5$
MAX6675 Module + K Type Thermocouple Sensor for Arduino - 4.5$
UN3F SLA-05VDC-SL-A 5V 30A Relay Module For Arduino - 3.7$
Корпус (распаечную коробку) - 5$
Схема:
Знаю, что немного не правильно сделал питание, надо было +5 отдельно от блока завести на реле, а не через ардуинку. Но в целом работает нормально...
Вот что вышло, внешне вполне нормально, все блоки на разьемах, все разбирается без паяльника:
ну а вот результаты калибровки, снимал за 1 раз, но потом вручную подгонял значения:
вот результаты 10 минутной работы в режиме поддержания температуры на температуре 330с и 740с, в принцципе в заданый коридор (плюс минус 5с) укладывается.
теперь собственно программа, состоит из нескольких файлов, так удобнее разрабатывать и отлаживать.
Алгоритм расписывать не буду, долго, да и функционал то-же, хотя там есть интересные моменты типа автокалибровки и автоопределения открытия дверцы и т.д.:
файл term.ino
// --------------------------------------------------------- // управление муфельной печью модуль "term" // // состав проекта: // Arduino R3 UNO // LCD 1602 Keypad shield // MAX6675 Module + K Type Thermocouple Sensor for Arduino // UN3F SLA-05VDC-SL-A 5V 30A Relay Module For Arduino // // версия среды Arduino 1.5.2 // // автор vde69@mail.ru (с), процедуры главного алгоритма // --------------------------------------------------------- // добавляем необходимые библиотеки #include <EEPROM.h> #include <LiquidCrystal.h> #include <util/delay.h> // общие настройки const int TIME_GET_THERMOMETR = 1000; // время шага опроса термодатчиков const int TIME_GET_ERROR = 1000; // время шага диагностики ошибок const int TIME_ANIME = 300; // время шага анимации const int TIME_RUN = 100; // время шага расчета действий по включению/выключению нагрузки const int TIME_STAB = 2000; // время расчета скорости разогрева/остывания, время должно выбиратся достаточным для игнорирования дребезга температуры const int BUTTON_TIME = 20; // время между нажатием и отпусканием (устранение дребезга) const int BUTTON_TIME_LONG = 5000; // время перевода в режим SETUP const int TERM_LIMIT = 1100; // предельная температура контролера, установить температуру выше нельзя, при реальном превышении будет издавать звук const byte TERM_GRADIENT = 5; // допустимый градиент температуры (от установленой), влияет на процесс калибровки, рекомендуется от 5 до 20 (5 - самая точная калибрвка) const byte TERM_LIMIT_SPEED = 2; // допустимая скорость разогрева или остывания (градус/сек), используется для определения открытия/закрытия печи // для текущей печи реальная максимальная скорость разогрева 2/3 градус сек, предел выбран в 3 раза больше реальной скорости // статусы нажатых кнопок, определить несколько нажатых кнопок одновременно - нельзя const int BUTTON_RIGHT = 1; // 0 < 100 const int BUTTON_UP = 2; // 148 < 200 const int BUTTON_DOWN = 3; // 338 < 400 const int BUTTON_LEFT = 4; // 515 < 600 const int BUTTON_SELECT = 5; // 750 < 800 const int BUTTON_NONE = 0; // 1023 // статусы режимов работы const byte STATUS_STOP = 1; // отображаем текущую и установленую температуру, // установленую температуру можно редактировать, // нагрузка не включается // переход возможен в статусы RUN и SETUP const byte STATUS_RUN = 2; // отображаем текущую и установленую температуру, // установленую температуру нельзя редактировать, // нагрузка включается для поддержания установленой температуры // переход возможен только в статус STOP const byte STATUS_SETUP = 3; // отображаем линейку настройки и вариант выхода STOP/START // линейку и вариант выхода можно редактировать, // нагрузка не включается, текущая температура отображается // переход возможен только статус STOP или SETUP_RUN const byte STATUS_SETUP_RUN = 4; // отображаем линейку выполнения настройки контроллера, текущий этап // нагрузка включается для определения параметров калибровки, текущая температура отображается // переход возможен только в статус SETUP const byte STATUS_ERROR = 5; // отображаем описание ошибки, // нагрузка не включается, текущая температура отображается // переход возможен только в статус STOP // -------------------------------------------------------- // объявления для индикаторов и кнопок управления // LiquidCrystal lcd(8, 9, 4, 5, 6, 7 ); // переменные для поддержки циклов анимации unsigned long time_anime = 0; unsigned long time_anime_new = 0; int step_anime = 0; // переменные для поддержки циклов клавиатуры unsigned long time_button = 0; unsigned long time_button_new = 0; // переменные для поддержки циклов опроса датчиков unsigned long time_temp = 0; unsigned long time_temp_new = 0; // переменные для поддержки циклов включения/выключения нагревателя и расчетов unsigned long time_run = 0; unsigned long time_run_new = 0; unsigned long time_run_step = 0; // переменные для контроля, изменения и отображения температур // конкретные значения могут переустанавливатся (из сохраненых в памяти) при старте системы int term_set = 0; // установленая температура int term_set_display = 0; // установленая температура выведеная на дисплей int term_set_new = 0; // новая установленая температура int term_real = 0; // текущая температура полученая от датчиков int term_real_display = 0; // текущая температура выведеная на дисплей int term_real_new = 0; // новая температура полученая от датчиков // переменные для поддержки клавиатуры и экрана int button = BUTTON_NONE; // текущаяя нажатая клавиша int button_new = BUTTON_NONE; // новое значение нажатой клавиши boolean FlagCursor = false; // кеширующий флаг обозначающий, что включен режим курсора "Blink" // переменные для поддержки статусов контроллера byte mode = STATUS_STOP; // текущий статус работы контролера int edit_symbol = 0; // номер символа в поле редактирования на экране // для температуры 1...4 // для полок калибровки 1..10 // 0-не редактируется, курсор не показывается // переменные для общения с термопарой int thermoDO = 17; // А3 он же SO int thermoCS = 16; // A2 int thermoCLK = 15; // A1 он же SCK // переменные для реле int heatCTRL = 18; // A4 // переменные для динамика int beepCTRL = 19; // A5 // переменные для расчетов int r_pow; // текущий коэффициэнт (сотая доля процента, 10000 = 100%) относительной мощность для поддержания планируемой температуры int r_term; // текущий перебег (градусы), время перебега при нагреве до планируемой температуры со 100% мощностью int r_dt; // время цикла on/off сек int r_step; // время шага хранения истории сек (для апроксимации поддержания температуры) // дополнительные коэффициэнты (сотая доля процента, 10000 = 100%) int rk_pow; int rk_term; boolean mode_heat = false; // флаг показывающий включена или нет нагрузка unsigned long time_heat_start = 0; // время последнего включения нагрузки unsigned long time_heat_stop = 0; // время последнего отключения нагрузки // структуры которые хранят примерные таблицы тепловых характеристик int ar_temp[10] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000}; // температуры градусы int ar_pow[10] = { 4, 6, 12, 20, 30, 42, 53, 63, 70, 75}; // мощность процент int ar_tep[10] = {30, 23, 18, 14, 11, 9, 7, 6, 5, 5}; // температура перебега int ar_dt[10] = {30, 25, 20, 20, 20, 20, 20, 20, 20, 20}; // время цикла on/off нагрузки сек int ar_step[10] = {120, 90, 70, 55, 42, 33, 24, 20, 18, 18}; // время перебега температуры (инерционность) сек int ar_setup[10] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; // резерв для вычислений при выполнения SETUP byte ar_setup_scr[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // признаки для обновления на экране элементов массива ar_setup byte setup_step; // переменные для расчетов // переменные используемые только при калибровке, пишу сюда, что-бы были доступны для логов unsigned long time_setup_t1; // время достижения калибровочной температуры, или время запуска нагрева на втором этапе калибровки unsigned long time_setup_t2; // время достижения калибровочной температуры, или время запуска нагрева на втором этапе калибровки int term_setup_max; // максимальная температура (поиск перебега) int pow_setup_max; // минимальная мощность на которой проявился перегрев int pow_setup_min; // максимальная мощность на которой проявился недогрев int pow_setup; // текущая мощность byte step_run; // текущий режим работы или калибровки // 0 - нет // 1 - первичный разогрев // 2 - поиск перебега // 3 - остываение ниже градиента // 4 - вторичный разогрев для поиска оптимальной мощности поддержания температуры // текущий режим работы // 0 - инициализация // 1 - ожидание стабилизации // 2 - первичный разогрев // 3 - ожидание остывания до текущей температуры // 4 - поддержание температуры // переменные для ошибок int Error_Term; // температура на которой зафиксирована ошибка int Error_Number; // номер ошибки unsigned long time_error; // время последнего опроса ошибок // переменные для контроля предела скорости нагрева/остывания int Stab_Term; // предыдущая температура unsigned long time_Stab; // время последней температуры boolean stability = false; // флаг контроля скорости нагрева/остывания, false - состояние не стабильное byte si; //************************************************************************************************* // процедура инициализации контролера //************************************************************************************************* void setup() { int i; Serial.begin(9600); LogSerial(0); // читаем сохраненные значения в энергонезависимой памяти // первые 2 байта - сигнатура формата, для нашего проекта возьмем значение "T1", что равнозначно числу 21469 // при изменении физического местоположения сохраняемых значений идентификатор следует изменить // при изменении идентификатора (и физического места данных), чтение производится не будет // что будет равносильно полному заливки скетча в новый контроллер i = Get_signature(); if (i != 21469) { // перезапишем все сохраняемые параметры LogSerial(1); i = 21469; Set_signature(i); Set_term_set(term_set); for (byte i1=0; i1 <= 9; i1++) { Set_temp(i1, ar_temp[i1]); Set_pow(i1, ar_pow[i1]); Set_tep(i1, ar_tep[i1]); Set_dt(i1, ar_dt[i1]); Set_step(i1, ar_step[i1]); Set_setup(i1, ar_setup[i1]); } } // теперь читаем все параметры из энергонезависимой памяти // читаем установленую температуру i = Get_term_set(); term_set = i; term_set_display = i; term_set_new = i; // читаем структуры for (byte i1=0; i1 <= 9; i1++) { ar_temp[i1] = Get_temp (i1); ar_pow[i1] = Get_pow (i1); ar_tep[i1] = Get_tep (i1); ar_dt[i1] = Get_dt (i1); ar_step[i1] = Get_step (i1); ar_setup[i1] = Get_setup(i1); } LogSerial(2); // инициализируем модуль контролера термопары pinMode(thermoCS, OUTPUT); pinMode(thermoCLK, OUTPUT); pinMode(thermoDO, INPUT); digitalWrite(thermoCS, HIGH); delay(500); // инициализируем порт реле pinMode(heatCTRL, OUTPUT); off_Relay(); // инициализируем экран, у нас 16 символов и 2 строки lcd.begin(16, 2); lcd.noCursor(); screen_out(true, true, true); Beep(1); LogSerial(3); } //************************************************************************************************* // главный цикл //************************************************************************************************* void loop() { unsigned long time_loop_new; unsigned long time_pause; int button_temp; time_loop_new = millis(); // получим время начала цикла // -------------------------------------------------------------------------------------------- // обработка управления кнопками, должена идти как можно ближе к началу цикла button_temp = getPressedButton(); if (button_new != button_temp) { // изменилось состояние клавиатуры, запомним время и новое состояние button_new = button_temp; time_button_new = time_loop_new; } time_pause = getDelayTime(time_button_new, time_loop_new); if (time_pause <= BUTTON_TIME) { // ожидание минимальной паузы } else { if (button_new != button) { // сотояния клавиатуры изменилось, минимальная пауза выдержана if (button_new == BUTTON_NONE) { // кнопка отпущена // вызовем обработчик, при этом расчитаем время нажатие от события нажатия а не от нового события отпускания OnKeyUp (button, getDelayTime(time_button, time_button_new)); } else if (button != BUTTON_NONE) { // кнопка нажата, пока обработчик этого события мне не нужен, // по тому, что кнопка SELECT обрабатывается по таймеру двумя разными способами // здесь ничего не делаем, код оставляю для расширения в других проектах } else { // смена нажатой кнопки на другую, пока обработчик этого события мне не нужен // по тому, что мы работаем не по событию нажатия, а по событию отжатия // здесь ничего не делаем, код оставляю для расширения в других проектах } // изменения клавиатуры отработаны, теперь запомним новое состояние time_button = time_loop_new; button = button_new; } else if ((button_new == button) && (button != BUTTON_NONE)) { // есть какая-то зажатая кнопка if ((button == BUTTON_SELECT) && (time_pause >= BUTTON_TIME_LONG)) { // кнопка SELECT нажата достаточно долго для перехода в режим SETUP // в текущем проекте это пока не нужно, мы пойдем другим путем и будем передовать время удержания в событие отжатия кнопки // здесь ничего не делаем, код оставляю для расширения в других проектах, // но, все-же, напомним звуком, что кнопка зажата или это ошибка... Beep(2); } } } // -------------------------------------------------------------------------------------------- // получение актуальной температуры time_pause = getDelayTime(time_temp, time_loop_new); if (time_pause > TIME_GET_THERMOMETR) { time_temp = time_loop_new; term_real_new = readCelsius(); } // -------------------------------------------------------------------------------------------- // поиск и обработка ошибок time_pause = getDelayTime(time_error, time_loop_new); if (time_pause > TIME_GET_ERROR) { time_error = time_loop_new; // включение пищалки динамика if (term_real_new > TERM_LIMIT || mode == STATUS_ERROR) { Beep(2); } if (mode == STATUS_RUN) { // ошибки при работе if ( term_real_new < -100 // ошибка термопары || term_real_new > (term_set + 50) // перегрев на 50 градусов || term_real_new > TERM_LIMIT) { // общий перегрев mode = STATUS_ERROR; Error_Term = term_real_new; Error_Number = 1; off_Relay(); screen_out(true, true, true); } } else if (mode == STATUS_SETUP_RUN) { // ошибки при калибровке if ( term_real_new < -100 // ошибка термопары || term_real_new > TERM_LIMIT) { // общий перегрев mode = STATUS_ERROR; Error_Term = term_real_new; Error_Number = 1; off_Relay(); screen_out(true, true, true); } } } // -------------------------------------------------------------------------------------------- // контроль скорости разогрева/остывания time_pause = getDelayTime(time_Stab, time_loop_new); if (time_pause > TIME_STAB) { if ( ((Stab_Term + (1.0 * TERM_LIMIT_SPEED * TIME_STAB / 1000)) <= term_real_new) || ((Stab_Term - (1.0 * TERM_LIMIT_SPEED * TIME_STAB / 1000)) >= term_real_new)) { // обнаружен выход скорости нагрева/остывания за допустимые границы stability = false; if (mode == STATUS_RUN) { if ( (step_run == 2) || (step_run == 3) || (step_run == 4)) { // перевод контролеера в режим стабилизации step_run = 1; } } else if (mode == STATUS_SETUP_RUN) { // для режима калибровки это будет ошибкой mode = STATUS_ERROR; Error_Term = term_real_new; Error_Number = 2; off_Relay(); screen_out(true, true, true); } } else { stability = true; } // все нормально // обновим переменные цикла time_Stab = time_loop_new; Stab_Term = term_real_new; } // -------------------------------------------------------------------------------------------- // расчет параметров для включения и выключения разогрева time_pause = getDelayTime(time_run, time_loop_new); if (time_pause > TIME_RUN) { time_run = time_loop_new; Run(time_loop_new); } // -------------------------------------------------------------------------------------------- // обновление экрана, это последнее... time_pause = getDelayTime(time_anime, time_loop_new); if (time_pause > TIME_ANIME) { time_anime = time_loop_new; screen_out(true, false, false); } else { screen_out(false, false, false); } }
файл Screen_out.ino
//************************************************************************************************* // процедура выводит все параметры и оформление на экран // anime - вызывает перерисовку анимации в зависимости он значения переменных time_anime, time_anime_new, step_anime // face - вызывает перерисовку надписей и прочих статических значений в зависимости от текущего режима контролера // force - параметр для явного обновления всего экрана вне зависимости от остальных параметров // true - полностью обновляет все параметры // false - обновляет только измененые параметры //************************************************************************************************* void screen_out(boolean anime, boolean face, boolean force) { byte ii; if ( (mode == STATUS_RUN) || (mode == STATUS_SETUP_RUN) || (mode == STATUS_ERROR)) { // отключаем курсор для редактируемых полей edit_symbol = 0; } // строка 1: // текущая температура выводится всегда в колонках 12,13,14,15 // оформление текущей температувы выводится всегда в колонках 10,11,16 term_real_display = OutNumber(term_real_display, term_real_new, 11, 0, 4, force); if (force || face) { PrintLcd("t:", 9, 0); PrintLcd("c", 15, 0); } //------------------------------------------------------- if (mode == STATUS_STOP) { // строка 1: // анимацию не выводим, вместо анимации выводим оформление в колонках 1,2,3,4,5,6,7,8,9 if (force || face) { PrintLcd("=STOP= ", 0, 0); } // строка 2: // установленая температура выводится в колонках 12,13,14,15 // оформление выводим в колонки 1,2,3,4,5 // оформление установленой температувы выводится в колонки 6,7,8,9,10,11,16 term_set_display = OutNumber(term_set_display, term_set_new, 11, 1, 4, force); if (force || face) { PrintLcd(" ", 0, 1); PrintLcd("SET T:", 5, 1); PrintLcd("c", 15, 1); } // установка курсора во вторую строку редактируемого поля if (edit_symbol == 0) { if (FlagCursor == true) { lcd.noBlink(); FlagCursor = false; } } else { lcd.setCursor(10 + edit_symbol, 1); if (FlagCursor != true) { lcd.blink(); FlagCursor = true; } } } //------------------------------------------------------- else if (mode == STATUS_RUN) { // строка 1: // очистим поле для анимации в колонках 1,2,3,4,5,6,7,8,9 if (force || face) { PrintLcd(" ", 0, 0); } // анимацию выводим в первую строку if (anime == true) { // делаем шаг анимации if (step_anime < 3) { step_anime = step_anime + 1; } else { step_anime = 0; } // выводим текущий шак на экран if (step_anime == 0) { PrintLcd("|", 0, 0); } else if (step_anime == 1) { PrintLcd("/", 0, 0); } else if (step_anime == 2) { PrintLcd("-", 0, 0); } else { PrintLcd("-", 0, 0); } } // строка 2: // установленая температура выводится в колонках 12,13,14,15 // оформление выводим в колонки 1,2,3,4,5 // оформление установленой температувы выводится в колонки 6,7,8,9,10,11,16 term_set_display = OutNumber(term_set_display, term_set_new, 11, 1, 4, force); if (force || face) { PrintLcd(" ", 0, 1); PrintLcd(" T:", 5, 1); PrintLcd("c", 15, 1); } } //------------------------------------------------------- else if (mode == STATUS_SETUP) { // строка 1: // анимацию не выводим, вместо анимации выводим оформление в колонках 1,2,3,4,5,6,7,8,9 if (force || face) { PrintLcd(" =SETUP= ", 0, 0); } // строка 2: if (force || face) { PrintLcd(" ", 0, 1); for (byte ii=0; ii <= 9; ii++) { if (ar_setup[ii] == 0) { PrintLcd("_", ii + 3, 1); } else if (ar_setup[ii] == 1) { PrintLcd("o", ii + 3, 1); } else if (ar_setup[ii] == 2) { PrintLcd("O", ii + 3, 1); } ar_setup_scr[ii] = 0; } } else { for (byte ii=0; ii <= 9; ii++) { if (ar_setup_scr[ii] == 1) { if (ar_setup[ii] == 0) { PrintLcd("_", ii + 3, 1); } else if (ar_setup[ii] == 1) { PrintLcd("o", ii + 3, 1); } else if (ar_setup[ii] == 2) { PrintLcd("O", ii + 3, 1); } } ar_setup_scr[ii] = 0; } } // установка курсора во вторую строку редактируемого поля if (edit_symbol == 0) { if (FlagCursor == true) { lcd.noBlink(); FlagCursor = false; } } else { lcd.setCursor(2 + edit_symbol, 1); if (FlagCursor != true) { lcd.blink(); FlagCursor = true; } } } //------------------------------------------------------- else if (mode == STATUS_SETUP_RUN) { // строка 1: // очистим поле для анимации в колонках 1,2,3,4,5,6,7,8,9 if (force || face) { PrintLcd(" SETUP ", 0, 0); } // анимацию выводим в первую строку if (anime == true) { // делаем шаг анимации if (step_anime < 3) { step_anime = step_anime + 1; } else { step_anime = 0; } // выводим текущий шак на экран if (step_anime == 0) { PrintLcd("|", 0, 0); } else if (step_anime == 1) { PrintLcd("/", 0, 0); } else if (step_anime == 2) { PrintLcd("-", 0, 0); } else { PrintLcd("-", 0, 0); } } // строка 2: if (force || face) { PrintLcd(" ", 0, 1); for (byte ii=0; ii <= 9; ii++) { if (ar_setup[ii] == 0) { PrintLcd("_", ii + 3, 1); } else if (ar_setup[ii] == 1) { PrintLcd("o", ii + 3, 1); } else if (ar_setup[ii] == 2) { PrintLcd("O", ii + 3, 1); } ar_setup_scr[ii] = 0; } } else { for (byte ii=0; ii <= 9; ii++) { if (ar_setup_scr[ii] == 1) { if (ar_setup[ii] == 0) { PrintLcd("_", ii + 3, 1); } else if (ar_setup[ii] == 1) { PrintLcd("o", ii + 3, 1); } else if (ar_setup[ii] == 2) { PrintLcd("O", ii + 3, 1); } } ar_setup_scr[ii] = 0; } } } //------------------------------------------------------- else if (mode == STATUS_ERROR) { // строка 1: // анимацию не выводим, вместо анимации выводим оформление в колонках 1,2,3,4,5,6,7,8,9 if (force || face) { PrintLcd("=ERROR= ", 0, 0); } // строка 2: // температура ошибки выводится в колонках 12,13,14,15 // оформление выводим в колонки 1,2,3,4,5 // оформление температуры ошибки температувы выводится в колонки 6,7,8,9,10,11,16 term_set_display = OutNumber(Error_Term, Error_Term, 3, 1, 4, force); if (force || face) { PrintLcd(" T:", 0, 1); PrintLcd("c", 15, 1); } } }
файл Run.ino
//************************************************************************************************* // процедура расчитывает разогрев и включает, отключает нагрузку //************************************************************************************************* void Run(unsigned long time_loop_new) { int temp_pow; int i; int i1; //-------------------------------------------------------------------- if (mode == STATUS_RUN) { if (si < 10) { si = si + 1; } else { si = 0; LogSerial(5); } // вывод лога 1 раз в 10 циклов (при текущих настройках TIME_RUN это 1 раз в 1 секунду) if (step_run == 0) { // иницилизируем дефолтные параметры расчета r_pow = GetParamProc(1, term_set); r_term = GetParamProc(2, term_set); r_dt = GetParamProc(3, term_set); r_step = GetParamProc(4, term_set); // дополнительные коэффициенты (плюсуются к основным) rk_pow = 0; LogSerial(10); step_run = 1; } else if (step_run == 1) { // ожидание стабилизации температуры, предотвращает перегрев нагревателей при открытом муфеле off_Relay(); if (stability == true) { time_heat_start = time_loop_new; step_run = 2; } else { Beep(2); } } else if (step_run == 2) { // разогрев с мощностью 100% if (term_real_new < (term_set - r_term)) { on_off_heat(10000, r_dt * 1000, time_loop_new); } else { time_heat_start = time_loop_new; step_run = 3; } } else if (step_run == 3) { // ожидание остывания до установленной температуры if (term_real_new >= term_set) { off_Relay(); } else { time_heat_start = time_loop_new; time_run_step = time_loop_new; step_run = 4; } } else if (step_run == 4) { // поддержание температуры if (term_real_new > (term_set + TERM_GRADIENT)) { // явный перегрев, идем к предыдущему шагу step_run = 3; } else if ( (getDelayTime(time_heat_start, time_loop_new) >= (1000.0 * r_dt)) // закончен малый цикл on-off && (getDelayTime(time_run_step, time_loop_new) >= (1000.0 * r_step))) { // и прошло время перебега // тут все равно попали мы в диапазон или нет, все равно вносим корректор... temp_pow = 1.0 * term_set / term_real_new * (100.0 * r_pow + rk_pow); rk_pow = temp_pow - (100 * r_pow + rk_pow); // начинаем новый малый цикл on-off, малый цикл меняется дополнительно в процедуре on_off_heat time_heat_start = time_loop_new; // начинаем сново считать время перебега time_run_step = time_loop_new; } on_off_heat(100 * r_pow + rk_pow, r_dt * 1000, time_loop_new); } else { off_Relay(); } } //-------------------------------------------------------------------- else if (mode == STATUS_SETUP_RUN) { // выбор полки калибровки // надо проверить может мы отстали... проверяем всегда, без учета степов for (byte i=setup_step; i <= 9; i++) { setup_step = i; if (ar_setup[setup_step] != 2) { break; // все нормально, можно идти дальше (выходим из цикла) } } if (setup_step == 9) { if (ar_setup[setup_step] == 2) { // это конец калибровки, переходим в режим SETUP mode = STATUS_SETUP; off_Relay(); Beep(1); // после изменения режима нужно полностью обновить экран screen_out(true, true, true); LogSerial(2); return; } } // полка выбрана, идем дальше теперь собственно этапы калибровки if (step_run == 1) { // этап 1. - разогрев до температуры полки if (term_real_new < ar_temp[setup_step]) { // до температуры калибровки еще не дошли, греем дальше и запоминаем время on_off_heat(10000, ar_dt[setup_step] * 1000, time_loop_new); time_setup_t1 = time_loop_new; } else { off_Relay(); step_run = 2; LogSerial(7); } } else if (step_run == 2) { // этап 2. - ожидание потолка перебега off_Relay(); if (term_real_new >= (term_setup_max-1)) { // идет перебег, нагрев нужно выключить, и запомнить температуру (-1 это на дребезг темппературы) if (term_real_new > term_setup_max) { term_setup_max = term_real_new; time_setup_t2 = time_setup_t1; } } else { // начали остывать, температура опустилась ниже максимума на 2 градуса, реальное время - середина между time_setup_t2 и time_setup_t1 // пишем результаты if (ar_tep[setup_step] != (term_setup_max - ar_temp[setup_step])) { ar_tep[setup_step] = term_setup_max - ar_temp[setup_step]; Set_tep(setup_step, ar_tep[setup_step]); // пишем температуру перебега } i = getDelayTime(time_setup_t1, time_loop_new) / 1000; i1 = getDelayTime(time_setup_t2, time_loop_new) / 1000; i = (i + i1) / 2; if (ar_step[setup_step] != i) { ar_step[setup_step] = i; Set_step(setup_step, ar_step[setup_step]); } ar_setup[setup_step] = 1; ar_setup_scr[setup_step] = 1; Set_setup(setup_step, ar_setup[setup_step]); pow_setup_max = 100; pow_setup_min = 0; pow_setup = 0; step_run = 3; LogSerial(8); } } else if (step_run == 3) { // этап 3. - остывание ниже градиента off_Relay(); if (term_real_new <= (ar_temp[setup_step] - TERM_GRADIENT)) { // остыли, будущую мощность выберем как среднюю между pow_setup_max и pow_setup_min pow_setup = (pow_setup_min + pow_setup_max) / 2; // запомним время перехода в 4 режим time_setup_t1 = time_loop_new; // нагрев был выключен, значит запуск этапа 4 нужно начинать со старта mode_heat = true; time_heat_start = time_loop_new; on_Relay(); step_run = 4; LogSerial(9); } } else if (step_run == 4) { // этап 4. - нагреваем с выбраной ранее мощностью if (term_real_new > (ar_temp[setup_step] + TERM_GRADIENT)) { // явный перегрев, идем к предыдущему шагу off_Relay(); pow_setup_max = pow_setup; step_run = 3; } else if ( (getDelayTime(time_heat_start, time_loop_new) < (1000.0 * ar_dt[setup_step])) // не закончен малый цикл on-off отсчитаного от времени включения реле || (getDelayTime(time_setup_t1, time_loop_new) < (2000.0 * ar_step[setup_step]))) { // или не прошло двойное время перебега on_off_heat(pow_setup * 100, ar_dt[setup_step] * 1000, time_loop_new); } else if ( ((pow_setup_max - pow_setup_min) == 1) // разница между режимами перегрева и негорева 1%, дальше оптимизировать нечего || (term_real_new >= (ar_temp[setup_step] - TERM_GRADIENT))) { // или мы попали в диапазон градиента // мощность подобрана правильно // этап 2 завершен, пишем результаты if (ar_pow[setup_step] != (pow_setup)) { ar_pow[setup_step] = pow_setup; if (ar_pow[setup_step] > 100) {ar_pow[setup_step] = 100;} Set_pow(setup_step, ar_pow[setup_step]); } ar_setup[setup_step] = 2; ar_setup_scr[setup_step] = 1; Set_setup(setup_step, ar_setup[setup_step]); step_run = 1; // по сколько этот этап мы прошли, следующий идем с первого шага LogSerial(10); } else { // это недогрев, меняем параметры и продолжаем pow_setup_min = pow_setup; pow_setup = (pow_setup_min + pow_setup_max) / 2; time_setup_t1 = time_loop_new; step_run = 3; } } else { off_Relay(); } } //-------------------------------------------------------------------- else { off_Relay(); } }
файл other.ino
// --------------------------------------------------------- // вспомогательные процедуры // --------------------------------------------------------- //************************************************************************************************* // процедура включает и выключает нагрев в зависимости от необходимой мощности // p - относительной мощность сотые доли % (допустимые значения от 0 до 10 000) // dt - общее время одного цикла вкл/выкл (в мсек, допустимые значения от 0 до 32 767), это время делется пропорционально параметра "p" // 100% мощность этого времени не должно приводить к перебегу температуры выше разумной погрешности, // по этому общее время одного цикла должно расчитыватся заранее. // t - текущее время //************************************************************************************************* void on_off_heat(int p, int dt, unsigned long t) { unsigned long t1 = 1; if (p >= 9900) { // 99% мощности считаем как 100% в целях сохранения ресурса реле mode_heat = true; time_heat_start = t; on_Relay(); } else if (p <= 100) { // 1% мощности считаем 0% в целях сохранения ресурса реле mode_heat = false; time_heat_stop = t; off_Relay(); } else if (mode_heat == true) { // идет нагрев, нужно проверить может стоит остановится t1 = t1*dt/100*p/100; if (getDelayTime(time_heat_start, t) >= t1) { mode_heat = false; time_heat_stop = t; off_Relay(); } } else if (mode_heat == false) { // идет охлаждение, нужно проверить может стоит запустится t1 = t1*dt/100*(100-p/100); if (getDelayTime(time_heat_stop, t) >= t1) { mode_heat = true; time_heat_start = t; on_Relay(); } } } void on_Relay() { //Serial.println("on reley"); digitalWrite(heatCTRL, HIGH); } void off_Relay() { //Serial.println("off reley"); digitalWrite(heatCTRL, LOW); } //************************************************************************************************* // процедура выводит на экран число и возвращает новое значение // num - текущее значение числа // new_num - новое значение числа // col - номер позиции в строке экрана для вывода // line - номер строки экрана для вывода // count - количество символов на экране для вывода // force - параметр для явного обновления //************************************************************************************************* int OutNumber(int num, int new_num, byte col, byte line, byte count, boolean force) { String stringOne; int str_l; if (force || (new_num != num)) { num = new_num; stringOne = String(num); str_l = count - stringOne.length(); for (int i=1; i <= str_l; i++){ stringOne = String(" ") + stringOne; } PrintLcd(stringOne, col, line); } return num; } //************************************************************************************************* // процедура возвращает значение нажатой клавиши на клавиатуре //************************************************************************************************* int getPressedButton() { int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0) if (buttonValue < 100) { return BUTTON_RIGHT; } else if (buttonValue < 200) { return BUTTON_UP; } else if (buttonValue < 400){ return BUTTON_DOWN; } else if (buttonValue < 600){ return BUTTON_LEFT; } else if (buttonValue < 800){ return BUTTON_SELECT; } else if (buttonValue >= 800){ return BUTTON_NONE; } return BUTTON_NONE; } //************************************************************************************************* // процедура сравнивает два времени и возвращает разницу в виде числа, учитывает переход времени через 0 // start_time - начальное время // end_time - конечное время // // !!!! процедура чуствительна к разрядности исполняемого кода !!!! // !!!! процедура может работать неправильно при двойном переходе времени через 0 !!!! //************************************************************************************************* unsigned long getDelayTime(unsigned long start_time, unsigned long end_time) { unsigned long result; if (start_time <= end_time) { result = end_time - start_time; } else { result = 4294967295 - end_time + start_time; } return result; } //************************************************************************************************* // процедура увеличивает или умеьшает значение разряда числа // num - начальное число // count - максимальная разрядность числа // nb - номер разряда, считам с лево на право, например: num=2, count=4, nb=2 считаем разряд указаный "X" 0X02. Результат увеличения будет число 102. // inc - флаг увеличение/уменьшения, если true - то увеличиваем //************************************************************************************************* int ChangeValue(int num, int count, int nb, boolean inc) { int result; String stringOne; int str_l; int sim; // сначала добьем нулями разрядность stringOne = String(num); str_l = count - stringOne.length(); for (int i=1; i <= str_l; i++){ stringOne = String("0") + stringOne; } // теперь изменяем нужный разряд sim = int(stringOne.charAt(nb-1)); if ((inc == true) && (sim < 57)) { sim = sim + 1; } else if ((inc == false) && (sim > 48)) { sim = sim - 1; } // запаковываем обратно в число stringOne.setCharAt(nb-1, char(sim)); result = stringOne.toInt(); return result; } //************************************************************************************************* // процедура выводит на экран заранее подготовленую строку, при этом выключает режим "Blink" // str - строка или String для вывода // col - номер позиции в строке экрана для вывода // line - номер строки экрана для вывода //************************************************************************************************* void PrintLcd(String str, int col, int line) { if (FlagCursor == true) { lcd.noBlink(); FlagCursor = false; } lcd.setCursor(col, line); lcd.print(str); } //************************************************************************************************* // процедура читает из энерго независимой памяти двухбйтовое число // num - идентификатор памяти (номер двухбайтного слова) //************************************************************************************************* int ReadInt(int num) { byte a; byte b; int result; a = int(EEPROM.read(num * 2)); b = int(EEPROM.read(num * 2 + 1)); result = a * 255 + b; return result; } //************************************************************************************************* // процедура записывает в энерго независимую память двухбйтовое число // num - идентификатор памяти (номер двухбайтного слова) // value - записываемое значение //************************************************************************************************* void WriteInt(int num, int value) { byte a; byte b; b = value % 255; a = (value - b) / 255; EEPROM.write(num * 2, a); EEPROM.write(num * 2 + 1, b); Beep(4); // сделано для выявления безконтрольной записи и снижения ресурса EEPROM } //************************************************************************************************* // процедура проверяет новую установленую температуру и сохраняет ее //************************************************************************************************* void set_term_set() { if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } if (term_set_new != term_set) { // применяем новую температуру и запоминаем ее term_set = term_set_new; Set_term_set(term_set); } } //************************************************************************************************* // две процедуры, для получения данных с термопары //************************************************************************************************* double readCelsius() { uint16_t v; digitalWrite(thermoCS, LOW); _delay_ms(1); v = spiread(); v <<= 8; v |= spiread(); digitalWrite(thermoCS, HIGH); if (v & 0x4) { // uh oh, no thermocouple attached! //return NAN; return -101; } v >>= 3; return v*0.25; } byte spiread() { int i; byte d = 0; for (i=7; i>=0; i--) { digitalWrite(thermoCLK, LOW); _delay_ms(1); if (digitalRead(thermoDO)) { //set the bit to 0 no matter what d |= (1 << i); } digitalWrite(thermoCLK, HIGH); _delay_ms(1); } return d; } //************************************************************************************************* // процедура получает апромиксированое значение по температуре из эталонных массивов // NumParam - номер массива, 1 - ar_pow, 2 - ar_tep, 3 - ar_dt, 4 - ar_step // Term - температура к которой нужно получить значение //************************************************************************************************* int GetParamProc(byte NumParam, int Term){ int ir = 0; byte ii; byte i1; byte i2; // найдем индексы i1 = 0; for (ii=0; ii <= 9; ii++) { if (ar_temp[ii] > Term) { ii = ii - 1; break; } } if (ii <= 0) { i1 = 0; i2 = 1; } else if (ii >=9) { i1 = 8; i2 = 9; } else { i1 = ii; i2 = ii + 1; } // в i1 и i2 лежат индексы для массивов, начинаем апромиксацию if (NumParam == 1) { ir = ar_pow[i1] + 1.0 * (Term - ar_temp[i1]) * (ar_pow[i2] - ar_pow[i1]) / (ar_temp[i2] - ar_temp[i1]); } if (NumParam == 2) { ir = ar_tep[i1] + 1.0 * (Term - ar_temp[i1]) * (ar_tep[i2] - ar_tep[i1]) / (ar_temp[i2] - ar_temp[i1]); } if (NumParam == 3) { ir = ar_dt[i1] + 1.0 * (Term - ar_temp[i1]) * (ar_dt[i2] - ar_dt[i1]) / (ar_temp[i2] - ar_temp[i1]); } if (NumParam == 4) { ir = ar_step[i1]+ 1.0 * (Term - ar_temp[i1]) * (ar_step[i2]- ar_step[i1]) / (ar_temp[i2] - ar_temp[i1]); } return ir; } //************************************************************************************************* // процедуры, для записи и получении всех данных EEPROM // собраны здесь для единообразного адресного хранения и визуализации карты использования EEPROM, // адреса в двух байтовом виде (1 - это два байта с физическим адресом 2 и 3) //************************************************************************************************* // ------------------- чтение ------------------- int Get_signature() { return ReadInt(0); } // 0 - сигнатура int Get_term_set() { return ReadInt(1); } // 1 - установленая температура // 2...9 - резерв int Get_temp(int Num) { return ReadInt(Num+10); } // 10..19 - массив калибровочных температур int Get_pow(int Num) { return ReadInt(Num+20); } // 20..29 - массив мощности поддержания температуры int Get_tep(int Num) { return ReadInt(Num+30); } // 30..39 - массив температур инерционности int Get_dt(int Num) { return ReadInt(Num+40); } // 40..49 - массив времени цикла on/off int Get_step(int Num) { return ReadInt(Num+50); } // 50..59 - массив времени инерционности int Get_setup(int Num){ return ReadInt(Num+60); } // 60..69 - массив флагов калибровки // 70..99 - резерв // 100..511 - свободно // ------------------- запись ------------------- int Set_signature(int value) { WriteInt(0, value); } // 0 - сигнатура int Set_term_set(int value) { WriteInt(1, value); } // 1 - установленая температура // 2...9 - резерв void Set_temp(int Num, int value) { WriteInt(Num+10, value); } // 10..19 - массив калибровочных температур void Set_pow(int Num, int value) { WriteInt(Num+20, value); } // 20..29 - массив мощности поддержания температуры void Set_tep(int Num, int value) { WriteInt(Num+30, value); } // 30..39 - массив температур инерционности void Set_dt(int Num, int value) { WriteInt(Num+40, value); } // 40..49 - массив времени цикла on/off void Set_step(int Num, int value) { WriteInt(Num+50, value); } // 50..59 - массив времени инерционности void Set_setup(int Num, int value) { WriteInt(Num+60, value); } // 60..69 - массив флагов калибровки // 70..99 - резерв // 100..511 - свободно
файл OnKeyUp.ino
//************************************************************************************************* // обработчик вызывается при отпускании кнопки клавиатуры // key - кнопка которая была отпущена // pressing - время которое кнопка была нажата //************************************************************************************************* void OnKeyUp (int key, unsigned long pressing) { int i ; if (key == BUTTON_SELECT) { // реализуем переход режима работы контроллера // ******************** переход в режим RUN ******************** if (mode == STATUS_STOP && pressing < BUTTON_TIME_LONG) { set_term_set(); // перед стартом применим (и сохраним) установленую температуру mode = STATUS_RUN; // установка режима контроллера step_run = 0; // установка этапа выполнения Beep(3); } // ******************** переход в режим SETUP ******************** else if ( (mode == STATUS_STOP && pressing >= BUTTON_TIME_LONG) || (mode == STATUS_SETUP_RUN)) { mode = STATUS_SETUP; off_Relay(); } // ******************** переход в режим STOP ******************** else if ( (mode == STATUS_ERROR) || (mode == STATUS_RUN) || (mode == STATUS_SETUP && pressing < BUTTON_TIME_LONG)) { mode = STATUS_STOP; off_Relay(); Error_Term = 0; Error_Number = 0; } // ******************** переход в режим SETUP_RUN ******************** else if ((mode == STATUS_SETUP) && (pressing >= BUTTON_TIME_LONG)) { // перед калибровкой применим (и сохраним) установленую линейку порогов for (byte i=0; i <= 9; i++) { if (Get_setup(i) != ar_setup[i]) { Set_setup(i, ar_setup[i]); } } mode = STATUS_SETUP_RUN; step_run = 1; term_setup_max = 0; pow_setup_max = 100; setup_step = 0; Beep(3); } // после изменения режима нужно полностью обновить экран screen_out(true, true, true); } else if (key == BUTTON_RIGHT) { if (mode == STATUS_STOP) { // переключаем редактируемый разряд установленой температуры if (edit_symbol >= 4) { edit_symbol = 0; } else { edit_symbol = edit_symbol + 1; } if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } screen_out(false, false, false); } else if (mode == STATUS_SETUP) { if (edit_symbol >= 10) { edit_symbol = 0; } else { edit_symbol = edit_symbol + 1; } screen_out(false, false, false); } } else if (key == BUTTON_LEFT) { if (mode == STATUS_STOP) { // переключаем редактируемый разряд установленой температуры if (edit_symbol == 0) { edit_symbol = 4; } else { edit_symbol = edit_symbol - 1; } if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } screen_out(false, false, false); } else if (mode == STATUS_SETUP) { if (edit_symbol == 0) { edit_symbol = 10; } else { edit_symbol = edit_symbol - 1; } screen_out(false, false, false); } } else if (key == BUTTON_UP) { if ((mode == STATUS_STOP) && (edit_symbol != 0)) { // мы редактируем текущий разряд установленой температуры term_set_new = ChangeValue(term_set_new, 4, edit_symbol, true); if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } } else if (mode == STATUS_SETUP) { switch (ar_setup[edit_symbol - 1]) { case 0: ar_setup[edit_symbol - 1] = 2; break; case 1: ar_setup[edit_symbol - 1] = 2; break; case 2: ar_setup[edit_symbol - 1] = 0; break; } ar_setup_scr[edit_symbol - 1] = 1; } } else if (key == BUTTON_DOWN) { if ((mode == STATUS_STOP) && (edit_symbol != 0)) { // мы редактируем текущий разряд установленой температуры term_set_new = ChangeValue(term_set_new, 4, edit_symbol, false); if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } } else if (mode == STATUS_SETUP) { switch (ar_setup[edit_symbol - 1]) { case 0: ar_setup[edit_symbol - 1] = 2; break; case 1: ar_setup[edit_symbol - 1] = 0; break; case 2: ar_setup[edit_symbol - 1] = 0; break; } ar_setup_scr[edit_symbol - 1] = 1; } } }
файл LogSerial.ino
//************************************************************************************************* // процедура выводит в порт лог глобальных переменных, собрано здесь для удобства включения/отключения // и уменьшения текстов основной программы, Num - номер вызова лога //************************************************************************************************* void LogSerial(byte Num) { int i; // if (false) { if (true) { switch (Num) { case 0: Serial.print("=debug (setup.Start)="); Serial.println(); break; case 1: Serial.print("=debug (setup.FORMAT)="); Serial.println(); break; case 2: Serial.print("=debug (setup.ActualTable)="); Serial.println(); for (byte i=0; i <= 9; i++) { Serial.print("i: "); Serial.print(i); Serial.print(", ar_temp: "); Serial.print(ar_temp[i]); Serial.print(", ar_pow: "); Serial.print(ar_pow[i]); Serial.print(", ar_tep: "); Serial.print(ar_tep[i]); Serial.print(", ar_dt: "); Serial.print(ar_dt[i]); Serial.print(", ar_step: "); Serial.print(ar_step[i]); Serial.print(", ar_setup: "); Serial.print(ar_setup[i]); Serial.println(); } break; case 3: Serial.print("=debug (setup.setup)= Complete."); Serial.println(); break; case -1: Serial.print("=debug (Run.Run.0)="); Serial.print(" term_set: "); Serial.print(term_set); Serial.print(", r_pow: "); Serial.print(r_pow); Serial.print(", r_term: "); Serial.print(r_term); Serial.print(", r_dt: "); Serial.print(r_dt); Serial.print(", r_step: "); Serial.print(r_step); Serial.println(); break; //Serial.print("=debug (Run.Run.0)="); //Serial.print(", step_run: "); Serial.print(step_run); //Serial.print(", term_real_new: "); Serial.print(term_real_new); //Serial.print(", rk_pow: "); Serial.print(rk_pow); //Serial.println(); //break; case 5: Serial.print("=debug (Run.Run)="); Serial.print(" step_run: "); Serial.print(step_run); Serial.print(", term_set: "); Serial.print(term_set); Serial.print(", term_real_new: "); Serial.print(term_real_new); Serial.print(", rk_pow: "); Serial.print(rk_pow); Serial.println(); break; case 6: Serial.print("=debug (Run.Run.0)="); Serial.print(" term_set: "); Serial.print(term_set); Serial.print(", r_pow: "); Serial.print(r_pow); Serial.print(", r_term: "); Serial.print(r_term); Serial.print(", r_dt: "); Serial.print(r_dt); Serial.print(", r_step: "); Serial.print(r_step); Serial.println(); break; case 7: Serial.print("=debug (Run.Setup.1)="); Serial.print(" setup_step: "); Serial.print(setup_step); Serial.print(", term_real_new: "); Serial.print(term_real_new); Serial.println(); break; case 8: Serial.print("=debug (Run.Setup.2)="); Serial.print(" setup_step: "); Serial.print(setup_step); Serial.print(", ar_tep[setup_step]: "); Serial.print(ar_tep[setup_step]); Serial.print(", ar_step[setup_step]: "); Serial.print(ar_step[setup_step]); Serial.println(); break; case 9: Serial.print("=debug (Run.Setup.3)="); Serial.print(" setup_step: "); Serial.print(setup_step); Serial.print(", term_real_new: "); Serial.print(term_real_new); Serial.println(); break; case 10: Serial.print("=debug (Run.Setup.End)="); Serial.print(" setup_step: "); Serial.print(setup_step); Serial.print(", term_real_new: "); Serial.print(term_real_new); Serial.print(", ar_pow[setup_step]: "); Serial.print(ar_pow[setup_step]); Serial.println(); break; } } }
файл Beep.ino
//************************************************************************************************* // процедура выводит на динамик звуки, сделана здесь, для удобства // Num - номер программы // 0 - нет звуков, // 1 - включение контроллера, // 2 - ошибка, // 3 - старт нагрева, // 4 - звук записи в EEPROM, //************************************************************************************************* void Beep(byte Num) { switch (Num) { case 0: // нет звуков noTone(beepCTRL); break; case 1: // включение контроллера tone(beepCTRL, 1000); delay(500); tone(beepCTRL, 4000, 500); break; case 2: // ошибка, tone(beepCTRL, 6000, 150); break; case 3: // старт нагрева, tone(beepCTRL, 300, 1000); break; case 4: // запись в EEPROM, tone(beepCTRL, 300, 100); break; } }
Отличная работа!
А Вы не думали использовать закон регулирования, я себе на дачу для конвектора собрал ПИ регулятор, (используеться медленный ШИМ на реле) отлично работает (+-0.03С).
Тут с ребятами разбирались (схема на семисторе с алгоритмом Брезенхема ВИДЕО), тоже отлично работает (+-0.5 но тут датчик температуры из диода на скорую руку)
Графики
Старый контроллер так и работал, но после ночного пробоя чуть пожар не случился (вовремя заметели) стал искать и другие варианты.
Я долго выбирал на чем делать, и остановился на реле по причине очень высокой инерционности (муфель закрытый), в виду этого инерционность (параметр ar_step) в среднем 20..30 секунд, была у меня идея использовать апромиксацию на основе кривой Бозье (легко считается и очень красиво работает), но в результате вышло все куда проще. Главное согласовать цикл вкл/выкл с остальными циклами (измерение, коррекция, ожидание).
Шим оправдан на муфеле с открытыми нагревателями :)
А как вы организовали проверку исправности термопары?
Мне с терморезистором надо чтонибудь придумать.
А как вы организовали проверку исправности термопары?
Мне с терморезистором надо чтонибудь придумать.
модуль "MAX6675 Module" сам контролирует проблеммы с термопарой....
программа, состоит из нескольких файлов
Можно вкратце, как их собрать в одну программу?
программа, состоит из нескольких файлов
Можно вкратце, как их собрать в одну программу?
создаешь папку "term" в нее кидаешь все файлы из сабжа, запускаешь файл "term.ino" откроется среда разработке с несколькими вкладками, на каждой вкладе будет свой файл.
если все правильно сделаешь, то будет так, при этом в один скетч ничего собирать не надо... очень удобно "прыгать" между основными блоками... Единственное ограничение нельзя запускать или открывать отдельно файлы отличные от "term.ino"
Спасибо, я так и делал, но вылезала куча ошибок при проверке. Сейчас всё нормально. Когда проверю на железе, ещё поспрашиваю??
Я сам керамист и подумывал над контроллером под разнообразные режимы обжига, но дальше простого термометра пока не продвинулся. И ещё, почему не твёрдотельное реле и MAX31855 ??
более правильно было-бы сделать ШИМ на семисторе,
можно и твердотельное, но я сторонник старых подходов....
сразу предупреждаю, что у китайцев с термопарами беда, тип К фиг найдешь, я 2шт купил и обе не те, MAX31855 конечно лучше, но в принцепе для меня пофиг...
вообще я делал как можно проще :)
ну и еще, если будешь использовать, то желательно (хотя и не обязательно) калибровку вручную докрутить а потом занести ручные результаты, я делал что-бы графики получились "правильными", на автомате она конечно калибруется но довольно средненько...
Собрал на макетке, вроде работает, непонятно только только с меню, там где нули, это зачем?
Собрал на макетке, вроде работает, непонятно только только с меню, там где нули, это зачем?
это настройка калибровки, там 10 нулей, по одному нулю на каждую температуру калибровки, "0" означает, что полку калибровать не нужно (предположительно она уже откалибрована). "_" это признак того, что калибровать заданую полку нужно...
для начала калибровки нужно установить "_" в желаемом диапазоне (можно и все, но я советую начинать с малого) и опять 5 удерживать красную кнопку до писка (5 сек) после этого запустится режим калибровки для диапазонов отмеченых "_"
Здравствуйте, как раз делаю контроллер муфельной печи, но разбирать чужой код ужас как не люблю. Не могли бы вы описать логику вашей программы. Если быть точнее логика и так понятна. Хотелось бы получить разъяснения по вашей таблице. А то у меня как раз с этим затык случился))). Идей миллион, но "всё смешалось в датском королевстве"))). Заранее благодарю.
если не хочется разбиратся - есть функция автокалибровки, она сама заполнится более менее правильными значениями...
Здравствуйте. Возможно дабавить в схему еще 5 термопар и 5 датчиков?
Насколько измениться код программы?
Прошу прощения "... и 5 тэнов?"
Прошу прощения "... и 5 тэнов?"
смотря что Вы хотите от этих 5 тенов, напишите подробнее
Плавильная печь для вытяжки металла из расплава.
При уменьшении уровня метала в тигеле тэны выключаются для уменьшения воздействия температуры на сам стержень.
На примере индукционки…
Только вместо индукции спирали (5 шт) и вытянутый тигель.
эмммм... я конкретно с такой технологией никогда не работал, но думаю там все совсем не просто....
судя по всему там важен не нагрев а отвод тепла от вытягиемого металла.
я бы не взялся решать такую задачу без теоретической подготовки не смотря на институтское образование "машиностроение и металообработка" и приличного опыта во всяких расплавах и прочем похожем...
задачка достойна как минимум докторской десертации....
добавлю - переход металов из одного фазового состояния в другое выделяет или поглащает энергию, по этому контроль температуре в зоне разных фаз должен быть какой-то очень хитрый, иначе либо жидкий метал встанет колом или заготовка стечет....
Спасибо за коментарий. Но Вы так и неответили о возможности доработки Вашей схемы.
Алгоритм простой - по мере уменьшения веса тигеля снижается температура нагревателя 1 потом 2 и т.д., а система охлождения уже готова. Остался вопрос о нагреве и плавном остывании.
Спасибо за коментарий. Но Вы так и неответили о возможности доработки Вашей схемы.
Алгоритм простой - по мере уменьшения веса тигеля снижается температура нагревателя 1 потом 2 и т.д., а система охлождения уже готова. Остался вопрос о нагреве и плавном остывании.
думаю моя схема слишком инерционна для подобного аппарата...
но если все-же делать на моей схеме - то ее можно оставить вообще без изменения, ее задача поддержка температуры в тигле и не более, а вот задачу с изменением уровня расплава в тигле я предлагаю решать по другому, пусть сам тигель поднимается по мере необходмости и верхняя часть выходит из зоны нагрева.
конкретику отвода тепла снизу можно думать исходя из материала тигеля, например для графитовых - можно оставить просто в виде гильзы, для корундов немного сложнее, видимо придется городить некое охлаждение снизу...
короче я-бы решал задачу чисто механическими средствами, всякими экранами и прочим...
Идея хорошая, но конструктивно уже все подготовлено включая охлаждение. Для проектирования и перерасчета новой установки понадобиться много времени и средств. Поэтому и ищу схему для управления почти готовой печи.
Тогда может посоветуете где поискать такую схему?
Не совсем понял как калибровку вручную докрутить, изменяю значения масива - заливаю на мк - значения НЕ изменяються, из эпрома читаються старые. Если сделать автокалибровку значения в эпроме изменяються. Возможно что-то не так делаю, подскажите пжл.
Здравствуйте!
Я ищу программиста для написания (платно) скетча подобного контроллера для электропечи. Обращаюсь к Вам, поскольку Вы в теме. Если Вас это заинтересует, то напишите, пожалуйста, на jarovne@mail.ru. Я вышлю Вам техзадание, после ознакомления с ним Вы назначите цену.