Контроллер муфельной печи.
- Войдите на сайт для отправки комментариев
Была у меня старая муфельная печь со сгоревшим блоком управления. Пришли две дочки и со слезами на глазах вывалили на стол всякие поделки из глины, и говорят - обжечь надо-бы. Стал я думать гадать, и вспомнил про старую печь. Ну тут и понеслось...
Потратил всего 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
001 | // --------------------------------------------------------- |
002 | // управление муфельной печью модуль "term" |
003 | // |
004 | // состав проекта: |
005 | // Arduino R3 UNO |
006 | // LCD 1602 Keypad shield |
007 | // MAX6675 Module + K Type Thermocouple Sensor for Arduino |
008 | // UN3F SLA-05VDC-SL-A 5V 30A Relay Module For Arduino |
009 | // |
010 | // версия среды Arduino 1.5.2 |
011 | // |
012 | // автор <a href="mailto:vde69@mail.ru">vde69@mail.ru</a> (с), процедуры главного алгоритма |
013 | // --------------------------------------------------------- |
014 |
015 | // добавляем необходимые библиотеки |
016 | |
017 | #include <EEPROM.h> |
018 | #include <LiquidCrystal.h> |
019 | #include <util/delay.h> |
020 |
021 | // общие настройки |
022 | const int TIME_GET_THERMOMETR = 1000; // время шага опроса термодатчиков |
023 | const int TIME_GET_ERROR = 1000; // время шага диагностики ошибок |
024 | const int TIME_ANIME = 300; // время шага анимации |
025 | const int TIME_RUN = 100; // время шага расчета действий по включению/выключению нагрузки |
026 | const int TIME_STAB = 2000; // время расчета скорости разогрева/остывания, время должно выбиратся достаточным для игнорирования дребезга температуры |
027 | const int BUTTON_TIME = 20; // время между нажатием и отпусканием (устранение дребезга) |
028 | const int BUTTON_TIME_LONG = 5000; // время перевода в режим SETUP |
029 | const int TERM_LIMIT = 1100; // предельная температура контролера, установить температуру выше нельзя, при реальном превышении будет издавать звук |
030 | const byte TERM_GRADIENT = 5; // допустимый градиент температуры (от установленой), влияет на процесс калибровки, рекомендуется от 5 до 20 (5 - самая точная калибрвка) |
031 | const byte TERM_LIMIT_SPEED = 2; // допустимая скорость разогрева или остывания (градус/сек), используется для определения открытия/закрытия печи |
032 | // для текущей печи реальная максимальная скорость разогрева 2/3 градус сек, предел выбран в 3 раза больше реальной скорости |
033 |
034 | // статусы нажатых кнопок, определить несколько нажатых кнопок одновременно - нельзя |
035 | const int BUTTON_RIGHT = 1; // 0 < 100 |
036 | const int BUTTON_UP = 2; // 148 < 200 |
037 | const int BUTTON_DOWN = 3; // 338 < 400 |
038 | const int BUTTON_LEFT = 4; // 515 < 600 |
039 | const int BUTTON_SELECT = 5; // 750 < 800 |
040 | const int BUTTON_NONE = 0; // 1023 |
041 |
042 | // статусы режимов работы |
043 | const byte STATUS_STOP = 1; // отображаем текущую и установленую температуру, |
044 | // установленую температуру можно редактировать, |
045 | // нагрузка не включается |
046 | // переход возможен в статусы RUN и SETUP |
047 | |
048 | const byte STATUS_RUN = 2; // отображаем текущую и установленую температуру, |
049 | // установленую температуру нельзя редактировать, |
050 | // нагрузка включается для поддержания установленой температуры |
051 | // переход возможен только в статус STOP |
052 |
053 | const byte STATUS_SETUP = 3; // отображаем линейку настройки и вариант выхода STOP/START |
054 | // линейку и вариант выхода можно редактировать, |
055 | // нагрузка не включается, текущая температура отображается |
056 | // переход возможен только статус STOP или SETUP_RUN |
057 |
058 | const byte STATUS_SETUP_RUN = 4; // отображаем линейку выполнения настройки контроллера, текущий этап |
059 | // нагрузка включается для определения параметров калибровки, текущая температура отображается |
060 | // переход возможен только в статус SETUP |
061 |
062 | const byte STATUS_ERROR = 5; // отображаем описание ошибки, |
063 | // нагрузка не включается, текущая температура отображается |
064 | // переход возможен только в статус STOP |
065 |
066 | // -------------------------------------------------------- |
067 | // объявления для индикаторов и кнопок управления |
068 | // |
069 |
070 | LiquidCrystal lcd(8, 9, 4, 5, 6, 7 ); |
071 |
072 | // переменные для поддержки циклов анимации |
073 | unsigned long time_anime = 0; |
074 | unsigned long time_anime_new = 0; |
075 | int step_anime = 0; |
076 |
077 | // переменные для поддержки циклов клавиатуры |
078 | unsigned long time_button = 0; |
079 | unsigned long time_button_new = 0; |
080 |
081 | // переменные для поддержки циклов опроса датчиков |
082 | unsigned long time_temp = 0; |
083 | unsigned long time_temp_new = 0; |
084 |
085 | // переменные для поддержки циклов включения/выключения нагревателя и расчетов |
086 | unsigned long time_run = 0; |
087 | unsigned long time_run_new = 0; |
088 | unsigned long time_run_step = 0; |
089 |
090 | // переменные для контроля, изменения и отображения температур |
091 | // конкретные значения могут переустанавливатся (из сохраненых в памяти) при старте системы |
092 | int term_set = 0; // установленая температура |
093 | int term_set_display = 0; // установленая температура выведеная на дисплей |
094 | int term_set_new = 0; // новая установленая температура |
095 | int term_real = 0; // текущая температура полученая от датчиков |
096 | int term_real_display = 0; // текущая температура выведеная на дисплей |
097 | int term_real_new = 0; // новая температура полученая от датчиков |
098 |
099 | // переменные для поддержки клавиатуры и экрана |
100 | int button = BUTTON_NONE; // текущаяя нажатая клавиша |
101 | int button_new = BUTTON_NONE; // новое значение нажатой клавиши |
102 | boolean FlagCursor = false ; // кеширующий флаг обозначающий, что включен режим курсора "Blink" |
103 |
104 | // переменные для поддержки статусов контроллера |
105 | byte mode = STATUS_STOP; // текущий статус работы контролера |
106 | int edit_symbol = 0; // номер символа в поле редактирования на экране |
107 | // для температуры 1...4 |
108 | // для полок калибровки 1..10 |
109 | // 0-не редактируется, курсор не показывается |
110 |
111 | // переменные для общения с термопарой |
112 | int thermoDO = 17; // А3 он же SO |
113 | int thermoCS = 16; // A2 |
114 | int thermoCLK = 15; // A1 он же SCK |
115 |
116 | // переменные для реле |
117 | int heatCTRL = 18; // A4 |
118 |
119 | // переменные для динамика |
120 | int beepCTRL = 19; // A5 |
121 |
122 | // переменные для расчетов |
123 | int r_pow; // текущий коэффициэнт (сотая доля процента, 10000 = 100%) относительной мощность для поддержания планируемой температуры |
124 | int r_term; // текущий перебег (градусы), время перебега при нагреве до планируемой температуры со 100% мощностью |
125 | int r_dt; // время цикла on/off сек |
126 | int r_step; // время шага хранения истории сек (для апроксимации поддержания температуры) |
127 |
128 | // дополнительные коэффициэнты (сотая доля процента, 10000 = 100%) |
129 | int rk_pow; |
130 | int rk_term; |
131 |
132 | boolean mode_heat = false ; // флаг показывающий включена или нет нагрузка |
133 | unsigned long time_heat_start = 0; // время последнего включения нагрузки |
134 | unsigned long time_heat_stop = 0; // время последнего отключения нагрузки |
135 |
136 | // структуры которые хранят примерные таблицы тепловых характеристик |
137 | int ar_temp[10] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000}; // температуры градусы |
138 | int ar_pow[10] = { 4, 6, 12, 20, 30, 42, 53, 63, 70, 75}; // мощность процент |
139 | int ar_tep[10] = {30, 23, 18, 14, 11, 9, 7, 6, 5, 5}; // температура перебега |
140 | int ar_dt[10] = {30, 25, 20, 20, 20, 20, 20, 20, 20, 20}; // время цикла on/off нагрузки сек |
141 | int ar_step[10] = {120, 90, 70, 55, 42, 33, 24, 20, 18, 18}; // время перебега температуры (инерционность) сек |
142 | int ar_setup[10] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; // резерв для вычислений при выполнения SETUP |
143 | byte ar_setup_scr[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // признаки для обновления на экране элементов массива ar_setup |
144 |
145 | byte setup_step; |
146 |
147 | // переменные для расчетов |
148 |
149 | // переменные используемые только при калибровке, пишу сюда, что-бы были доступны для логов |
150 | unsigned long time_setup_t1; // время достижения калибровочной температуры, или время запуска нагрева на втором этапе калибровки |
151 | unsigned long time_setup_t2; // время достижения калибровочной температуры, или время запуска нагрева на втором этапе калибровки |
152 | int term_setup_max; // максимальная температура (поиск перебега) |
153 | int pow_setup_max; // минимальная мощность на которой проявился перегрев |
154 | int pow_setup_min; // максимальная мощность на которой проявился недогрев |
155 | int pow_setup; // текущая мощность |
156 | byte step_run; // текущий режим работы или калибровки |
157 | // 0 - нет |
158 | // 1 - первичный разогрев |
159 | // 2 - поиск перебега |
160 | // 3 - остываение ниже градиента |
161 | // 4 - вторичный разогрев для поиска оптимальной мощности поддержания температуры |
162 | // текущий режим работы |
163 | // 0 - инициализация |
164 | // 1 - ожидание стабилизации |
165 | // 2 - первичный разогрев |
166 | // 3 - ожидание остывания до текущей температуры |
167 | // 4 - поддержание температуры |
168 |
169 | // переменные для ошибок |
170 | int Error_Term; // температура на которой зафиксирована ошибка |
171 | int Error_Number; // номер ошибки |
172 | unsigned long time_error; // время последнего опроса ошибок |
173 |
174 | // переменные для контроля предела скорости нагрева/остывания |
175 | int Stab_Term; // предыдущая температура |
176 | unsigned long time_Stab; // время последней температуры |
177 | boolean stability = false ; // флаг контроля скорости нагрева/остывания, false - состояние не стабильное |
178 |
179 |
180 |
181 | byte si; |
182 |
183 | //************************************************************************************************* |
184 | // процедура инициализации контролера |
185 | //************************************************************************************************* |
186 | void setup () |
187 | { |
188 | int i; |
189 |
190 | Serial .begin(9600); |
191 |
192 | LogSerial(0); |
193 | |
194 | // читаем сохраненные значения в энергонезависимой памяти |
195 | // первые 2 байта - сигнатура формата, для нашего проекта возьмем значение "T1", что равнозначно числу 21469 |
196 | // при изменении физического местоположения сохраняемых значений идентификатор следует изменить |
197 | // при изменении идентификатора (и физического места данных), чтение производится не будет |
198 | // что будет равносильно полному заливки скетча в новый контроллер |
199 |
200 | i = Get_signature(); |
201 | if (i != 21469) { |
202 | // перезапишем все сохраняемые параметры |
203 | LogSerial(1); |
204 | i = 21469; |
205 | Set_signature(i); |
206 | Set_term_set(term_set); |
207 | for ( byte i1=0; i1 <= 9; i1++) { |
208 | Set_temp(i1, ar_temp[i1]); |
209 | Set_pow(i1, ar_pow[i1]); |
210 | Set_tep(i1, ar_tep[i1]); |
211 | Set_dt(i1, ar_dt[i1]); |
212 | Set_step(i1, ar_step[i1]); |
213 | Set_setup(i1, ar_setup[i1]); |
214 | } |
215 | } |
216 | // теперь читаем все параметры из энергонезависимой памяти |
217 | // читаем установленую температуру |
218 | i = Get_term_set(); term_set = i; term_set_display = i; term_set_new = i; |
219 |
220 | // читаем структуры |
221 | for ( byte i1=0; i1 <= 9; i1++) { |
222 | ar_temp[i1] = Get_temp (i1); |
223 | ar_pow[i1] = Get_pow (i1); |
224 | ar_tep[i1] = Get_tep (i1); |
225 | ar_dt[i1] = Get_dt (i1); |
226 | ar_step[i1] = Get_step (i1); |
227 | ar_setup[i1] = Get_setup(i1); |
228 | } |
229 | LogSerial(2); |
230 | |
231 | // инициализируем модуль контролера термопары |
232 | |
233 | pinMode(thermoCS, OUTPUT); |
234 | pinMode(thermoCLK, OUTPUT); |
235 | pinMode(thermoDO, INPUT); |
236 | |
237 | digitalWrite(thermoCS, HIGH); |
238 | delay(500); |
239 |
240 | // инициализируем порт реле |
241 | pinMode(heatCTRL, OUTPUT); |
242 | off_Relay(); |
243 | |
244 | // инициализируем экран, у нас 16 символов и 2 строки |
245 | lcd.begin(16, 2); |
246 | lcd.noCursor(); |
247 |
248 | screen_out( true , true , true ); |
249 | Beep(1); |
250 | |
251 | LogSerial(3); |
252 | } |
253 |
254 | //************************************************************************************************* |
255 | // главный цикл |
256 | //************************************************************************************************* |
257 | void loop () { |
258 | unsigned long time_loop_new; |
259 | unsigned long time_pause; |
260 | int button_temp; |
261 | |
262 | time_loop_new = millis(); // получим время начала цикла |
263 | |
264 | // -------------------------------------------------------------------------------------------- |
265 | // обработка управления кнопками, должена идти как можно ближе к началу цикла |
266 | button_temp = getPressedButton(); |
267 | if (button_new != button_temp) { |
268 | // изменилось состояние клавиатуры, запомним время и новое состояние |
269 | button_new = button_temp; |
270 | time_button_new = time_loop_new; |
271 | } |
272 |
273 | time_pause = getDelayTime(time_button_new, time_loop_new); |
274 | if (time_pause <= BUTTON_TIME) { |
275 | // ожидание минимальной паузы |
276 | } |
277 | else { |
278 | if (button_new != button) { |
279 | // сотояния клавиатуры изменилось, минимальная пауза выдержана |
280 | if (button_new == BUTTON_NONE) { |
281 | // кнопка отпущена |
282 | // вызовем обработчик, при этом расчитаем время нажатие от события нажатия а не от нового события отпускания |
283 | OnKeyUp (button, getDelayTime(time_button, time_button_new)); |
284 | } |
285 | else if (button != BUTTON_NONE) { |
286 | // кнопка нажата, пока обработчик этого события мне не нужен, |
287 | // по тому, что кнопка SELECT обрабатывается по таймеру двумя разными способами |
288 | // здесь ничего не делаем, код оставляю для расширения в других проектах |
289 | } |
290 | else { |
291 | // смена нажатой кнопки на другую, пока обработчик этого события мне не нужен |
292 | // по тому, что мы работаем не по событию нажатия, а по событию отжатия |
293 | // здесь ничего не делаем, код оставляю для расширения в других проектах |
294 | } |
295 | // изменения клавиатуры отработаны, теперь запомним новое состояние |
296 | time_button = time_loop_new; |
297 | button = button_new; |
298 | } |
299 | else if ((button_new == button) && (button != BUTTON_NONE)) { |
300 | // есть какая-то зажатая кнопка |
301 | if ((button == BUTTON_SELECT) && (time_pause >= BUTTON_TIME_LONG)) { |
302 | // кнопка SELECT нажата достаточно долго для перехода в режим SETUP |
303 | // в текущем проекте это пока не нужно, мы пойдем другим путем и будем передовать время удержания в событие отжатия кнопки |
304 | // здесь ничего не делаем, код оставляю для расширения в других проектах, |
305 | // но, все-же, напомним звуком, что кнопка зажата или это ошибка... |
306 | Beep(2); |
307 | } |
308 | } |
309 | } |
310 | |
311 |
312 | // -------------------------------------------------------------------------------------------- |
313 | // получение актуальной температуры |
314 | time_pause = getDelayTime(time_temp, time_loop_new); |
315 | if (time_pause > TIME_GET_THERMOMETR) { |
316 | time_temp = time_loop_new; |
317 | term_real_new = readCelsius(); |
318 | } |
319 |
320 | // -------------------------------------------------------------------------------------------- |
321 | // поиск и обработка ошибок |
322 | time_pause = getDelayTime(time_error, time_loop_new); |
323 | if (time_pause > TIME_GET_ERROR) { |
324 | time_error = time_loop_new; |
325 |
326 | // включение пищалки динамика |
327 | if (term_real_new > TERM_LIMIT |
328 | || mode == STATUS_ERROR) { |
329 | Beep(2); |
330 | } |
331 | |
332 | if (mode == STATUS_RUN) { |
333 | // ошибки при работе |
334 | if ( term_real_new < -100 // ошибка термопары |
335 | || term_real_new > (term_set + 50) // перегрев на 50 градусов |
336 | || term_real_new > TERM_LIMIT) { // общий перегрев |
337 | mode = STATUS_ERROR; |
338 | Error_Term = term_real_new; |
339 | Error_Number = 1; |
340 | off_Relay(); |
341 | screen_out( true , true , true ); |
342 | } |
343 | } |
344 | else if (mode == STATUS_SETUP_RUN) { |
345 | // ошибки при калибровке |
346 | if ( term_real_new < -100 // ошибка термопары |
347 | || term_real_new > TERM_LIMIT) { // общий перегрев |
348 | mode = STATUS_ERROR; |
349 | Error_Term = term_real_new; |
350 | Error_Number = 1; |
351 | off_Relay(); |
352 | screen_out( true , true , true ); |
353 | } |
354 | } |
355 | } |
356 | |
357 | // -------------------------------------------------------------------------------------------- |
358 | // контроль скорости разогрева/остывания |
359 | time_pause = getDelayTime(time_Stab, time_loop_new); |
360 | if (time_pause > TIME_STAB) { |
361 | |
362 | if ( ((Stab_Term + (1.0 * TERM_LIMIT_SPEED * TIME_STAB / 1000)) <= term_real_new) |
363 | || ((Stab_Term - (1.0 * TERM_LIMIT_SPEED * TIME_STAB / 1000)) >= term_real_new)) { |
364 | |
365 | // обнаружен выход скорости нагрева/остывания за допустимые границы |
366 | stability = false ; |
367 | if (mode == STATUS_RUN) { |
368 | if ( (step_run == 2) |
369 | || (step_run == 3) |
370 | || (step_run == 4)) { |
371 | // перевод контролеера в режим стабилизации |
372 | step_run = 1; |
373 | } |
374 | } |
375 | else if (mode == STATUS_SETUP_RUN) { |
376 | // для режима калибровки это будет ошибкой |
377 | mode = STATUS_ERROR; |
378 | Error_Term = term_real_new; |
379 | Error_Number = 2; |
380 | off_Relay(); |
381 | screen_out( true , true , true ); |
382 | } |
383 | } |
384 | else { stability = true ; } // все нормально |
385 | |
386 | // обновим переменные цикла |
387 | time_Stab = time_loop_new; |
388 | Stab_Term = term_real_new; |
389 | } |
390 |
391 | // -------------------------------------------------------------------------------------------- |
392 | // расчет параметров для включения и выключения разогрева |
393 | time_pause = getDelayTime(time_run, time_loop_new); |
394 | if (time_pause > TIME_RUN) { |
395 | time_run = time_loop_new; |
396 | Run(time_loop_new); |
397 | } |
398 | |
399 | // -------------------------------------------------------------------------------------------- |
400 | // обновление экрана, это последнее... |
401 | time_pause = getDelayTime(time_anime, time_loop_new); |
402 | if (time_pause > TIME_ANIME) { |
403 | time_anime = time_loop_new; |
404 | screen_out( true , false , false ); |
405 | } |
406 | else { |
407 | screen_out( false , false , false ); |
408 | } |
409 | } |
файл Screen_out.ino
001 | //************************************************************************************************* |
002 | // процедура выводит все параметры и оформление на экран |
003 | // anime - вызывает перерисовку анимации в зависимости он значения переменных time_anime, time_anime_new, step_anime |
004 | // face - вызывает перерисовку надписей и прочих статических значений в зависимости от текущего режима контролера |
005 | // force - параметр для явного обновления всего экрана вне зависимости от остальных параметров |
006 | // true - полностью обновляет все параметры |
007 | // false - обновляет только измененые параметры |
008 | //************************************************************************************************* |
009 | void screen_out(boolean anime, boolean face, boolean force) |
010 | { |
011 | byte ii; |
012 | |
013 | if ( (mode == STATUS_RUN) |
014 | || (mode == STATUS_SETUP_RUN) |
015 | || (mode == STATUS_ERROR)) { |
016 | |
017 | // отключаем курсор для редактируемых полей |
018 | edit_symbol = 0; |
019 | } |
020 | |
021 | // строка 1: |
022 | // текущая температура выводится всегда в колонках 12,13,14,15 |
023 | // оформление текущей температувы выводится всегда в колонках 10,11,16 |
024 | term_real_display = OutNumber(term_real_display, term_real_new, 11, 0, 4, force); |
025 | if (force || face) { |
026 | PrintLcd( "t:" , 9, 0); |
027 | PrintLcd( "c" , 15, 0); |
028 | } |
029 | |
030 | //------------------------------------------------------- |
031 | if (mode == STATUS_STOP) { |
032 | // строка 1: |
033 | // анимацию не выводим, вместо анимации выводим оформление в колонках 1,2,3,4,5,6,7,8,9 |
034 | if (force || face) { |
035 | PrintLcd( "=STOP= " , 0, 0); |
036 | } |
037 | |
038 | // строка 2: |
039 | // установленая температура выводится в колонках 12,13,14,15 |
040 | // оформление выводим в колонки 1,2,3,4,5 |
041 | // оформление установленой температувы выводится в колонки 6,7,8,9,10,11,16 |
042 | term_set_display = OutNumber(term_set_display, term_set_new, 11, 1, 4, force); |
043 | if (force || face) { |
044 | PrintLcd( " " , 0, 1); |
045 | PrintLcd( "SET T:" , 5, 1); |
046 | PrintLcd( "c" , 15, 1); |
047 | } |
048 |
049 | // установка курсора во вторую строку редактируемого поля |
050 | if (edit_symbol == 0) { |
051 | if (FlagCursor == true ) { |
052 | lcd.noBlink(); |
053 | FlagCursor = false ; |
054 | } |
055 | } |
056 | else { |
057 | lcd.setCursor(10 + edit_symbol, 1); |
058 | if (FlagCursor != true ) { |
059 | lcd.blink(); |
060 | FlagCursor = true ; |
061 | } |
062 | } |
063 | } |
064 | //------------------------------------------------------- |
065 | else if (mode == STATUS_RUN) { |
066 | |
067 | // строка 1: |
068 | // очистим поле для анимации в колонках 1,2,3,4,5,6,7,8,9 |
069 | if (force || face) { |
070 | PrintLcd( " " , 0, 0); |
071 | } |
072 |
073 | // анимацию выводим в первую строку |
074 | if (anime == true ) { |
075 | // делаем шаг анимации |
076 | if (step_anime < 3) { step_anime = step_anime + 1; } |
077 | else { step_anime = 0; } |
078 | // выводим текущий шак на экран |
079 | if (step_anime == 0) { PrintLcd( "|" , 0, 0); } |
080 | else if (step_anime == 1) { PrintLcd( "/" , 0, 0); } |
081 | else if (step_anime == 2) { PrintLcd( "-" , 0, 0); } |
082 | else { PrintLcd( "-" , 0, 0); } |
083 | } |
084 |
085 | // строка 2: |
086 | // установленая температура выводится в колонках 12,13,14,15 |
087 | // оформление выводим в колонки 1,2,3,4,5 |
088 | // оформление установленой температувы выводится в колонки 6,7,8,9,10,11,16 |
089 | term_set_display = OutNumber(term_set_display, term_set_new, 11, 1, 4, force); |
090 | if (force || face) { |
091 | PrintLcd( " " , 0, 1); |
092 | PrintLcd( " T:" , 5, 1); |
093 | PrintLcd( "c" , 15, 1); |
094 | } |
095 | } |
096 |
097 | //------------------------------------------------------- |
098 | else if (mode == STATUS_SETUP) { |
099 | // строка 1: |
100 | // анимацию не выводим, вместо анимации выводим оформление в колонках 1,2,3,4,5,6,7,8,9 |
101 | if (force || face) { |
102 | PrintLcd( " =SETUP= " , 0, 0); |
103 | } |
104 |
105 | // строка 2: |
106 | if (force || face) { |
107 | PrintLcd( " " , 0, 1); |
108 | for ( byte ii=0; ii <= 9; ii++) { |
109 | if (ar_setup[ii] == 0) { PrintLcd( "_" , ii + 3, 1); } |
110 | else if (ar_setup[ii] == 1) { PrintLcd( "o" , ii + 3, 1); } |
111 | else if (ar_setup[ii] == 2) { PrintLcd( "O" , ii + 3, 1); } |
112 | ar_setup_scr[ii] = 0; |
113 | } |
114 | } |
115 | else { |
116 | for ( byte ii=0; ii <= 9; ii++) { |
117 | if (ar_setup_scr[ii] == 1) { |
118 | if (ar_setup[ii] == 0) { PrintLcd( "_" , ii + 3, 1); } |
119 | else if (ar_setup[ii] == 1) { PrintLcd( "o" , ii + 3, 1); } |
120 | else if (ar_setup[ii] == 2) { PrintLcd( "O" , ii + 3, 1); } |
121 | } |
122 | ar_setup_scr[ii] = 0; |
123 | } |
124 | } |
125 | |
126 | // установка курсора во вторую строку редактируемого поля |
127 | if (edit_symbol == 0) { |
128 | if (FlagCursor == true ) { |
129 | lcd.noBlink(); |
130 | FlagCursor = false ; |
131 | } |
132 | } |
133 | else { |
134 | lcd.setCursor(2 + edit_symbol, 1); |
135 | if (FlagCursor != true ) { |
136 | lcd.blink(); |
137 | FlagCursor = true ; |
138 | } |
139 | } |
140 | } |
141 | |
142 | //------------------------------------------------------- |
143 | else if (mode == STATUS_SETUP_RUN) { |
144 | // строка 1: |
145 | // очистим поле для анимации в колонках 1,2,3,4,5,6,7,8,9 |
146 | if (force || face) { |
147 | PrintLcd( " SETUP " , 0, 0); |
148 | } |
149 |
150 | // анимацию выводим в первую строку |
151 | if (anime == true ) { |
152 | // делаем шаг анимации |
153 | if (step_anime < 3) { step_anime = step_anime + 1; } |
154 | else { step_anime = 0; } |
155 | // выводим текущий шак на экран |
156 | if (step_anime == 0) { PrintLcd( "|" , 0, 0); } |
157 | else if (step_anime == 1) { PrintLcd( "/" , 0, 0); } |
158 | else if (step_anime == 2) { PrintLcd( "-" , 0, 0); } |
159 | else { PrintLcd( "-" , 0, 0); } |
160 | } |
161 |
162 | // строка 2: |
163 | if (force || face) { |
164 | PrintLcd( " " , 0, 1); |
165 | for ( byte ii=0; ii <= 9; ii++) { |
166 | if (ar_setup[ii] == 0) { PrintLcd( "_" , ii + 3, 1); } |
167 | else if (ar_setup[ii] == 1) { PrintLcd( "o" , ii + 3, 1); } |
168 | else if (ar_setup[ii] == 2) { PrintLcd( "O" , ii + 3, 1); } |
169 | ar_setup_scr[ii] = 0; |
170 | } |
171 | } |
172 | else { |
173 | for ( byte ii=0; ii <= 9; ii++) { |
174 | if (ar_setup_scr[ii] == 1) { |
175 | if (ar_setup[ii] == 0) { PrintLcd( "_" , ii + 3, 1); } |
176 | else if (ar_setup[ii] == 1) { PrintLcd( "o" , ii + 3, 1); } |
177 | else if (ar_setup[ii] == 2) { PrintLcd( "O" , ii + 3, 1); } |
178 | } |
179 | ar_setup_scr[ii] = 0; |
180 | } |
181 | } |
182 | } |
183 | |
184 | |
185 | //------------------------------------------------------- |
186 | else if (mode == STATUS_ERROR) { |
187 | // строка 1: |
188 | // анимацию не выводим, вместо анимации выводим оформление в колонках 1,2,3,4,5,6,7,8,9 |
189 | if (force || face) { |
190 | PrintLcd( "=ERROR= " , 0, 0); |
191 | } |
192 |
193 | // строка 2: |
194 | // температура ошибки выводится в колонках 12,13,14,15 |
195 | // оформление выводим в колонки 1,2,3,4,5 |
196 | // оформление температуры ошибки температувы выводится в колонки 6,7,8,9,10,11,16 |
197 | term_set_display = OutNumber(Error_Term, Error_Term, 3, 1, 4, force); |
198 | if (force || face) { |
199 | PrintLcd( " T:" , 0, 1); |
200 | PrintLcd( "c" , 15, 1); |
201 | } |
202 | } |
203 | } |
файл Run.ino
001 | //************************************************************************************************* |
002 | // процедура расчитывает разогрев и включает, отключает нагрузку |
003 | //************************************************************************************************* |
004 | void Run(unsigned long time_loop_new) { |
005 | int temp_pow; |
006 | int i; |
007 | int i1; |
008 | |
009 | //-------------------------------------------------------------------- |
010 | if (mode == STATUS_RUN) { |
011 | if (si < 10) { si = si + 1; } else { si = 0; LogSerial(5); } // вывод лога 1 раз в 10 циклов (при текущих настройках TIME_RUN это 1 раз в 1 секунду) |
012 | |
013 | if (step_run == 0) { |
014 | // иницилизируем дефолтные параметры расчета |
015 | r_pow = GetParamProc(1, term_set); |
016 | r_term = GetParamProc(2, term_set); |
017 | r_dt = GetParamProc(3, term_set); |
018 | r_step = GetParamProc(4, term_set); |
019 | |
020 | // дополнительные коэффициенты (плюсуются к основным) |
021 | rk_pow = 0; |
022 |
023 | LogSerial(10); |
024 | step_run = 1; |
025 | } |
026 | else if (step_run == 1) { |
027 | // ожидание стабилизации температуры, предотвращает перегрев нагревателей при открытом муфеле |
028 | off_Relay(); |
029 | if (stability == true ) { |
030 | time_heat_start = time_loop_new; |
031 | step_run = 2; |
032 | } |
033 | else { |
034 | Beep(2); |
035 | } |
036 | } |
037 | else if (step_run == 2) { |
038 | // разогрев с мощностью 100% |
039 | if (term_real_new < (term_set - r_term)) { |
040 | on_off_heat(10000, r_dt * 1000, time_loop_new); |
041 | } |
042 | else { |
043 | time_heat_start = time_loop_new; |
044 | step_run = 3; |
045 | } |
046 | } |
047 | else if (step_run == 3) { |
048 | // ожидание остывания до установленной температуры |
049 | if (term_real_new >= term_set) { |
050 | off_Relay(); |
051 | } |
052 | else { |
053 | time_heat_start = time_loop_new; |
054 | time_run_step = time_loop_new; |
055 | step_run = 4; |
056 | } |
057 | } |
058 | else if (step_run == 4) { |
059 | // поддержание температуры |
060 | if (term_real_new > (term_set + TERM_GRADIENT)) { |
061 | // явный перегрев, идем к предыдущему шагу |
062 | step_run = 3; |
063 | } |
064 | else if ( (getDelayTime(time_heat_start, time_loop_new) >= (1000.0 * r_dt)) // закончен малый цикл on-off |
065 | && (getDelayTime(time_run_step, time_loop_new) >= (1000.0 * r_step))) { // и прошло время перебега |
066 | |
067 | // тут все равно попали мы в диапазон или нет, все равно вносим корректор... |
068 | temp_pow = 1.0 * term_set / term_real_new * (100.0 * r_pow + rk_pow); |
069 | rk_pow = temp_pow - (100 * r_pow + rk_pow); |
070 | |
071 | // начинаем новый малый цикл on-off, малый цикл меняется дополнительно в процедуре on_off_heat |
072 | time_heat_start = time_loop_new; |
073 | // начинаем сново считать время перебега |
074 | time_run_step = time_loop_new; |
075 | } |
076 | on_off_heat(100 * r_pow + rk_pow, r_dt * 1000, time_loop_new); |
077 | } |
078 | else { off_Relay(); } |
079 | } |
080 | |
081 | //-------------------------------------------------------------------- |
082 | else if (mode == STATUS_SETUP_RUN) { |
083 | // выбор полки калибровки |
084 | // надо проверить может мы отстали... проверяем всегда, без учета степов |
085 | for ( byte i=setup_step; i <= 9; i++) { |
086 | setup_step = i; |
087 | if (ar_setup[setup_step] != 2) { |
088 | break ; // все нормально, можно идти дальше (выходим из цикла) |
089 | } |
090 | } |
091 | |
092 | if (setup_step == 9) { |
093 | if (ar_setup[setup_step] == 2) { |
094 | // это конец калибровки, переходим в режим SETUP |
095 | mode = STATUS_SETUP; |
096 | off_Relay(); |
097 | Beep(1); |
098 | // после изменения режима нужно полностью обновить экран |
099 | screen_out( true , true , true ); |
100 | LogSerial(2); |
101 | return ; |
102 | } |
103 | } |
104 |
105 | // полка выбрана, идем дальше теперь собственно этапы калибровки |
106 | |
107 | if (step_run == 1) { |
108 | // этап 1. - разогрев до температуры полки |
109 | if (term_real_new < ar_temp[setup_step]) { |
110 | // до температуры калибровки еще не дошли, греем дальше и запоминаем время |
111 | on_off_heat(10000, ar_dt[setup_step] * 1000, time_loop_new); |
112 | time_setup_t1 = time_loop_new; |
113 | } |
114 | else { |
115 | off_Relay(); |
116 | step_run = 2; |
117 | LogSerial(7); |
118 | } |
119 | } |
120 | else if (step_run == 2) { |
121 | // этап 2. - ожидание потолка перебега |
122 | off_Relay(); |
123 | if (term_real_new >= (term_setup_max-1)) { |
124 | // идет перебег, нагрев нужно выключить, и запомнить температуру (-1 это на дребезг темппературы) |
125 | if (term_real_new > term_setup_max) { |
126 | term_setup_max = term_real_new; |
127 | time_setup_t2 = time_setup_t1; |
128 | } |
129 | } |
130 | else { |
131 | // начали остывать, температура опустилась ниже максимума на 2 градуса, реальное время - середина между time_setup_t2 и time_setup_t1 |
132 | // пишем результаты |
133 | if (ar_tep[setup_step] != (term_setup_max - ar_temp[setup_step])) { |
134 | ar_tep[setup_step] = term_setup_max - ar_temp[setup_step]; |
135 | Set_tep(setup_step, ar_tep[setup_step]); // пишем температуру перебега |
136 | } |
137 | |
138 | i = getDelayTime(time_setup_t1, time_loop_new) / 1000; |
139 | i1 = getDelayTime(time_setup_t2, time_loop_new) / 1000; |
140 | i = (i + i1) / 2; |
141 | if (ar_step[setup_step] != i) { |
142 | ar_step[setup_step] = i; |
143 | Set_step(setup_step, ar_step[setup_step]); |
144 | } |
145 |
146 | ar_setup[setup_step] = 1; |
147 | ar_setup_scr[setup_step] = 1; |
148 | Set_setup(setup_step, ar_setup[setup_step]); |
149 | pow_setup_max = 100; |
150 | pow_setup_min = 0; |
151 | pow_setup = 0; |
152 | |
153 | step_run = 3; |
154 | LogSerial(8); |
155 | } |
156 | } |
157 | else if (step_run == 3) { |
158 | // этап 3. - остывание ниже градиента |
159 | off_Relay(); |
160 | |
161 | if (term_real_new <= (ar_temp[setup_step] - TERM_GRADIENT)) { |
162 | // остыли, будущую мощность выберем как среднюю между pow_setup_max и pow_setup_min |
163 | pow_setup = (pow_setup_min + pow_setup_max) / 2; |
164 | |
165 | // запомним время перехода в 4 режим |
166 | time_setup_t1 = time_loop_new; |
167 | |
168 | // нагрев был выключен, значит запуск этапа 4 нужно начинать со старта |
169 | mode_heat = true ; |
170 | time_heat_start = time_loop_new; |
171 | on_Relay(); |
172 | |
173 | step_run = 4; |
174 | LogSerial(9); |
175 | } |
176 | } |
177 | else if (step_run == 4) { |
178 | // этап 4. - нагреваем с выбраной ранее мощностью |
179 | |
180 | if (term_real_new > (ar_temp[setup_step] + TERM_GRADIENT)) { |
181 | // явный перегрев, идем к предыдущему шагу |
182 | off_Relay(); |
183 | pow_setup_max = pow_setup; |
184 | step_run = 3; |
185 | } |
186 | else if ( (getDelayTime(time_heat_start, time_loop_new) < (1000.0 * ar_dt[setup_step])) // не закончен малый цикл on-off отсчитаного от времени включения реле |
187 | || (getDelayTime(time_setup_t1, time_loop_new) < (2000.0 * ar_step[setup_step]))) { // или не прошло двойное время перебега |
188 | |
189 | on_off_heat(pow_setup * 100, ar_dt[setup_step] * 1000, time_loop_new); |
190 | } |
191 | else if ( ((pow_setup_max - pow_setup_min) == 1) // разница между режимами перегрева и негорева 1%, дальше оптимизировать нечего |
192 | || (term_real_new >= (ar_temp[setup_step] - TERM_GRADIENT))) { // или мы попали в диапазон градиента |
193 | // мощность подобрана правильно |
194 | // этап 2 завершен, пишем результаты |
195 | if (ar_pow[setup_step] != (pow_setup)) { |
196 | ar_pow[setup_step] = pow_setup; |
197 | if (ar_pow[setup_step] > 100) {ar_pow[setup_step] = 100;} |
198 | |
199 | Set_pow(setup_step, ar_pow[setup_step]); |
200 | } |
201 | |
202 | ar_setup[setup_step] = 2; |
203 | ar_setup_scr[setup_step] = 1; |
204 | Set_setup(setup_step, ar_setup[setup_step]); |
205 | step_run = 1; // по сколько этот этап мы прошли, следующий идем с первого шага |
206 | |
207 | LogSerial(10); |
208 | } |
209 | else { |
210 | // это недогрев, меняем параметры и продолжаем |
211 | pow_setup_min = pow_setup; |
212 | pow_setup = (pow_setup_min + pow_setup_max) / 2; |
213 | time_setup_t1 = time_loop_new; |
214 | step_run = 3; |
215 | } |
216 | } |
217 | else { off_Relay(); } |
218 | } |
219 | //-------------------------------------------------------------------- |
220 | else { off_Relay(); } |
221 | } |
файл other.ino
001 | // --------------------------------------------------------- |
002 | // вспомогательные процедуры |
003 | // --------------------------------------------------------- |
004 |
005 | //************************************************************************************************* |
006 | // процедура включает и выключает нагрев в зависимости от необходимой мощности |
007 | // p - относительной мощность сотые доли % (допустимые значения от 0 до 10 000) |
008 | // dt - общее время одного цикла вкл/выкл (в мсек, допустимые значения от 0 до 32 767), это время делется пропорционально параметра "p" |
009 | // 100% мощность этого времени не должно приводить к перебегу температуры выше разумной погрешности, |
010 | // по этому общее время одного цикла должно расчитыватся заранее. |
011 | // t - текущее время |
012 | //************************************************************************************************* |
013 |
014 | void on_off_heat( int p, int dt, unsigned long t) |
015 | { |
016 | unsigned long t1 = 1; |
017 | |
018 | if (p >= 9900) { |
019 | // 99% мощности считаем как 100% в целях сохранения ресурса реле |
020 | mode_heat = true ; |
021 | time_heat_start = t; |
022 | on_Relay(); |
023 | } |
024 | else if (p <= 100) { |
025 | // 1% мощности считаем 0% в целях сохранения ресурса реле |
026 | mode_heat = false ; |
027 | time_heat_stop = t; |
028 | off_Relay(); |
029 | } |
030 | else if (mode_heat == true ) { |
031 | // идет нагрев, нужно проверить может стоит остановится |
032 | t1 = t1*dt/100*p/100; |
033 | if (getDelayTime(time_heat_start, t) >= t1) { |
034 | mode_heat = false ; |
035 | time_heat_stop = t; |
036 | off_Relay(); |
037 | } |
038 | } |
039 | else if (mode_heat == false ) { |
040 | // идет охлаждение, нужно проверить может стоит запустится |
041 | t1 = t1*dt/100*(100-p/100); |
042 | if (getDelayTime(time_heat_stop, t) >= t1) { |
043 | mode_heat = true ; |
044 | time_heat_start = t; |
045 | on_Relay(); |
046 | } |
047 | } |
048 | } |
049 |
050 |
051 | void on_Relay() |
052 | { |
053 | //Serial.println("on reley"); |
054 | digitalWrite(heatCTRL, HIGH); |
055 | } |
056 |
057 | void off_Relay() |
058 | { |
059 | //Serial.println("off reley"); |
060 | digitalWrite(heatCTRL, LOW); |
061 | } |
062 |
063 |
064 | //************************************************************************************************* |
065 | // процедура выводит на экран число и возвращает новое значение |
066 | // num - текущее значение числа |
067 | // new_num - новое значение числа |
068 | // col - номер позиции в строке экрана для вывода |
069 | // line - номер строки экрана для вывода |
070 | // count - количество символов на экране для вывода |
071 | // force - параметр для явного обновления |
072 | //************************************************************************************************* |
073 | int OutNumber( int num, int new_num, byte col, byte line, byte count, boolean force) |
074 | { |
075 | String stringOne; |
076 | int str_l; |
077 |
078 | if (force || (new_num != num)) { |
079 | num = new_num; |
080 | stringOne = String(num); |
081 | str_l = count - stringOne.length(); |
082 | for ( int i=1; i <= str_l; i++){ |
083 | stringOne = String( " " ) + stringOne; |
084 | } |
085 | PrintLcd(stringOne, col, line); |
086 | } |
087 | return num; |
088 | } |
089 |
090 | //************************************************************************************************* |
091 | // процедура возвращает значение нажатой клавиши на клавиатуре |
092 | //************************************************************************************************* |
093 | int getPressedButton() |
094 | { |
095 | int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0) |
096 | if (buttonValue < 100) { |
097 | return BUTTON_RIGHT; |
098 | } |
099 | else if (buttonValue < 200) { |
100 | return BUTTON_UP; |
101 | } |
102 | else if (buttonValue < 400){ |
103 | return BUTTON_DOWN; |
104 | } |
105 | else if (buttonValue < 600){ |
106 | return BUTTON_LEFT; |
107 | } |
108 | else if (buttonValue < 800){ |
109 | return BUTTON_SELECT; |
110 | } |
111 | else if (buttonValue >= 800){ |
112 | return BUTTON_NONE; |
113 | } |
114 | |
115 | return BUTTON_NONE; |
116 | } |
117 |
118 | //************************************************************************************************* |
119 | // процедура сравнивает два времени и возвращает разницу в виде числа, учитывает переход времени через 0 |
120 | // start_time - начальное время |
121 | // end_time - конечное время |
122 | // |
123 | // !!!! процедура чуствительна к разрядности исполняемого кода !!!! |
124 | // !!!! процедура может работать неправильно при двойном переходе времени через 0 !!!! |
125 | //************************************************************************************************* |
126 | unsigned long getDelayTime(unsigned long start_time, unsigned long end_time) |
127 | { |
128 | unsigned long result; |
129 | if (start_time <= end_time) { |
130 | result = end_time - start_time; |
131 | } |
132 | else { |
133 | result = 4294967295 - end_time + start_time; |
134 | } |
135 | return result; |
136 | } |
137 |
138 | //************************************************************************************************* |
139 | // процедура увеличивает или умеьшает значение разряда числа |
140 | // num - начальное число |
141 | // count - максимальная разрядность числа |
142 | // nb - номер разряда, считам с лево на право, например: num=2, count=4, nb=2 считаем разряд указаный "X" 0X02. Результат увеличения будет число 102. |
143 | // inc - флаг увеличение/уменьшения, если true - то увеличиваем |
144 | //************************************************************************************************* |
145 | int ChangeValue( int num, int count, int nb, boolean inc) { |
146 | int result; |
147 | String stringOne; |
148 | int str_l; |
149 | int sim; |
150 |
151 | // сначала добьем нулями разрядность |
152 | stringOne = String(num); |
153 | str_l = count - stringOne.length(); |
154 | for ( int i=1; i <= str_l; i++){ |
155 | stringOne = String( "0" ) + stringOne; |
156 | } |
157 |
158 | // теперь изменяем нужный разряд |
159 | sim = int (stringOne.charAt(nb-1)); |
160 | if ((inc == true ) && (sim < 57)) { sim = sim + 1; } |
161 | else if ((inc == false ) && (sim > 48)) { sim = sim - 1; } |
162 |
163 | // запаковываем обратно в число |
164 | stringOne.setCharAt(nb-1, char (sim)); |
165 | result = stringOne.toInt(); |
166 | return result; |
167 | } |
168 |
169 | //************************************************************************************************* |
170 | // процедура выводит на экран заранее подготовленую строку, при этом выключает режим "Blink" |
171 | // str - строка или String для вывода |
172 | // col - номер позиции в строке экрана для вывода |
173 | // line - номер строки экрана для вывода |
174 | //************************************************************************************************* |
175 | void PrintLcd(String str, int col, int line) |
176 | { |
177 | if (FlagCursor == true ) { |
178 | lcd.noBlink(); |
179 | FlagCursor = false ; |
180 | } |
181 | lcd.setCursor(col, line); |
182 | lcd.print(str); |
183 | } |
184 |
185 | //************************************************************************************************* |
186 | // процедура читает из энерго независимой памяти двухбйтовое число |
187 | // num - идентификатор памяти (номер двухбайтного слова) |
188 | //************************************************************************************************* |
189 | int ReadInt( int num) |
190 | { |
191 | byte a; |
192 | byte b; |
193 | int result; |
194 | |
195 | a = int (EEPROM.read(num * 2)); |
196 | b = int (EEPROM.read(num * 2 + 1)); |
197 | result = a * 255 + b; |
198 |
199 | return result; |
200 | } |
201 |
202 | //************************************************************************************************* |
203 | // процедура записывает в энерго независимую память двухбйтовое число |
204 | // num - идентификатор памяти (номер двухбайтного слова) |
205 | // value - записываемое значение |
206 | //************************************************************************************************* |
207 | void WriteInt( int num, int value) |
208 | { |
209 | byte a; |
210 | byte b; |
211 | |
212 | b = value % 255; |
213 | a = (value - b) / 255; |
214 | EEPROM.write(num * 2, a); |
215 | EEPROM.write(num * 2 + 1, b); |
216 | Beep(4); // сделано для выявления безконтрольной записи и снижения ресурса EEPROM |
217 | } |
218 |
219 | //************************************************************************************************* |
220 | // процедура проверяет новую установленую температуру и сохраняет ее |
221 | //************************************************************************************************* |
222 | void set_term_set() |
223 | { |
224 | if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } |
225 |
226 | if (term_set_new != term_set) { |
227 | // применяем новую температуру и запоминаем ее |
228 | term_set = term_set_new; |
229 | Set_term_set(term_set); |
230 | } |
231 | } |
232 |
233 | //************************************************************************************************* |
234 | // две процедуры, для получения данных с термопары |
235 | //************************************************************************************************* |
236 | double readCelsius() { |
237 |
238 | uint16_t v; |
239 |
240 | digitalWrite(thermoCS, LOW); |
241 | _delay_ms(1); |
242 |
243 | v = spiread(); |
244 | v <<= 8; |
245 | v |= spiread(); |
246 |
247 | digitalWrite(thermoCS, HIGH); |
248 |
249 | if (v & 0x4) { |
250 | // uh oh, no thermocouple attached! |
251 | //return NAN; |
252 | return -101; |
253 | } |
254 | v >>= 3; |
255 |
256 | return v*0.25; |
257 | } |
258 |
259 | byte spiread() { |
260 | int i; |
261 | byte d = 0; |
262 |
263 | for (i=7; i>=0; i--) |
264 | { |
265 | digitalWrite(thermoCLK, LOW); |
266 | _delay_ms(1); |
267 | if (digitalRead(thermoDO)) { |
268 | //set the bit to 0 no matter what |
269 | d |= (1 << i); |
270 | } |
271 |
272 | digitalWrite(thermoCLK, HIGH); |
273 | _delay_ms(1); |
274 | } |
275 |
276 | return d; |
277 | } |
278 |
279 | //************************************************************************************************* |
280 | // процедура получает апромиксированое значение по температуре из эталонных массивов |
281 | // NumParam - номер массива, 1 - ar_pow, 2 - ar_tep, 3 - ar_dt, 4 - ar_step |
282 | // Term - температура к которой нужно получить значение |
283 | //************************************************************************************************* |
284 | int GetParamProc( byte NumParam, int Term){ |
285 | int ir = 0; |
286 | byte ii; |
287 | byte i1; |
288 | byte i2; |
289 |
290 | // найдем индексы |
291 | i1 = 0; |
292 | for (ii=0; ii <= 9; ii++) { |
293 | if (ar_temp[ii] > Term) { |
294 | ii = ii - 1; |
295 | break ; |
296 | } |
297 | } |
298 | |
299 | if (ii <= 0) { |
300 | i1 = 0; |
301 | i2 = 1; |
302 | } |
303 | else if (ii >=9) { |
304 | i1 = 8; |
305 | i2 = 9; |
306 | } |
307 | else { |
308 | i1 = ii; |
309 | i2 = ii + 1; |
310 | } |
311 |
312 | // в i1 и i2 лежат индексы для массивов, начинаем апромиксацию |
313 | 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]); } |
314 | 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]); } |
315 | 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]); } |
316 | 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]); } |
317 |
318 | return ir; |
319 | } |
320 |
321 | //************************************************************************************************* |
322 | // процедуры, для записи и получении всех данных EEPROM |
323 | // собраны здесь для единообразного адресного хранения и визуализации карты использования EEPROM, |
324 | // адреса в двух байтовом виде (1 - это два байта с физическим адресом 2 и 3) |
325 | //************************************************************************************************* |
326 |
327 | // ------------------- чтение ------------------- |
328 | int Get_signature() { return ReadInt(0); } // 0 - сигнатура |
329 | int Get_term_set() { return ReadInt(1); } // 1 - установленая температура |
330 | // 2...9 - резерв |
331 | int Get_temp( int Num) { return ReadInt(Num+10); } // 10..19 - массив калибровочных температур |
332 | int Get_pow( int Num) { return ReadInt(Num+20); } // 20..29 - массив мощности поддержания температуры |
333 | int Get_tep( int Num) { return ReadInt(Num+30); } // 30..39 - массив температур инерционности |
334 | int Get_dt( int Num) { return ReadInt(Num+40); } // 40..49 - массив времени цикла on/off |
335 | int Get_step( int Num) { return ReadInt(Num+50); } // 50..59 - массив времени инерционности |
336 | int Get_setup( int Num){ return ReadInt(Num+60); } // 60..69 - массив флагов калибровки |
337 | // 70..99 - резерв |
338 | // 100..511 - свободно |
339 |
340 | // ------------------- запись ------------------- |
341 | int Set_signature( int value) { WriteInt(0, value); } // 0 - сигнатура |
342 | int Set_term_set( int value) { WriteInt(1, value); } // 1 - установленая температура |
343 | // 2...9 - резерв |
344 | void Set_temp( int Num, int value) { WriteInt(Num+10, value); } // 10..19 - массив калибровочных температур |
345 | void Set_pow( int Num, int value) { WriteInt(Num+20, value); } // 20..29 - массив мощности поддержания температуры |
346 | void Set_tep( int Num, int value) { WriteInt(Num+30, value); } // 30..39 - массив температур инерционности |
347 | void Set_dt( int Num, int value) { WriteInt(Num+40, value); } // 40..49 - массив времени цикла on/off |
348 | void Set_step( int Num, int value) { WriteInt(Num+50, value); } // 50..59 - массив времени инерционности |
349 | void Set_setup( int Num, int value) { WriteInt(Num+60, value); } // 60..69 - массив флагов калибровки |
350 | // 70..99 - резерв |
351 | // 100..511 - свободно |
файл OnKeyUp.ino
001 | //************************************************************************************************* |
002 | // обработчик вызывается при отпускании кнопки клавиатуры |
003 | // key - кнопка которая была отпущена |
004 | // pressing - время которое кнопка была нажата |
005 | //************************************************************************************************* |
006 | void OnKeyUp ( int key, unsigned long pressing) |
007 | { |
008 | int i ; |
009 | |
010 | if (key == BUTTON_SELECT) { |
011 | // реализуем переход режима работы контроллера |
012 | |
013 | // ******************** переход в режим RUN ******************** |
014 | if (mode == STATUS_STOP && pressing < BUTTON_TIME_LONG) { |
015 | set_term_set(); // перед стартом применим (и сохраним) установленую температуру |
016 | mode = STATUS_RUN; // установка режима контроллера |
017 | step_run = 0; // установка этапа выполнения |
018 | Beep(3); |
019 | } |
020 | |
021 | // ******************** переход в режим SETUP ******************** |
022 | else if ( (mode == STATUS_STOP && pressing >= BUTTON_TIME_LONG) |
023 | || (mode == STATUS_SETUP_RUN)) { |
024 | mode = STATUS_SETUP; |
025 | off_Relay(); |
026 | } |
027 | |
028 | // ******************** переход в режим STOP ******************** |
029 | else if ( (mode == STATUS_ERROR) |
030 | || (mode == STATUS_RUN) |
031 | || (mode == STATUS_SETUP && pressing < BUTTON_TIME_LONG)) { |
032 | mode = STATUS_STOP; |
033 | off_Relay(); |
034 | Error_Term = 0; |
035 | Error_Number = 0; |
036 | } |
037 | |
038 | // ******************** переход в режим SETUP_RUN ******************** |
039 | else if ((mode == STATUS_SETUP) && (pressing >= BUTTON_TIME_LONG)) { |
040 | // перед калибровкой применим (и сохраним) установленую линейку порогов |
041 | for ( byte i=0; i <= 9; i++) { |
042 | if (Get_setup(i) != ar_setup[i]) { Set_setup(i, ar_setup[i]); } |
043 | } |
044 | |
045 | mode = STATUS_SETUP_RUN; |
046 | step_run = 1; |
047 | term_setup_max = 0; |
048 | pow_setup_max = 100; |
049 | setup_step = 0; |
050 | Beep(3); |
051 | } |
052 | |
053 | // после изменения режима нужно полностью обновить экран |
054 | screen_out( true , true , true ); |
055 | } |
056 | else if (key == BUTTON_RIGHT) { |
057 | if (mode == STATUS_STOP) { |
058 | // переключаем редактируемый разряд установленой температуры |
059 | if (edit_symbol >= 4) { edit_symbol = 0; } |
060 | else { edit_symbol = edit_symbol + 1; } |
061 |
062 | if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } |
063 | screen_out( false , false , false ); |
064 | } |
065 | else if (mode == STATUS_SETUP) { |
066 | if (edit_symbol >= 10) { edit_symbol = 0; } |
067 | else { edit_symbol = edit_symbol + 1; } |
068 | |
069 | screen_out( false , false , false ); |
070 | } |
071 | } |
072 | else if (key == BUTTON_LEFT) { |
073 | if (mode == STATUS_STOP) { |
074 | // переключаем редактируемый разряд установленой температуры |
075 | if (edit_symbol == 0) { edit_symbol = 4; } |
076 | else { edit_symbol = edit_symbol - 1; } |
077 | |
078 | if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } |
079 | screen_out( false , false , false ); |
080 | } |
081 | else if (mode == STATUS_SETUP) { |
082 | if (edit_symbol == 0) { edit_symbol = 10; } |
083 | else { edit_symbol = edit_symbol - 1; } |
084 | |
085 | screen_out( false , false , false ); |
086 | } |
087 | } |
088 | else if (key == BUTTON_UP) { |
089 | if ((mode == STATUS_STOP) && (edit_symbol != 0)) { |
090 | // мы редактируем текущий разряд установленой температуры |
091 | term_set_new = ChangeValue(term_set_new, 4, edit_symbol, true ); |
092 | if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } |
093 | } |
094 | else if (mode == STATUS_SETUP) { |
095 | switch (ar_setup[edit_symbol - 1]) { |
096 | case 0: ar_setup[edit_symbol - 1] = 2; break ; |
097 | case 1: ar_setup[edit_symbol - 1] = 2; break ; |
098 | case 2: ar_setup[edit_symbol - 1] = 0; break ; |
099 | } |
100 | ar_setup_scr[edit_symbol - 1] = 1; |
101 | } |
102 | } |
103 | else if (key == BUTTON_DOWN) { |
104 | if ((mode == STATUS_STOP) && (edit_symbol != 0)) { |
105 | // мы редактируем текущий разряд установленой температуры |
106 | term_set_new = ChangeValue(term_set_new, 4, edit_symbol, false ); |
107 | if (term_set_new > TERM_LIMIT) { term_set_new = TERM_LIMIT; } |
108 | } |
109 | else if (mode == STATUS_SETUP) { |
110 | switch (ar_setup[edit_symbol - 1]) { |
111 | case 0: ar_setup[edit_symbol - 1] = 2; break ; |
112 | case 1: ar_setup[edit_symbol - 1] = 0; break ; |
113 | case 2: ar_setup[edit_symbol - 1] = 0; break ; |
114 | } |
115 | ar_setup_scr[edit_symbol - 1] = 1; |
116 | } |
117 | } |
118 | } |
файл LogSerial.ino
001 | //************************************************************************************************* |
002 | // процедура выводит в порт лог глобальных переменных, собрано здесь для удобства включения/отключения |
003 | // и уменьшения текстов основной программы, Num - номер вызова лога |
004 | //************************************************************************************************* |
005 | void LogSerial( byte Num) { |
006 | int i; |
007 | |
008 | // if (false) { |
009 | if ( true ) { |
010 | |
011 | switch (Num) { |
012 | case 0: |
013 | Serial .print( "=debug (setup.Start)=" ); |
014 | Serial .println(); |
015 | |
016 | break ; |
017 | |
018 | case 1: |
019 | Serial .print( "=debug (setup.FORMAT)=" ); |
020 | Serial .println(); |
021 | |
022 | break ; |
023 | |
024 | case 2: |
025 | Serial .print( "=debug (setup.ActualTable)=" ); |
026 | Serial .println(); |
027 | for ( byte i=0; i <= 9; i++) { |
028 | Serial .print( "i: " ); Serial .print(i); |
029 | Serial .print( ", ar_temp: " ); Serial .print(ar_temp[i]); |
030 | Serial .print( ", ar_pow: " ); Serial .print(ar_pow[i]); |
031 | Serial .print( ", ar_tep: " ); Serial .print(ar_tep[i]); |
032 | Serial .print( ", ar_dt: " ); Serial .print(ar_dt[i]); |
033 | Serial .print( ", ar_step: " ); Serial .print(ar_step[i]); |
034 | Serial .print( ", ar_setup: " ); Serial .print(ar_setup[i]); |
035 | Serial .println(); |
036 | } |
037 | |
038 | break ; |
039 | |
040 | case 3: |
041 | Serial .print( "=debug (setup.setup)= Complete." ); |
042 | Serial .println(); |
043 | |
044 | break ; |
045 | |
046 | case -1: |
047 | Serial .print( "=debug (Run.Run.0)=" ); |
048 | Serial .print( " term_set: " ); Serial .print(term_set); |
049 | Serial .print( ", r_pow: " ); Serial .print(r_pow); |
050 | Serial .print( ", r_term: " ); Serial .print(r_term); |
051 | Serial .print( ", r_dt: " ); Serial .print(r_dt); |
052 | Serial .print( ", r_step: " ); Serial .print(r_step); |
053 | Serial .println(); |
054 | |
055 | break ; |
056 |
057 |
058 |
059 | //Serial.print("=debug (Run.Run.0)="); |
060 | //Serial.print(", step_run: "); Serial.print(step_run); |
061 | //Serial.print(", term_real_new: "); Serial.print(term_real_new); |
062 | //Serial.print(", rk_pow: "); Serial.print(rk_pow); |
063 | //Serial.println(); |
064 | |
065 | //break; |
066 | |
067 | case 5: |
068 | Serial .print( "=debug (Run.Run)=" ); |
069 | Serial .print( " step_run: " ); Serial .print(step_run); |
070 | Serial .print( ", term_set: " ); Serial .print(term_set); |
071 | Serial .print( ", term_real_new: " ); Serial .print(term_real_new); |
072 | Serial .print( ", rk_pow: " ); Serial .print(rk_pow); |
073 | Serial .println(); |
074 | |
075 | break ; |
076 | |
077 | case 6: |
078 | Serial .print( "=debug (Run.Run.0)=" ); |
079 | Serial .print( " term_set: " ); Serial .print(term_set); |
080 | Serial .print( ", r_pow: " ); Serial .print(r_pow); |
081 | Serial .print( ", r_term: " ); Serial .print(r_term); |
082 | Serial .print( ", r_dt: " ); Serial .print(r_dt); |
083 | Serial .print( ", r_step: " ); Serial .print(r_step); |
084 | Serial .println(); |
085 | |
086 | break ; |
087 | |
088 | case 7: |
089 | Serial .print( "=debug (Run.Setup.1)=" ); |
090 | Serial .print( " setup_step: " ); Serial .print(setup_step); |
091 | Serial .print( ", term_real_new: " ); Serial .print(term_real_new); |
092 | Serial .println(); |
093 | |
094 | break ; |
095 |
096 | case 8: |
097 | Serial .print( "=debug (Run.Setup.2)=" ); |
098 | Serial .print( " setup_step: " ); Serial .print(setup_step); |
099 | Serial .print( ", ar_tep[setup_step]: " ); Serial .print(ar_tep[setup_step]); |
100 | Serial .print( ", ar_step[setup_step]: " ); Serial .print(ar_step[setup_step]); |
101 | Serial .println(); |
102 |
103 | break ; |
104 | |
105 | case 9: |
106 | Serial .print( "=debug (Run.Setup.3)=" ); |
107 | Serial .print( " setup_step: " ); Serial .print(setup_step); |
108 | Serial .print( ", term_real_new: " ); Serial .print(term_real_new); |
109 | Serial .println(); |
110 |
111 | break ; |
112 | |
113 | case 10: |
114 | Serial .print( "=debug (Run.Setup.End)=" ); |
115 | Serial .print( " setup_step: " ); Serial .print(setup_step); |
116 | Serial .print( ", term_real_new: " ); Serial .print(term_real_new); |
117 | Serial .print( ", ar_pow[setup_step]: " ); Serial .print(ar_pow[setup_step]); |
118 | Serial .println(); |
119 | |
120 | break ; |
121 | |
122 | } |
123 | } |
124 | } |
файл Beep.ino
01 | //************************************************************************************************* |
02 | // процедура выводит на динамик звуки, сделана здесь, для удобства |
03 | // Num - номер программы |
04 | // 0 - нет звуков, |
05 | // 1 - включение контроллера, |
06 | // 2 - ошибка, |
07 | // 3 - старт нагрева, |
08 | // 4 - звук записи в EEPROM, |
09 | //************************************************************************************************* |
10 | void Beep( byte Num) { |
11 |
12 | switch (Num) { |
13 | case 0: |
14 | // нет звуков |
15 | noTone(beepCTRL); |
16 | break ; |
17 | case 1: |
18 | // включение контроллера |
19 | tone(beepCTRL, 1000); |
20 | delay(500); |
21 | tone(beepCTRL, 4000, 500); |
22 | break ; |
23 | |
24 | case 2: |
25 | // ошибка, |
26 | tone(beepCTRL, 6000, 150); |
27 | break ; |
28 | |
29 | case 3: |
30 | // старт нагрева, |
31 | tone(beepCTRL, 300, 1000); |
32 | break ; |
33 |
34 | case 4: |
35 | // запись в EEPROM, |
36 | tone(beepCTRL, 300, 100); |
37 | break ; |
38 | |
39 | } |
40 | } |
Отличная работа!
А Вы не думали использовать закон регулирования, я себе на дачу для конвектора собрал ПИ регулятор, (используеться медленный ШИМ на реле) отлично работает (+-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. Я вышлю Вам техзадание, после ознакомления с ним Вы назначите цену.