Контроллер муфельной печи.
- Войдите на сайт для отправки комментариев
Была у меня старая муфельная печь со сгоревшим блоком управления. Пришли две дочки и со слезами на глазах вывалили на стол всякие поделки из глины, и говорят - обжечь надо-бы. Стал я думать гадать, и вспомнил про старую печь. Ну тут и понеслось...
Потратил всего 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. Я вышлю Вам техзадание, после ознакомления с ним Вы назначите цену.