Найти тему
Dexter's Lab

АВТОМАТИЧЕСКОЕ УПРАВЛЕНИЕ СУШИЛКАМИ УГЛЯ на базе ARDUINO (Часть 4)

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

Итак, сердцем нашего устройства является плата Arduino Mega 2560 R3 на базе микроконтроллера ATmega2560 (на фотографии ниже он под номером 3):

Кнопка 4 необходима для перезапуска платы, разъём 1 служит для подключения источника питания 7-12 вольт. Ниже стрелкой я обозначил место под стабилизатор напряжения AMS1117 в корпусе SOT-223 на 5 вольт (это напряжение необходимо для работы платы). Я случайно сжёг стабилизатор, неосторожно закоротив +5 вольт на плате на "землю". Бывает. Фото сделано в момент замены стабилизатора (стабилизатор напряжения 3,3 вольта для питания самого контроллера не пострадал:).

По периметру платы расположены разъемы входов/выходов, всего которых 54, то есть более чем достаточно для наших нужд.

Через разъём USB A (под цифрой 2 на фото) будем "шить" нашу плату. Для разработки кода и прошивки контроллера использую среду ARDUINO IDE:

-2

Чёрное поле внизу - область, где отражается процесс "заливки" ПО на плату, а также возможные ошибки кода, если таковые были сделаны программистом.

Стрелкой вверху я указал строку, где перечислены открытые в среде файлы с кодом. Удобно фрагменты кода сохранять в отдельные файлы и вызывать их из файлов, просто указав название файла. В данный момент открыт файл menu.h - то самое меню, отражающееся на дисплее 20х04:

-3

Фото было сделано до последней доработки кода. Здесь не отражен последний пункт меню: "ПРОВЕТРИВАНИЕ". Расскажу о нём позже.

Кратко об остальных файлах:

Drying_Shop_Script.ino - это основной файл, к нему присоединены все остальные. Файл содержит код обработки данных датчиков и управления механизмами сушилок. Также в нём содержится таблица, отражающаяся на дисплее 128х64, вот этом:

-4

..., а также таймеры, настройки шрифтов меню и стартовой экранной заставки, и много другой информации.

Подключал я этот экран по протоколу SPI. При подключении я ориентировался вот на это фото из сети:

-5

Файл cat.h содержит зашифрованную картинку стартовой заставки. На момент разработки данного устройства мы сотрудничали с "Pedal Correctors", оказывающих услуги ремонта аудиотехники. За основу рисунка заставки был взят их логотип:

-6

А вот так выглядит код кота в шестнадцатиричном виде, привожу только фрагмент (используется drawXBMP):

static unsigned char u8g_cat_bits[] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x67, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f,
0xbf, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xb0, 0x7d, 0x7e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xaf, 0x67, 0x3f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x2f,
0xaf, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0xf6, 0xad, 0xfe, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xef, 0xb6, 0xff, 0xfd, 0x03, 0x00,

// здесь ещё много строк ...

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x10, 0x2b, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x10,
0xc9, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};

Этот кот в упрощённом виде появляется на экране в момент старта устройства.

Следующий файл pins.h содержит информацию с номерами контактов на плате Arduino, к которым подключены все устройства системы.

pinMode(2, OUTPUT); // управление реле заслонки в первой комнате
pinMode(23, INPUT); pinMode(22, OUTPUT); // один пин отвечает за считывание показаний датчиков, на другом пине постоянно логическая "1" (+5 вольт) - для притягивания первого (считывающего) пина к положительному выводу источника питания через резистор (поясню чуть ниже)
pinMode(45, INPUT);
// считывание состояния тумблера. Далее схожие группы контактов для других комнат:
pinMode(3, OUTPUT);
pinMode(25, INPUT); pinMode(24, OUTPUT);
pinMode(43, INPUT);
pinMode(4, OUTPUT);
pinMode(27, INPUT); pinMode(26, OUTPUT);
pinMode(41, INPUT);
pinMode(5, OUTPUT);
pinMode(29, INPUT); pinMode(28, OUTPUT);
pinMode(39, INPUT);
pinMode(6, OUTPUT);
pinMode(31, INPUT); pinMode(30, OUTPUT);
pinMode(37, INPUT);
pinMode(7, OUTPUT);
pinMode(33, INPUT); pinMode(32, OUTPUT);
pinMode(35, INPUT);

pinMode(8, OUTPUT); // управление вытяжным компрессором

pinMode(9, OUTPUT); // в случае аварийной перезагрузки этот контакт активирует реле времени (о нём рассказал в предыдущей статье)

Далее подключаю энкодер:
pinMode(49, INPUT); // pin_CLK
pinMode(51, INPUT); // pin_DT
pinMode(53, INPUT_PULLUP); // pin_Btn

Следующий файл rus.h содержит русские буквы для дисплея 20х04. Русские буквы, отличные от букв латинского алфавита, пришлось отражать специальным кодом:

byte bukva_B[8] = {B11110,B10000,B10000,B11110,B10001,B10001,B11110,B00000,}; // буква "Б"
byte bukva_G[8] = {B11111,B10001,B10000,B10000,B10000,B10000,B10000,B00000,}; // буква "Г"
byte bukva_D[8] = {B01111,B00101,B00101,B01001,B10001,B11111,B10001,B00000,}; // буква "Д"
byte bukva_ZH[8] = {B10101,B10101,B10101,B01110,B10101,B10101,B10101,B00000,}; // буква "Ж"
- и так далее для всех русских букв, не имеющих латинских аналагов по написанию, тогда как, например, слово КОМНАТА записывается содержащимися в памяти дисплея символами из латиницы.

Файл rus5x8.h от Maxim Z. - содержит шрифт размером 5х8 пикселей с русскими буквами для дисплея 128х64.

Файл sensors.h - подключаю всех шесть датчиков, задаю их имена в системе:

DHT dht_1(23, DHT22);
DHT dht_2(25, DHT22);
DHT dht_3(27, DHT22);
DHT dht_4(29, DHT22);
DHT dht_5(31, DHT22);
DHT dht_6(33, DHT22);

И, наконец, файл variables.h. Здесь содержатся значения всех переменных, некоторые из них для удобства обращения к ним, объединены в массив. В этот файл при ошибке датчиков сохраняются значения всех переменных, отсюда же считываются значения переменных при вынужденной перезагрузке. И, конечно, сюда сохраняются все переменные, изменяемые вручную (настройки пользователя) или автоматически (изменяемые кодом). Для примера привожу код для одной комнаты:

int cur_hum_1 = 1; // задал переменную для показаний датчика относительной влажности
int cur_temp_1 = 1; // задал переменную для показаний датчика температуры
int cur_hum_com = 1; // задал переменную для суммы показаний всех шести датчиков относительной влажности. Если хотя бы один из датчиков уйдёт в ошибку (покажет "0"), сохраняемся в EEPROM и уходим в перезагрузку
long hum_incr_time_1_ms = 600000; // переменная отражает в миллисекундах период времени, в течение которого относительная влажность должна достигнуть верхнего предела (расскажу попозже поподробнее)
long time_fresh_1_ms = 0; // в переменную в миллисекундах сохраняется время, в течение которого нужно проветрить комнату по окончании сушки
bool active_1; // сюда записывается состояние тумблера активации комнаты (на передней панели): значение "0" будет соответствовать активному состоянию комнаты, значение "1" - комната дезактивирована
bool fan_on = 0; // переменная состояния компрессора (ВКЛ/ВЫКЛ)
long fan_time = 0;
// эта переменная нужна для отсчёта времени до отложенного запуска компрессора (пока полностью не откроется заслонка хотя бы в одной комнате, компрессор не запустится)
bool reset = 0;
int state = 0;
// переменная для отслеживания ошибки датчиков и реакции на ошибку
struct Dryers { // массив с переменными комнат
int top_hum_1 = 99; // сушим уголь всегда в каком-то диапазоне относительной влажности, "коридоре" что ли. Данная переменная задаёт верхнюю границу диапазона. При старте задать можно вручную через меню, а по умолчанию - 99%
int dif_hum_1 = 10; // разница между верхним пределом и нижним пределами диапазона. По умолчанию - 10%. Это значит, что нижняя граница будет 99%-10%=89%. Переменная задаётся вручную из меню.
int bot_hum_1 = top_hum_1 - dif_hum_1; // та же нижняя граница, автоматически вычисляется и записывается в эту переменную
int temp_thres_1 = 45; // можно задать вручную значение температуры, ниже которого температура в комнате никогда не упадёт.
bool hum_down_1 = 1; // после нескольких циклов сушки и открытия/закрытия заслонки в определённом коридоре относительной влажности уголь начинает сохнуть медленнее, поэтому можно снижать температуру в комнате. В эту переменную код сохраняет значение, пора ли снижать температуру ("1") или ещё рано ("0")
int hum_incr_time_1 = 10; // время интервала, задаётся вручную, по умолчанию - 10 (аналог переменной long hum_incr_time_1_ms, только в минутах). Если в течение 10 минут с момента закрытия заслонки относительная влажность не падает, будем снижать оба значения диапазона
long time_close_1 = 0; // переменная, отвечающая за своевременное закрытие заслонки
bool open_1 = 0; // переменная состояния заслонки (открыта/закрыта)
int perc_down_1 = 5; // снижаем верхнюю границу диапазона относительной влажности на значение в процентах из этой переменной. Процент снижения задаётся вручную, по умолчанию - 5%. Вслед за верхней границей автоматически снизится и нижняя
bool new_start_1 = 1; // эта переменная задаётся вручную (из меню) при загрузке в комнату новой партии угля
int time_fresh_1 = 0; // переменная задаётся вручную из меню, в ней сохраняется в минутах значение времени, в течение которого нужно проветрить комнату по окончании сушки
long incr_time_fresh_1 = 0; // эта переменная работает, как счётчик, в неё дискретно, с интервалом в 4,2 секунды, заносится время, в течение которого в данный момент проветривается комната

... далее здесь идёт такой же код для остальных пяти комнат, пропускаю его сейчас ...
};
Dryers dryers;
// объединили все данные в массив, из него будем выводить значения на оба дисплея в том числе

Написание кода и сборка цифровой части заняли у меня в общей сложности месяц. Начал я с написания на листке бумаги алгоритма (сценария) работы одной камеры, одновременно создавая переменные с удобными для оперирования ими названиями.

Также стояла задача подключить одновременно оба дисплея (я сделал это по разным протоколам), разместить их коды в нужных местах скрипта.

Поскольку дисплеи сидят на лицевой стенке щита, к ним тянется довольно широкая шина с проводами. Провода я заплёл в косички для уменьшения наводок. Это показано стрелкой и хорошо видно внизу на следующем фото:

-7

Реле пронумерованы согласно номерам комнат (1-6), заслонками в которых они управляют. Номер 7 - это компрессор для сброса влажного воздуха.

Для предотвращения помех на провода питания схемы я надевал ферритовые кольца. Одно из них хорошо видно здесь:

-8

Отдельно остановлюсь на подключении тумблеров активации оборудования каждой камеры, вот этих:

-9

Когда тумблер замкнут, это явная логическая единица на входе платы. Но когда тумблер разомкнут пользователем, провода ловят из окружающей среды всё подряд, и на входах на плате появляется дребезг, то есть то "1", то обратно "0". Для предотвращения наводок выходы с тумблеров притянуты к массе через резисторы. Я реализовал это прямо на плате (правая из двух группа - подтяжка контактов тумблеров):

-10

Рекомендовано сопротивление 5-50 кОм. У меня 6 резисторов. Если допустить, что все комнаты будут выключены, общее сопротивление параллельно подключённых резисторов не должно быть ниже 5 кОм, поэтому я использовал резисторы номиналом 51 кОм. Ближний ряд пинов (стрелка) - рабочие, с них считываются состояния тумблеров (ВКЛ ("1")/ВЫКЛ ("0"")), дальний ряд пинов притянут к массе (капля припоя у правого из шести резисторов).

Теперь левая группа:

-11

Сюда подключены датчики (температуры/относительной влажности воздуха). Их требуется подтягивать к положительному полюсу питания. "Плюс" питания - это как раз два пина, крайние левые на фото, рядом с винтом крепления к стойке. На этих пинах (без номера) по умолчанию +5 вольт. Помимо этого я программно привёл к каждому из спаянных вместе пинов входа/выхода логическую единицу, для верности. Сопротивление резисторов то же, 51 кОм. Датчик самой дальней от пульта комнатой соединён с системой кабелем длиной более 20 м. Без описанной выше подтяжки датчики показали бы что угодно, только не правду, и виноваты в этом были бы помехи из окружающей среды.

Контакты дисплеев 20х04 и 128х64 подтягивать схожим образом не пришлось, дисплеи и так всё прекрасно показывают, а подтяжка контактов энкодера реализована прямо на плате энкодера самим его производителем.

Я думаю, аппаратная часть достаточно подробно освещена, можно опять перейти к коду. Сделаем это в следующей статье:)