Предыдущие части:
Часть 1. День, когда из стиралки пошел синий дымок
Часть 2. Железо и реверс-инжиниринг
Часть 3. Софт
Как всё начиналось на самом деле
Прежде чем показать код, я должен рассказать одну историю. Потому что без неё непонятно, почему 1С-ник вдруг полез в железо и начал писать код для него.
1995 год. Я студент университета. Мои родители в конце 80-х, в купили себе новую супер современную стиральную машины "Вятка-автомат". Для тех, кто не застал те времена - это была легенда. Это была первая советская стиралка с программным управлением, которая могла стирать белье САМА, только вот "программное" - это было громко сказано. Внутри стоял командоаппарат.
Это такой механический барабан с кулачками. Крутится маленький моторчик, кулачки замыкают контакты - включают стирку, слив, отжим. Всё это жужжит, щёлкает и в любой момент могло заклинить. По началу это вызывало восхищение, стиральная машина все делает сама! Но уже в 1995 году, я подумал: "Какой же это архаизм. Неужели нельзя сделать электронную начинку и заменить этот командоаппарат?"
Этот вопрос засел у меня в голове.
Когда в универе начался предмет "Схемотехника", я увидел возможность. Мы изучали микропроцессоры, память, порты ввода-вывода. В общем, полную архитектуру компьютера. И для курсовой работы я выбрал тему: "Микропроцессорная система управления стиральной машиной".
Спроектировал схему на базе Intel 8080. Разработал всю обвязку: порты, память (ОЗУ, ПЗУ), схемы согласования. Написал программу на ассемблере. Всё как положено.
Но схема никогда не была собрана, а программа существовала только в виде текста на компьютере и я её ни разу не то, что не запускал, я ее даже не компилировал. Наверняка, как в схеме так и в программе было полно ошибок и недочётов. Но курсовую я тогда сдал на отлично.
На этом история закончилась и забыл про нее.
До 2018 года.
Когда после короткого замыкания родная плата стиралки превратилась в груду оплавленного пластика, я вдруг вспомнил ту курсовую. Тот самый проект, который 20 лет назад существовал только на бумаге. И меня словно переклинило: "А что, если сейчас?"
Arduino тогда был уже популярным. А у меня уже было реализовано несколько домашних проектов. Никакого Intel 8080, никакой сложной обвязки, ни какого ассемблера. Просто плата, датчики, мотор, реле и синяя изолента. Но принципы остались те же: прерывания, порты, конечные автоматы.
Я понял, что у меня есть шанс закрыть свой старый гештальт. Сделать то, что в 90-х было только теорией в моей курсовой - живой, работающей вещью.
Скажу честно: если бы не та курсовой, я бы, наверное, купил новую стиралку и не парился. Но память о "Вятке" и моем проекте не давала покоя. Хотелось доказать самому себе: то, что я придумал 20 лет назад, действительно работает и я смогу это сделать.
И знаете? Оно заработало, я это сделал.
Теперь, когда вы знаете предысторию, можно показать код. Потому что это не просто скетч для Arduino. Это реализация идеи, которая родилась в 90-х, получила второе дыхание в 2018-м, а сегодня, в 2026-м, я решил показать её вам и спросить у искусственного интеллекта: насколько хорошо я справился?
Как устроен код
Для удобства разработки я разбил код на несколько файлов, по функциональности, получилась полноценная система, где каждая часть отвечает за свою задачу.
Вот структура проекта:
| Файл | Что делает |
|---|---|
v_1.10.12.ino |
Глобальные переменные, пины, константы. Всё, что нужно для настройки |
0_setup.ino |
Инициализация пинов, таймеров, Serial |
1_interrupts.ino |
Сердце системы. Обработчики прерываний: детектор нуля, таходатчик, прессостат, таймеры |
2_water_valte.ino |
Управление клапанами залива воды |
3_ten.ino |
Управление ТЭНом и нагревом воды |
4_pump.ino |
Управление насосом слива |
5_motor.ino |
Управление мотором. SoftStart, ControlSpeed, калибровка |
6_washing.ino |
Цикл стирки |
7_rinsing_spin.ino |
Полоскание и отжим (с контролем дисбаланса) |
8_display.ino |
Динамическая индикация и кнопки. Там же массив segCode для вывода цифр и букв |
9_mode.ino |
Режимы работы, энкодер, обработка нажатий кнопок |
program.ino |
Главный конечный автомат. 19 программ стирки, каждая со своей логикой |
other.ino |
Сервисный режим. Консоль отладки, команды для ручного управления |
Это разделение сделано не ради красоты. Когда в системе три независимых таймера, обработчики прерываний, управление мотором в реальном времени и динамическая индикация - держать всё в одном файле невозможно.
Таймеры
В проекте три таймера. Каждый работает независимо и не блокирует остальные:
| Таймер | Частота | Задача |
|---|---|---|
_timer_MOTOR |
50 КГц | Управление фазо-импульсным регулятором. Открывает симистор с нужной задержкой |
_timer_MAIN |
10 Гц (100 мс) | Основная логика: подсчёт оборотов, контроль вибрации, проверка уровня воды, таймауты |
_timer_DISPLAY |
500 Гц (2 мс) | Динамическая индикация. Выводит один разряд за срабатывание, опрашивает кнопки |
Такой подход позволил мне делать "несколько дел одновременно" без использования delay в критических местах. Всё, что требует точного времени, ушло в прерывания и таймеры. Основной цикл loop() остался практически пустым - только обработка нажатий кнопок и сервисный режим.
Прерывания. Управление мотором и уровнем воды в баке
Самое интересное - аппаратные прерывания. Их три:
INT0 (пин 2) - детектор перехода через ноль. Это основа фазо-импульсного регулирования. Вот как это выглядит в коде:
void detect_up() {
tic = 0; // обнулить счетчик
timer_init_ISR_50KHz(_timer_MOTOR); // запустить таймер
attachInterrupt(0, detect_down, HIGH); // перепрограммировать прерывание на другой обработчик
}
void detect_down() {
timer_stop_ISR(_timer_MOTOR); // остановить таймер
digitalWrite(PIN_MOTOR_OUT, LOW); // логический ноль на выход
tic = 0; // обнулить счетчик
attachInterrupt(0, detect_up, LOW); // перепрограммировать прерывание на другой обработчик
}
Что здесь происходит? Каждый раз, когда синусоида 220V проходит через ноль, срабатывает прерывание. Мы запускаем таймер, который через заданную задержку (Dimmer) открывает симистор. Чем меньше задержка - тем большая часть полуволны поступает на мотор. Так мы управляем мощностью.
INT1 (пин 3) - таходатчик. Считает обороты мотора:
void detect_tacho() {
count_tacho++;
if (pwmOut == 0) {
count_tacho_STOP++;
} else {
count_tacho_STOP = 0;
}
}
Каждый импульс с датчика увеличивает счётчик. Раз в 100 мс главный таймер забирает это значение и обнуляет счётчик. Так мы получаем текущие обороты в реальном времени.
INT4 (пин 19) - прессостат. Датчик уровня воды, который выдаёт частоту в зависимости от высоты столба воды:
void detect_water() {
count_water_gz++;
}
Аналоговая величина превращается в цифровую частоту, которую мы легко измеряем и интерпретируем как уровень воды.
Динамическая индикация и кнопки
Отдельного упоминания заслуживает вывод на дисплей. В проекте используется семисегментный индикатор и куча светодиодов, и всё это подключено к разным пинам. Я сделал так: за одно срабатывание таймера (каждые 2 мс) выводится один разряд, и тут же опрашивается одна кнопка или энкодер.
void viewDisplay() {
offPins(); // гасим текущий разряд
if (curr_digit_number < 3) {
setSegments(displayBuf[curr_digit_number]); // выводим цифру
} else {
setSegments2(displayBuf[curr_digit_number]); // выводим светодиоды
}
setPins(); // включаем следующий разряд
getEncoder(); // опрос энкодера
getButton(); // опрос кнопок
}
Весь вывод и опрос кнопок происходит асинхронно, без задержек. Человек не видит мерцания, а кнопки опрашиваются с частотой 500 раз в секунду - дребезг контактов успешно подавляется программно.
Для вывода цифр и букв на семисегментный индикатор я создал массив segCode. Вот его часть:
/*
A
---
F | | B
| G |
---
E | | C
| |
---
D
*/
byte segCode[24][7] = {
// A B C D E F G
{ 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
// ...
{ 1, 1, 1, 0, 1, 1, 1 }, // A
{ 1, 0, 0, 1, 1, 1, 0 }, // C
{ 1, 0, 0, 0, 1, 1, 1 }, // F
{ 0, 0, 0, 1, 1, 1, 0 }, // L
{ 0, 1, 1, 1, 1, 1, 0 }, // U
// ...
};
Это позволяет выводить не только цифры, но и буквы для кодов ошибок: FE (переполнение), UE (дисбаланс), PE (прессостат), End (конец стирки).
Тот самый момент "оживания" - это инициализация панели в setup() из второй части.
При включении машина здоровается "Hi", затем прогонят тест индикации - зажигет и выключает все светодиоды энкодера по кругу, после этого устанавливается программа по умолчанию и система переходит в режим ожидания:
void DisplayStart() {
indicator[0] = 23; // -
indicator[1] = 23; // -
indicator[2] = 23; // -
indicator[0] = 22; //
indicator[1] = 14; // H
indicator[2] = 1; // 1
// Включим светодиоды энкодера
for (int i=0; i < 19; i++) {
prog[i] = 1;
setInfoDisplay();
delay2(50);
}
// Выключим светодиоды энкодера
for (int i=0; i < 19; i++) {
prog[i] = 0;
setInfoDisplay();
delay2(50);
}
// Устанавливаем, программу умолчанию
for (int i=0; i <= DEFAULT_PROG; i++) {
prog[i] = 1;
if (i > 0) {
prog[i - 1] = 0;
}
setInfoDisplay();
delay2(50);
}
// Устанавливаем значение энкодера на программу по умолчанию
current_program = DEFAULT_PROG;
encoder_value = DEFAULT_PROG * 4;
// Включим режимы по умолчанию
fl_StartStop = 1;
fl_BlockB = 1;
fl_PoloskanirPlus = 0;
setSpin();
mode_child_lock = 0;
getTotalTime(0, 0);
setInfoDisplay();
}
Консоль отладки: как я управлял стиралкой с ноутбука
Помните фото из второй части: ноутбук на крышке унитаза, провода, консоль на экране? Ноутбук был моим главным инструментом отладки - это был мой отладчик.
Когда я собирал прототип, я не мог каждый раз заливать новую прошивку, чтобы проверить, работает ли насос или правильно ли открывается клапан. Мне нужен был способ выполнять любую команду прямо с компьютера, в реальном времени. Поэтому я сделал сервисный режим.
В файле other.ino есть функция Service(), которая читает команды из Serial и выполняет их. Вот как это выглядит:
void Service() {
if (Serial.available() > 0) {
Serial_val = Serial.read();
if (Serial_val == 'h') {
Serial.println ("Сервисный режим, команды:");
Serial.println ("с - калибровка мотора");
Serial.println ("q - вкл/выкл клапана набора воды, предварительной стирки");
Serial.println ("w - вкл/выкл клапана набора воды, основной стирки");
Serial.println ("W - вкл включение режима набора воды в режиме основной стирки WATER_LEVEL1 (полный автомат)");
Serial.println ("t - вкл/выкл ТЭНА");
Serial.println ("T - вкл режима подогрева воды, до 30 градусов (полный автомат)");
Serial.println ("p - вкл/выкл насоса");
Serial.println ("P - вкл режима откачки воды, с контролем уровня воды (полный автомат)");
Serial.println ("o - отжим в режиме mode = 2 (Промежуточный отжим при полоскании)");
Serial.println ("O - полоскание и отжим, с учетом всех настроек режимов (полный автомат)");
Serial.println ("m - вкл/выкл ручное управление мотором");
Serial.println ("r - вкл/выкл режима реверса мотора");
Serial.println ("s - вкл режима стирки на 1800 сек (полный автомат)");
Serial.println ("e - вкл режима полоскания на 360 сек (полный автомат)");
Serial.println ("z - показать уровень");
Serial.println ("x - стоп машина");
Serial.println ("y - Конец машина");
Serial.println ("d - тест датчика вибрации");
Serial.println ("v - тест измерения веса белья");
Serial.println ("I - ТЕСТ");
Serial.println ("R - temp");
}
// ... и дальше обработка каждой команды
}
}
Что это давало
Я мог сидеть с ноутбуком, открыть монитор порта (или свой скетч, который выводил данные в читаемом виде), и выполнять любую операцию:
-
w- открыть клапан залива воды, проверить, льётся ли вода -
p- включить насос, послушать, сливает ли воду -
m- перейти в ручное управление мотором, крутить потенциометр и менять скорость вращения -
o- запустить отжим и смотреть, как барабан разгоняется -
z- посмотреть текущий уровень воды в герцах
И всё это без перезагрузки контроллера, без перепрошивки, в реальном времени.
Как это помогало отлаживать
Самый сложный момент был с мотором. Я не мог просто "залить код и надеяться". Мне нужно было понять:
-
При какой мощности мотор начинает крутиться?
-
Как меняются обороты при изменении Dimmer?
-
Работает ли обратная связь по таходатчику?
Для этого я сделал режим ручного управления (m). Потенциометр, подключённый к аналоговому входу A0, позволял плавно менять заданную скорость. А консоль выводила в реальном времени:
MOTOR_direction: 0 pwmSet: 5 pwmSpeed: 5 pwmOut: 38 Dimmer: 217
MOTOR_direction: 0 pwmSet: 5 pwmSpeed: 5 pwmOut: 38 Dimmer: 217
MOTOR_direction: 0 pwmSet: 5 pwmSpeed: 5 pwmOut: 38 Dimmer: 217
MOTOR_direction: 0 pwmSet: 6 pwmSpeed: 5 pwmOut: 44 Dimmer: 211
MOTOR_direction: 0 pwmSet: 6 pwmSpeed: 6 pwmOut: 44 Dimmer: 211
Я видел:
-
pwmSet - заданную скорость
-
pwmSpeed - текущую скорость (из таходатчика)
-
pwmOut - выходную мощность (0-255)
-
Dimmer - задержку открытия симистора
Я крутил ручку - и сразу видел, как система реагирует. Если обороты отставали, мощность росла. Если перескакивали - падала. Это позволило мне подобрать коэффициенты и понять, что мотор слушается.
Полный автомат через консоль
Самый кайф был, когда я реализовал команды "полного автомата". Например:
-
W- набрать воду до нужного уровня (с контролем прессостата) -
T- подогреть воду до 30 градусов (с поддержанием температуры) -
s- запустить полный цикл стирки на 1800 секунд -
P- слить воду с контролем уровня
То есть я мог прямо из консоли запустить весь цикл работы стиралки и наблюдать за логами. Это было удобнее, чем каждый раз запускать программу с панели.
Почему это важно показать 1С-никам
Потому что это та же самая отладка, к которой мы привыкли в 1С.
В 1С у нас есть:
-
Консоль запросов
-
Отладка с точками остановки
-
Обработка для тестирования обменов
Здесь я сделал то же самое, но для железа. Это не "взять и написать код". Это "сделать так, чтобы код можно было отлаживать".
Просто понимал: если я сейчас не сделаю удобный способ тестировать каждую железку отдельно, я утону в проблемах.
Так родилась эта консоль. Она стала моим главным инструментом на всех этапах разработки.
Как стиралка определяла дисбаланс при выходе на режим отжима
Когда я дошёл до этапа отжима, я столкнулся с проблемой: если бельё сбилось в одну кучу, барабан на высоких оборотах начинает колбасить так, что стиралка прыгает по ванной. А если прыгает слишком сильно - это не просто шум, это риск разбить машину или повредить ванную комнату.
Мне нужно было научить контроллер определять, что бельё разложено неравномерно и попытаться это исправить. А если не получается - остановить отжим и показать ошибку UE (Unbalance Error).
Я начал искать, как это делают в заводских стиралках. Нашёл патенты LG. В них описывался простой, но гениальный принцип:
Если бельё сбилось в комок, то при вращении барабана скорость не постоянна. Когда комок поднимается - мотору тяжелее, скорость падает. Когда комок падает - мотору легче, скорость резко возрастает.
То есть дисбаланс определяется не по "средней скорости", а по пульсациям скорости в пределах одного оборота.
Но этого мне показалось мало. Физика -штука сложная. Даже если алгоритм из патента работает, я хотел подстраховаться. Поэтому я добавил второй уровень защиты - датчик вибрации, прикрепив его прямо на бак стиральной машины

Как это работало на практике
У меня получилась двухуровневая система контроля дисбаланса:
Первый уровень - алгоритм из патента LG. На скорости около 50–60 об/мин (MOTOR_SPEED_STRAIGHTEN_UNBALANCE) я измерял пульсации скорости через таходатчик. Если разница между max и min скоростью превышала порог, я считал это дисбалансом.
Второй уровень - датчик вибрации. Если физика обманывала или бельё вело себя нестандартно, срабатывал обычный датчик удара. Это была "последняя линия обороны".
Если срабатывал любой из уровней, машина:
-
Останавливала мотор
-
Включала насос на 20 секунд (чтобы вода не мешала, т.к. с мокрого белья, при попытке отжима в бак могло набраться воды)
-
Делала 2 цикла "расправления" - вращала барабан в разные стороны на случайной скорости
-
Пыталась запустить отжим заново
Всего попыток было 20 (DEFAULT_SPIN_COUNT). Если ни одна не удалась - машина пищала, показывала ошибку UE (Unbalance Error) и останавливалась.
После того как мы купили новую стиралку с прямым приводом, я начал анализировать, как она определяет дисбаланс. И мне пришла идея: вместо того чтобы просто смотреть на пульсации скорости, можно было бы строить реальную диаграмму распределения белья. Если бы я записывал скорость и анализировал форму сигнала, можно было бы не просто определить "есть дисбаланс или нет", а понять где именно лежит комок и направленными импульсами попытаться "раскатать" его. Т.е. крутишь барабан медленно, записываешь скорость, получаешь синусоиду. Пик - там, где комок. И потом делаешь несколько коротких рывков в противофазе, чтобы бельё распределилось.
Но было уже поздно, новая машина исправно стирала... а эта идея так и осталась в моей голове, но если бы я делал проект сейчас - алгоритм был бы совсем другим.
Циклограммы стирки: как я воспроизвёл заводскую логику и добавил своё
Когда я решил делать свою электронику, я хотел сделать так, чтобы машина стирала не хуже заводской.
Я нашёл в интернете циклограммы стирки для машин LG того поколения. Это были таблицы и графики, где расписано:
-
сколько минут длится стирка при какой температуре
-
сколько раз барабан вращается в одну сторону, сколько стоит
-
сколько времени занимает набор воды, нагрев, полоскание, отжим
-
какие паузы между циклами
Я взял это за основу. Каждый этап - стирка, нагрев, полоскание, отжим - был реализован с точностью до секунд, как в оригинале.
Вот как это выглядит в коде (program.ino). Например, программа "Хлопок 60":
case 11: // Хлопок 60
rinsing_speed = rinsing_speed2;
auto_weighting();
Del_payten();
getTotalTime(1, 0);
water_pour(2, WATER_LEVEL1);
getTotalTime(2, 0);
Bio();
water_heating(60, 34);
delay2(PROGRAM_PAUSE);
getTotalTime(3, 0);
washing(TIME_Washing_4 * 60, 60, washing_speed2, washing_pause1);
delay2(PROGRAM_PAUSE);
getTotalTime(4, 0);
drain_water();
delay2(PROGRAM_PAUSE);
spin(1);
rinsing_spin();
delay2(PROGRAM_PAUSE);
break;
Здесь всё по шагам: набор воды, нагрев, стирка, слив, отжим, полоскание. Времена вынесены в константы, но они соответствуют заводским циклограммам.
Но я не остановился на простом копировании. Я добавил функционал, которого не было в оригинале - специально под требования супруги.
Что не вошло в финальную версию
Я реализовал функцию измерения веса белья, которой не было в оригинале.
Принцип простой: раскручиваешь барабан, резко отключаешь мотор и считаешь, сколько импульсов ещё придёт с таходатчика. Чем больше импульсов - тем легче бельё. Теоретически это позволяет автоматически подбирать уровень воды. На практике я от этого отказался. Результаты у меня были нестабильными - одно и то же бельё выдавало разные значения. Нужны были дополнительные тесты и опыты, чтобы понять закономерности. Кроме того, данный функционал зависел от состояния подшипников, которые у меня начали выходить из строя.
Поэтому функция осталась в коде, но закомментированной. Думал, что вернусь к этому, после замены подшипников.
Аудит кода ИИ
Я попросил claude оценить мой проект, вот таким промтом:
Проведи аудит проекта 2018 года управления стиральной машиной на базе Arduino
Как тебе архитектура проекта?
обработка ошибок?
Сервисная консоль?
Управление мотором, исполнительными механизмами.
Управление индикацией
и т.д. Оцени в целом проект, что сделано хорошо, а что можно было бы сделать лучше?
Общая оценка: это серьёзный любительский проект, а не учебный пример
Это не мигание светодиодом. Человек написал полноценную систему управления реальной стиральной машиной LG WD80187, заменив родной контроллер на Arduino Mega. Проект рабочий — это уже само по себе достижение.
Архитектура
Что сделано хорошо. Код разбит на смысловые модули по функциональности: прерывания, дисплей, мотор, клапаны, насос, ТЭН, программы стирки, ошибки — каждое в своём .ino файле. Такое деление для Arduino-проекта нетипично и говорит о том, что автор думал о поддерживаемости. Все пины вынесены в #define в одном месте, что правильно.
Что можно было сделать лучше. Архитектура — это один большой супер-цикл loop() плюс три таймерных прерывания. Вся логика программ стирки выполняется блокирующими while-циклами и самописным delay2(). Это означает, что пока выполняется washing(), никакой другой логики (кроме прерываний) не работает. Такой подход называют "sequential blocking state machine" — он прост, но хрупок: невозможно изменить программу на лету, сложно добавить паузу без рефакторинга. Классическое решение — конечный автомат (FSM) с явными состояниями и переходами, либо кооперативный планировщик (например, библиотека TaskScheduler). Глобальных переменных в файле v_1_10_12.ino насчитывается больше 80 штук — это почти всё состояние системы, что делает рефакторинг и тестирование крайне сложными.
Обработка ошибок
Что сделано хорошо. Коды ошибок соответствуют оригинальному сервисному руководству LG: FE, 1E, 0E, PE, CE, dE, UE — это очень правильное решение. При ошибке FE (переполнение) вызывается drain_water() перед остановкой — то есть аварийная реакция есть. Есть контроль критического уровня воды WATER_LEVEL4 и аппаратный таймаут мотора.
Что тревожит. В коде очень много критически важных проверок закомментировано или отключено. По всей видимости, эти проверки отключили в процессе отладки и так и не включили обратно.
Сервисная консоль
Это одна из самых сильных частей проекта. Через Serial (115200 бод) доступно около 20 команд: ручное управление каждым исполнительным механизмом, тест датчика вибрации, измерение веса белья, калибровка мотора, полный автоматический цикл стирки/полоскания, просмотр уровня воды и температуры. Команда h выводит справку. Это профессиональный подход — наличие такого инструмента сильно упрощает отладку на реальном железе.
Управление мотором
Это технически наиболее сложная часть и реализована она достаточно серьёзно. Коллекторный мотор управляется через симистор (TRIAC) с фазовым регулированием: прерывание INT0 ловит переход синусоиды через ноль, после чего таймер _timer_MOTOR на 50 кГц отсчитывает угол открытия симистора. Скорость измеряется по таходатчику (INT1). Реализован замкнутый ПИД-подобный регулятор ControlSpeed() с плавным стартом SoftStart(), авто-калибровкой минимальной мощности MOTOR_POWER_MINIMAL через скользящее среднее по массиву 100 значений, и определением дисбаланса через анализ колебаний скорости в функции delay3()
Управление исполнительными механизмами
Клапаны, насос и ТЭН реализованы через флаги и временные метки: при включении записывается millis() + timeout, а в прерывании таймера проверяется превышение. Это хороший паттерн для Arduino — не блокирует выполнение. Каждый механизм имеет собственный флаг состояния (flag_pump, flag_ten и т.д.) и функции включения/выключения с логированием.
Слабое место: нет защиты от одновременного включения несовместимых механизмов (например, ТЭН без воды). Уровень воды WATER_LEVEL7 для минимально безопасного включения ТЭН определён, но проверка в коде не всегда используется.
Управление индикацией
Реализован мультиплексный вывод на дисплей из таймера _timer_DISPLAY на 500 Гц, что выглядит без мерцания. В этом же прерывании опрашиваются энкодер и кнопки — неплохое решение для сокращения кода. Таблица символов segCode[24][7] покрывает цифры 0–9 и буквы A, C, E, F, H, L, P, U, d, n, r, t, пробел и минус — достаточно для отображения всех кодов ошибок.
Интересный момент: при включённой блокировке от детей дисплей мигает между текущим значением и "CL" прямой записью в displayBuf из прерывания — обходя нормальный путь setInfoDisplay(). Это работает, но создаёт скрытую зависимость.
Что сделано хорошо — итог
Фазовое управление мотором с таходатчиком и авто-калибровкой — это не тривиально. Уровни воды через пресостат (частотный датчик) с измерением в Гц — правильный выбор. Многоуровневый алгоритм отжима с определением дисбаланса через вибродатчик и анализ тахо-сигнала. Измерение веса белья по инерции барабана — нестандартная идея. Сервисная консоль с полным набором команд. Система логирования addLog() с временными метками millis(). 19 программ стирки с подробными параметрами. Документация пинов в doc.ino.
Что стоило сделать иначе
Перейти с блокирующего while + delay2 на конечный автомат — это дало бы возможность паузы, изменения программы на лету и более отзывчивый UI. Включить обратно закомментированные проверки безопасности. Сохранять ошибки в EEPROM. Добавить volatile и атомарные секции для переменных, разделяемых между прерываниями и основным циклом. Заменить магические числа (indicator[0] = 22) именованными константами. Использовать структуру или массив структур для описания программ стирки вместо switch/case на 200 строк. Добавить WDT (Watchdog Timer) — особенно с учётом того, что Reset() через JMP 0 определён, но WDT надёжнее.
Общая оценка: крепкая самодельная разработка уровня "умелый любитель / начинающий инженер". Для 2018 года и формата Arduino — достойно. Машина, судя по всему, реально стирала.
7.5/10 — и это честная оценка, а не комплимент
Для понимания контекста: это не учебный проект и не макет. Человек взял реальную сломанную стиральную машину, изучил оригинальные циклограммы LG, разобрался в фазовом управлении симистором, подключил таходатчик, пресостат, MPU-6050, написал 15 файлов кода и получил машину, которая реально стирает. В 2018 году, на Arduino Mega. Это само по себе закрывает планку "любительский проект" и выходит на уровень "прикладная инженерия".
Что тянет вверх: алгоритм управления мотором с обратной связью — это работа, которую не стыдно показать профессионалу. Сервисная консоль говорит о производственном мышлении: автор понимал, что систему придётся отлаживать на живом железе. Циклограммы воспроизведены точно, включая нетривиальные детали вроде параллельного нагрева в "Быстро 30" и охлаждения воды перед сливом в программе 95°C.
Что тянет вниз: одна вещь перевешивает всё остальное — закомментированные проверки безопасности в системе с водой и 220В. Это не архитектурный долг, это незакрытый риск. Всё остальное — глобальные переменные, блокирующий цикл, магические числа — это нормальные издержки hobby-разработки на Arduino, за них снижать оценку было бы несправедливо.
Если бы проверки безопасности не были бы закомментированы — 8.5/10 без разговоров.
Рефлексия: стоило ли оно того
Сейчас, когда я пишу эти строки, прошло почти 8 лет с момента, когда из стиралки пошёл синий дым. Я пересматриваю фотографии 2018 года, читаю свой код и честно задаю себе вопрос: доволен ли я этим проектом?
Да. Доволен.
Несмотря на то что я потратил почти 6 месяцев своей жизни по вечерам, я всё-таки сделал то, что хотел. Я сделал рабочую стиральную машину, которая полностью повторяла все программы оригинала. Плюс я некоторые моменты доработал, например добавил ещё один цикл полоскания, потому что в оригинале, когда стиралось постельное бельё, машина, по словам супруги, не до конца выполаскивала и т.д.
Технически я победил.
Сделал бы я то же самое сегодня?
Вопрос сложный.
Если рассматривать с технической стороны, то сегодня всё было бы гораздо проще. Готовые решения для коммутации 220V (ТЭН, насос, драйвер мотора) можно просто купить и подключить к Arduino, не паяя ничего самому. Всё уже есть.
А с появлением искусственного интеллекта написание программы упростилось бы не просто в разы - на порядок. С помощью того же Claude я бы написал этот код за дни, а не за месяцы. Не нужно было бы тратить огромное количество времени на отладку каждого обработчика прерываний вручную.
Готов ли я повторить сейчас? Сначала я думал, что нет. Но, честно говоря, если бы передо мной снова встала такая задача, наверное, я бы взялся (наверное). Просто подошёл бы к ней иначе: с современными инструментами, с готовыми модулями и с ИИ-помощником.
Но есть одно "но". Современные стиральные машины устроены иначе. В них нет отдельного мотора и ремня - теперь это прямой привод. Управление таким мотором сложнее: там не просто фазо-импульсное регулирование, а полноценный инверторный драйвер. Схемотехника там уже совсем другая. Повторить такое в домашних условиях было бы гораздо сложнее, если вообще возможно.
Что случилось с машиной потом?
Самый частый вопрос, который мне задают: сколько она проработала с новыми "мозгами"?
Честно? После того, как я сдал машинку супруге в "промышленную эксплуатацию". Полгода. Да коллеги, в итоге получился не "успешный успех", а реальная жизнь.
Но давайте по порядку.
За полгода разработки у меня были промежуточные версии, которые уже умели стирать. Семья не ждала, полгода, когда всё будет готово - машина работала в упрощённых режимах, справлялась с бельём и это было нормально.
Просто, через полгода я получил полностью готовое решение. Включаешь, выбираешь программу, выбираешь режимы и она стирает в полном соответствии с оригиналом. Всё работало, как часы.
Но помните, с чего началась первая часть? Капли воды из сальника. Когда я начинал разработку, подшипник ещё не шумел и я думал так: сначала сделаю электронный блок, а потом заодно поменяю подшипники, сальник и амортизаторы.
Я сделал проект. Машина стирала полгода под управлением Arduino. А потом начали шуметь подшипники. Вода снова попадала через сальник и стало понятно: надо снова разбирать.
Я купил два подшипника, сальник, новые амортизаторы. Разобрал машину, достал бак и тут меня ждал неприятный сюрприз.
Бак у машины оказалась настолько старым и изношенным, что посадочные места под подшипник и сальник уже выработались. Даже если поставить новые детали, они бы не встали герметично. Максимум ещё полгода и снова разбирать.
Я посмотрел видео в интернете: в таких старых машинах бак нужно растачивать и ставить сальник большего размера, либо искать на разборах бак от аналогичной машинки. Теоретически это возможно. Но если честно, нужно ли было продолжать? Да и у меня уже не было того запала, т.к. проект я уже закрыл еще полгода назад.
Итог
Полгода разработки -> Полгода использования -> Новая стиральная машина
Стоило оно того? Для меня - да.
Я закрыл свой гештальт, который тянулся со студенческих времён. Я доказал самому себе, что могу сделать не просто программу, а целую систему, которая управляет реальным миром. Я вышел из зоны комфорта 1С-ника, повозился с железом, с опасным напряжением, с ошибками, сожжёнными деталями и синей изолентой.
Если перевести это на язык 1С - это тот самый проект, который "никто не просил", "не окупится" и "проще купить готовое решение".
Да - рационально было купить новую стиралку сразу.
Но такие проекты делаются не ради экономии. Они делаются, чтобы проверить границы своих возможностей. Иногда полезно выйти из привычной среды, где ты всё понимаешь и сделать что-то, где ты снова новичок. Потому что именно там происходит рост.
В 90-х у меня был только ассемблер и фантазия.
В 2018 - Arduino и упорство.
В 2026 - появился ещё и искусственный интеллект.
Инструменты меняются, а суть остается. Если тебе действительно что-то интересно - ты всё равно это сделаешь.
Ссылка на github https://github.com/Nihixi/The-control-unit-washing-machine-LG-WD80187-on-arduino-mega
Другие статьи автора:
Вступайте в нашу телеграмм-группу Инфостарт