Imax9
НОВОСТИ   ПУБЛИКАЦИИ   FILES   ИНФОРМАЦИЯ

Переводим морозильную камеру Атлант 7184 на микроконтроллерное управление.

Update 29.08.19 В проекте поначалу использовал твердотельное реле, но двигатель иногда не включался. Поставил простое электромагнитное.

Update 01.11.19 Запитал экранчик от +3,3V вместо +5V. Добавил режим screensaver с бегающим временем и пониженной яркостью, вычитал что oled выгорает после года постоянной работы. Также добывил поддержку watchdog - однажды контроллер завис и догнал температуру до -25.

Здравствуйте, уважаемые читатели ! С рождением первого ребенка встал вопрос о покупке морозильной камеры, т. к. объема заморозки холодильника на зиму уже не хватало. В магазине "манагер" посоветовал по критериям цена-качество и "все берут" - Атлант 7184. Сначала все радовались, но со временем, начал замечать странную ее работу поработает минут 5-7 и через 10 опять включается. Регулятор менял режим в небольших пределах, но тотального изменения не было. Единственное, что мешало его отвезти обратно - это вес и половина морозилки замороженных фруктов. Тогда-то и появилась мысль перевести ее на цифровое управление. Сейчас, с изучением arduino, мысль оформилась в проект, который и представляю на ваш суд.

Изначально хотел сделать простое управление с конкретными температурами включения/отключения, но решил добавить гибкости на микроконтроллере arduino nano c oled 0,96" дисплеем и кнопкой для выбора режима работы.

Используемые компоненты :

  1. Сердце проекта Arduino Nano
  2. Датчик температуры DS18b20 в герметичном исполнении.
  3. OLED дисплей 128x64 I2C интерфейс.
  4. Плата реле 1 канал для arduino.
  5. Блок питания входное 220V, выход не менее 7V, я брал на 9V.

Стоит заметить, что при питании 5V (первый попавшийся под руку) arduin-ка работала нормально, но с датчика температура прыгала, разброс в показаниях был до 5гр C.

Схема оформилась в нечто подобное :

Далее код, в начале подключаем библиотеки для работы с датчиком и экраном. Определяем константы. В массиве - выбираемые значения температур вкл/выкл времени непрерывной работы для охлаждения двигателя и времени работы отдыха при отсутствии внятных показаний с датчиков :

Include

#include <avr/wdt.h>
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
mcusr_mirror = MCUSR;
MCUSR = 0;
wdt_disable();
}
#include <OneWire.h>
OneWire ds(A1); // on pin 10 (a 4.7K resistor is necessary)
//#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define relay_pin 7 // пин реле
int hitemp= -15; //Температура включения двигателя +1
int lowtemp= -17; //температура выключения двигателя
word timenonstop=90*60; // Время непрерывной работы
word timeon=10*60; //на 10 минут включать двигатель
word timeoff=20*60; // на 20 мин выключать двигатель
int param1[4]={-14,-15,-16,-17}; // выбор температыры включения
int param2[4]={-17,-18,-19,-20}; // выбор температыры включения
int param3[4]={300,3600,5400,7200}; //
int param4[4]={7*60,10*60,13*60,15*60}; //
int param5[4]={15*60,20*60,26*60,30*60}; //

byte state,oldstate; //режим работы
#define motoroff 0 // нормальный режим двигатель отключен
#define motoron 1 // нормальный режим двигатель включен
#define timealert 2 // аварийный режим долгая работа нужна пауза
#define tempalertoff 4 // аварийный режим с датчиков хрень - 20мин пауза
#define tempalerton 5 //аварийный режим с датчиков хрень - 10мин работа
#define modeedit 8 // режим редактирования температур, времени непрерывной работы

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
uint32_t myTimerstr, myTimerstp;
uint32_t Timerlastpress;
bool flagscrsave;
#define timescrsave 60 //время до screensaver
byte scrx,scry;
word TimeStr, TimeStp,timealarm,hltemp;
unsigned long fisttime;
int realtemp;
byte temperatureh,temperaturel; // числа целое и после запятой
byte data[2]; // Место для значения температуры


Инициализируем переменные, настраиваем порты :

Setup

void setup() {
// Serial.begin(9600);
cli(); // отключить глобальные прерывания
TCCR1A = 0; // установить регистры в 0
TCCR1B = 0;

OCR1A = 15624; // установка регистра совпадения
TCCR1B |= (1 << WGM12); // включение в CTC режим

// Установка битов CS10 и CS12 на коэффициент деления 1024
TCCR1B |= (1 << CS10);
TCCR1B |= (1 << CS12);

TIMSK1 |= (1 << OCIE1A); // включение прерываний по совпадению
sei(); // включить глобальные прерывания

wdt_enable(WDTO_8S);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
display.ssd1306_command(SSD1306_SETCONTRAST); //0x81
display.ssd1306_command(0x40);
display.ssd1306_command(SSD1306_SETPRECHARGE); //0xD9
display.ssd1306_command(0x40);

ds.reset();
ds.write(0xCC);
ds.write(0x4E);
ds.write(0x7F); // верх и низ для аварийных температур
ds.write(0x81); // alarm нам не нужен
ds.write(0x20); // это разрядность конвертации
// ds.write(0x48); // сделать один раз запись в EPROM

pinMode(relay_pin, OUTPUT);
digitalWrite(relay_pin, 1); //отключить двигатель
pinMode(3, INPUT_PULLUP); //кнопка на D3 с подтяжкой +5В
myTimerstp = millis();
Timerlastpress=myTimerstr=myTimerstp;
state=motoroff;
TimeStr=TimeStp=0;
scrx=scry=0;
flagscrsave=false;
}

Функция опроса температурного датчика.

GetTemp

void GetTemp ()
{
ds.reset(); // Начинаем взаимодействие со сброса всех предыдущих команд и параметров
ds.write(0xCC); // Даем датчику DS18b20 команду пропустить поиск по адресу. 1 датчик
ds.write(0x44); // Даем датчику DS18b20 команду измерить температуру.
delay(800); // Микросхема измеряет температуру, а мы ждем.
ds.reset(); // Теперь готовимся получить значение измеренной температуры
ds.write(0xCC);
ds.write(0xBE); // Просим передать нам значение регистров со значением температуры
data[0] = ds.read(); // Читаем младший байт значения температуры
data[1] = ds.read(); // А теперь старший
if (data[1]>7) {hltemp=(~((data[1]<<8)|data[0]))+1; //температура отрицательная
temperatureh=(hltemp>>4)&0xff;
realtemp=-temperatureh;}
else {hltemp=((data[1]<<8)|data[0]);
temperatureh = (hltemp>>4);
realtemp=temperatureh;}
}

Функция опроса кнопки.

KeyScan

void KeyScan (int i)
{
fisttime=millis();
while ((millis()-fisttime)<i)
{
if (!digitalRead(3))
{Timerlastpress=millis();
if (state!=modeedit) {oldstate=state; state=modeedit;}
}
delay(100);
}
if ((millis()-Timerlastpress)/1000>timescrsave)
{flagscrsave=true;
display.ssd1306_command(SSD1306_SETCONTRAST); //0x81
display.ssd1306_command(0x10);
display.ssd1306_command(SSD1306_SETPRECHARGE); //0xD9
display.ssd1306_command(0x10);
}
else flagscrsave=false;
}

Главный цикл, где происходит отработка алгоритмов при нажатии кнопки, результатов температуры, управление двигателем и подключаем прерывания для сброса watchdog чтоб не перегружал нам когда его не просят.

Main

void loop() {
byte cycle;
boolean butt_flag = 0;
boolean butt;
unsigned long last_press;

display.setTextSize(2);
switch (state){
case timealert :{// ждем 10 минут
if (timealarm>5)
{
display.clearDisplay();
display.setCursor(32,0);
display.print("Pause");
display.setCursor(48,16);
display.print(timealarm);
display.display();
}
else {state=motoroff; myTimerstr = millis();} // снова за работу
KeyScan(5000);
TimeStp=(millis()-myTimerstp)/1000;
timealarm=600-TimeStp;
}
break;

case tempalerton: {
GetTemp();
if (((realtemp<50)||(realtemp>-25))&&((data[0]&data[1])!=0xff)) {state=motoron; break;}
display.clearDisplay();
display.setCursor(0,0);
display.print("Temp Error");
display.setCursor(8,16);
display.print("On : ");
TimeStr=(millis()-myTimerstr)/1000;
display.print(TimeStr);
display.display();
KeyScan(5000);
if (TimeStr>timeon) {
state=tempalertoff;
digitalWrite(relay_pin, 1); //выключить двигатель
myTimerstp = millis();
}

}
break;

case tempalertoff: {
GetTemp();
if (((realtemp<50)||(realtemp>-25))&&((data[0]&data[1])!=0xff)) {state=motoroff; break;}
display.clearDisplay();
display.setCursor(0,0);
display.print("Temp Error");
display.setCursor(8,16);
display.print("Off :");
TimeStp=(millis()-myTimerstp)/1000;
display.print(TimeStp);
display.display();
KeyScan(5000);
if (TimeStp>timeoff) {
state=tempalerton;
digitalWrite(relay_pin, 0); //включить двигатель
myTimerstr = millis();
}

}
break;

case modeedit:{

cycle=0; //fase1
display.clearDisplay();
display.setCursor(24,0);
display.print("Hi Temp");
display.setCursor(48,16);
hitemp=param1[cycle];
display.print(hitemp);
display.display();
fisttime=millis();
while ((millis()-fisttime)<5000){
butt = !digitalRead(3);
last_press = millis();
if (butt == 1 && butt_flag == 0) {
butt_flag = 1;
delay(100);

}
if (butt == 0 && butt_flag == 1) {
butt_flag = 0;
cycle++;
cycle=cycle&0x3;
hitemp=param1[cycle];
fisttime=millis();
display.clearDisplay();
display.setCursor(24,0);
display.print("Hi Temp");
display.setCursor(48,16);
display.print(hitemp);
display.display();
}
}

cycle=0; //fase2
display.clearDisplay();
display.setCursor(16,0);
display.print("Low Temp");
display.setCursor(48,16);
lowtemp=param2[cycle];
display.print(lowtemp);
display.display();
fisttime=millis();
while ((millis()-fisttime)<5000){
butt = !digitalRead(3);
last_press = millis();
if (butt == 1 && butt_flag == 0) {
butt_flag = 1;
delay(100);

}
if (butt == 0 && butt_flag == 1) {
butt_flag = 0;
cycle++;
cycle=cycle&0x3;
lowtemp=param2[cycle];
fisttime=millis();
display.clearDisplay();
display.setCursor(16,0);
display.print("Low Temp");
display.setCursor(48,16);
display.print(lowtemp);
display.display();
}
}

cycle=0; //fase3
display.clearDisplay();
display.setCursor(16,0);
display.print("Long Time");
display.setCursor(48,16);
timenonstop=param3[cycle];
display.print(timenonstop);
display.display();
fisttime=millis();
while ((millis()-fisttime)<5000){
butt = !digitalRead(3);
last_press = millis();
if (butt == 1 && butt_flag == 0) {
butt_flag = 1;
delay(100);

}
if (butt == 0 && butt_flag == 1) {
butt_flag = 0;
cycle++;
cycle=cycle&0x3;
timenonstop=param3[cycle];
fisttime=millis();
display.clearDisplay();
display.setCursor(16,0);
display.print("Long Time");
display.setCursor(48,16);
display.print(timenonstop);
display.display();
}
}

cycle=0; //fase4
display.clearDisplay();
display.setCursor(24,0);
display.print("Time On");
display.setCursor(48,16);
timeon=param4[cycle];
display.print(timeon);
display.display();
fisttime=millis();
while ((millis()-fisttime)<5000){
butt = !digitalRead(3);
last_press = millis();
if (butt == 1 && butt_flag == 0) {
butt_flag = 1;
delay(100);

}
if (butt == 0 && butt_flag == 1) {
butt_flag = 0;
cycle++;
cycle=cycle&0x3;
timeon=param4[cycle];
fisttime=millis();
display.clearDisplay();
display.setCursor(24,0);
display.print("Time On");
display.setCursor(48,16);
display.print(timeon);
display.display();
}
}

cycle=0; //fase5
display.clearDisplay();
display.setCursor(16,0);
display.print("Time Off");
display.setCursor(48,16);
timeoff=param5[cycle];
display.print(timeoff);
display.display();
fisttime=millis();
while ((millis()-fisttime)<5000){
butt = !digitalRead(3);
last_press = millis();
if (butt == 1 && butt_flag == 0) {
butt_flag = 1;
delay(100);

}
if (butt == 0 && butt_flag == 1) {
butt_flag = 0;
cycle++;
cycle=cycle&0x3;
timeoff=param5[cycle];
fisttime=millis();
display.clearDisplay();
display.setCursor(16,0);
display.print("Time Off");
display.setCursor(48,16);
display.print(timeoff);
display.display();
}
}

state=oldstate;
Timerlastpress=millis();
}//case mode edit
break;

default: {

GetTemp();
if ((realtemp>50)||(realtemp<-25)||(data[0]&data[1]==0xff))
{state=tempalerton;
digitalWrite(relay_pin, 0); //включить двигатель
myTimerstr = millis();
break;
}

temperaturel =0;
if (hltemp&0x08) temperaturel+=50;
if (hltemp&0x04) temperaturel+=25;
if (hltemp&0x02) temperaturel+=12;

if ((realtemp>hitemp)&&(state==motoroff))
{digitalWrite(relay_pin, 0); //включить двигатель
state=motoron; //флаг двигатель включен
myTimerstr = millis(); //сохраняем время начала start
TimeStp=(millis()-myTimerstp)/1000;}

if ((realtemp<lowtemp)&&(state==motoron))
{digitalWrite(relay_pin, 1); //выключить двигатель
state=motoroff; //флаг двигатель выключен
myTimerstp = millis(); //сохраняем время начала stop
TimeStr=((millis()-myTimerstr))/1000;}

if (state==motoron) TimeStr=((millis()-myTimerstr))/1000; //уже включен
else TimeStp=((millis()-myTimerstp))/1000; //уже выключен

display.clearDisplay();
if (flagscrsave==false)
{
display.setCursor(16,0);
if (data[1]>7) {display.print("-");} // температура отрицательная
else {display.print("+");}

display.print(temperatureh);
display.print(".");
display.print(temperaturel);
display.setCursor(0,16);
display.setTextSize(1);
display.print("s");
display.setTextSize(2);
display.setCursor(8,16);
display.print(TimeStr);
display.display();
display.setCursor(64,16);
display.setTextSize(1);
display.print("p");
display.setTextSize(2);
display.setCursor(72,16);
display.print(TimeStp);
}
else
{
display.setTextSize(1);
display.setCursor(scrx,scry);
if (state==motoron) {display.print("s"); display.print(TimeStr);}
else {display.print("p"); display.print(TimeStp);}
scrx+=32;
if (scrx>96){scrx=0; scry+=8;}
if (scry>24) scry=0;
}

display.display();

KeyScan(4000); // пауза между измерениями

if (TimeStr>timenonstop) {state=timealert;
digitalWrite(relay_pin, 1); //выключить двигатель
myTimerstp = millis();
timealarm=600;}

} // не alarm и редактирование
break;
}
}
ISR(TIMER1_COMPA_vect)
{
wdt_reset();
}

Теперь немного картинок в процессе тестирования. Слева - направо, сверху - вниз : нормальная работа, без датчика температуры, режим редактирования температуры, выбора времени непрерывной работы.

Хвост датчика температуры протащил через отверстие для трубки хладагента, расковыряв белый пластилин сзади и прошелся герметиком внутри и снаружи:

Фотография термореле, к которому подключаться.

И схема электрическая, на которой пометил цвет проводов.


Подключаем нашу схему к контактам термореле и блока индикациии.


Как видите, через 60сек, для уменьшения эффекта выгорания изображение выводится поочередно от верхнего левого угла до нижнего правого с уменьшенной яркостью и шрифтом .

Настоятельно рекомендую проверить свою arduino на корректную работу с watchdog. Мне пришлось записывать скетч ArduinoISP вливанием примера в одну. К ней подключаю вторую 10-rst, 11-11, 12-12, 13-13, gnd-gnd, +5-+5. В файле board.txt правлю скорость и загрузчик optiboot :

nano.menu.cpu.atmega328old.upload.speed=115200
##nano.menu.cpu.atmega328old.upload.speed=57600

##nano.menu.cpu.atmega328old.bootloader.file=atmega/ATmegaBOOT_168_atmega328.hex
nano.menu.cpu.atmega328old.bootloader.file=optiboot/optiboot_atmega328.hex

В IDE выбираю программатор Arduino as ISP и в IDE выбираю Записать Загрузчик. Детальную информацию найдете на arduino.ru и habr.ru. На всякий случай в архиве два скетча, старый без watchdog-а.

В разделе ФАЙЛЫ добавил ссылку на архив проекта для arduino.

Адрес для контактов : imax9@narod.ru

Читайте также статьи :

STM32F407VET6. Урок 02. Мигаем светодиодами при помощи ШИМ таймера
STM32F407VET6. Урок 01. Мигаем светодиодами и не только
Обзор китайской платы разработчика на STM32F407VET6
Переводим морозильную камеру Атлант 7184 на микроконтроллерное управление
Обзор 9W светодиодной китайской лампочки
Сравнительный обзор конструкторов для сборки лампочек 5,7,12*1W
Обзор драйверов и регулируемых светодиодных лампочек
Светодиодные самодельные ходовые огни
Светодиодные светильники 30W и 5W своими руками
Обзор светодиодов

Если вам понравились мои работы и вы желаете поддержать сайт (отключение навязчивой рекламы ucoza) сделайте дотацию.


При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.

© Максим Ильин 2019г.

Яндекс.Метрика