Контроллер стиральной машины на ардуино.

bort707

★★★★★★✩
21 Сен 2020
3,358
971
Илья, вы такой правильный, не пойму почему вы строем не ходите...ой, хотел сказать почему не хотите начать этот проект сами.
Поймите, когда вы сами реализуете безумную идею - это одно. А когда пытаетесь развести других, чтобы они все за вас сделали - совсем другое. Нахлебников никто не любит.
 

Boroda22

★✩✩✩✩✩✩
23 Фев 2022
265
41
Лично я, в таком случае, разобрал бы стиралку полностью и провёл бы полный осмотр всех узлов и дефектовку при малейшем подозрении на выход из строя какого-то элемента, например патрубков и т. п.
ну так разберите стиралку, соберите контроллер и схему, и потом собрать не забудьте, проверьте программы для машины, и сообщите о результатах. зачем вот это всё "разобрал БЫ", "провел осмотр" и т. п. сделайте, потом расскажите

Нередко так рождаются великие идеи и изобретения, изменившие историю;)
нее, это чисто случайность, когда хотят сделать одно, а потом получается совершенно другое, и все такие: "О, да мы же это и хотели изобрести" 😁
 
  • Лойс +1
Реакции: bort707

poty

★★★★★★★
19 Фев 2020
3,567
1,022
@Ilya_G, когда сам "изобретатель" себя гробит - я могу это понять, в конце-концов имя этого изобретателя, в случае успеха, будет известно всем, останется в веках (хотя ничего гарантировать нельзя). Но вот зачем других вести по опасному пути?
Что касается "разобрать полностью", "провести дефектовку"... Во-первых, современное (и я говорю про вещи конца прошлого века - начала текущего) производство довольно часто делает детали неразборными - так проще обеспечить их надёжность. Как пример - разборный бак: никто в здравом уме всё равно не будет менять на нём прокладки, потому что есть много факторов, которые приводят к тому, что действовать как изначально задумывалось это всё равно не будет: механические нагрузки приводят к деформациям, устаёт металл, особенно в зонах креплений и проч.
Во-вторых, провести материаловедческую дефектовку - не под силам и многим компаниям, я не говорю уже о стоимости этого дела.
В-третьих, осмотр, допустим, электрической изоляции - ни о чём, как и замена электропроводки для работы во влажных помещениях (отсылаю Вас к ПУЭ). Стиральная машинка - один из самых мощных потребителей с самыми сложными условиями для защиты.
Ну, и в четвёртых... Сколько же времени потребуется для того, чтобы всё это сделать? И чем заменять то, что отнесено в гараж, разобрано до основания и "ждём попадания в область видимости чего-нибудь подходящего для замены"? Не проще заняться чем-то более интересным, а время посвятить зарабатыванию этих пары тысяч разницы между купить готовую деталь для замены и переделать всю машинку?
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1
хотел сказать почему не хотите начать этот проект сами.
Я и не против, но! нужна помощь. По крайней мере человека, который разбирается в машинках и технологии стирки
Не проще заняться чем-то более интересным, а время посвятить зарабатыванию этих пары тысяч разницы между купить готовую деталь для замены и переделать всю машинку?
1. Электроника - моё хобби и моя же работа
2. Этот проект мне выгоден напрямую
Ну, и в четвёртых... Сколько же времени потребуется для того, чтобы всё это сделать? И чем заменять то, что отнесено в гараж, разобрано до основания и "ждём попадания в область видимости чего-нибудь подходящего для замены"?
ну так разберите стиралку, соберите контроллер и схему, и потом собрать не забудьте, проверьте программы для машины, и сообщите о результатах.
При всём желании создать что-то типа "умного таймера можно" за 1 вечер. Но есть несколько но, которые как минимум могут испортить всю малину. Где нибудь ниже я это опишу
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1
Но есть несколько но, которые как минимум могут испортить всю малину. Где нибудь ниже я это опишу
1. Управление. В своём большом сообщении я описывал то, что обычной ардуинки наврядли хватит для опроса всех кнопок, энкодеров, сенсоров, вывода изображения на экран и всего остального, что взаимодействует с пользователем. Конечно если это не самая простая модель с парой кнопок и крутилкой.​
Идей тут можно предложить несколько:
  1. Выкинуть стандартную панель управления нафиг и заколхозить что-то самому. Если на 3d-принтере, то может получиться довольно неплохой результат
  2. Оставить панель управления, подключиться к ней и использовать её. Минус - не универсально и много геморроя.
  3. Оставить панель, но не подключать её, а связь держать с помощью мобильного приложения (связь через bluetooth) или web-интерфейса.
2. Платформа для разработки.​
  • По идее, достаточно обычной Arduino Nano/микроконтроллер ATMega328 + у неё достаточно выводов для всего
  • Если помощнее, то можно взять STM32F401, F411, F103. Стоят как Arduino Mega, но мощнее на порядок. Но это уже без меня, т. к. я и с обычной ардуинкой не справляюсь + нет навыка программирования на C++
  • Если с Wi-Fi, то ESP8266. Минус - мало выводов. Плюс - дешёвая и довольно мощная
  • Модульный проект, состоящий из нескольких отдельных модулей, например ESP8266 как мозг и связь и Arduino Nano/микроконтроллер ATMega328 как расширитель портов и продвинутый ПИД-регулятор. Дополнительные модули типа контроллера мотора (а почему бы и нет, хотя можно и без него), модулей обработки сигнала с датчиков (тахометр и прессостат как минимум) не учитываются
3. Особенности датчиков и конструкции​
Как минимум в СМА используются "особенные" датчики, такие как индукционный тахометр и прессостат. Стоит ли их менять на более простые и распространённые (оптический тахометр и резистивный прессостат).
Если да - упрощается разработка. Если нет - придётся заморочиться, но изменений в конструкции минимум.
Да и термистор может быть "не стандартным", а его коэффициент мне не известен.
4. Особенности работы​
По большей части это касается уровня воды в баке на каждом из режимов, режима работы двигателя и других тонкостей, которые не очевидны, но могут быть ощутимыми в дальнейшем
Вроде бы всё описал:unsure:
 
Изменено:

Arhat109

★★★✩✩✩✩
9 Июн 2019
468
198
@bort707, 20-30 тысяч может кому-то и не дорого, а кто-то столько в месяц получает, а ещё и кушать хочет .. всё относительно.
 

Divin

★★★★✩✩✩
30 Янв 2021
463
207
@Arhat109, Ну это точно, кому война, а кому и спецоперация, но с доходом в 30к электроника как хобби это подрыв бюджета.
 

Boroda22

★✩✩✩✩✩✩
23 Фев 2022
265
41
@Ilya_G, понятно. в электронике зничит шарите, а в остальном не очень? занимаетесь ремонтом бытовухи?
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1

Boroda22

★✩✩✩✩✩✩
23 Фев 2022
265
41
Довольно неплохой заработок можно иметь и работа не пыльная
и вы хотите ставить свое изобретение в стиральные машины? есть уверенность в том, что это прокатит? лично у меня нет, поскольку производители не очень любят юзать универсальные комплектующие и сопутствующее в своих продуктах. а если универсальность не подходит, тогда пилить свой контроллер под каждого производителя?
Смотря что именно вы имели ввиду
я имел ввиду прграммирование МК

может тогда проще будет собрать свою стиралку из бочки?
 

Ilya_G

✩✩✩✩✩✩✩
1 Июн 2022
0
1
я имел ввиду прграммирование МК
Ну, этим я тоже занимаюсь, только в меньшей степени. Увы или к счастью (каждый сам выбирает), без программирования МК сейчас электронщику уже не обойтись. Конечно, я не Nich1con который библиотеки пишет, но немного в этом разбираюсь.
и вы хотите ставить свое изобретение в стиральные машины?
Если не сделаю это я - сделает дядя Вася из другого города. К тому же, если этот проект наберёт популярность, то он уже без моего прямого участия обрастёт новыми функциями и улучшится его совместимость
поскольку производители не очень любят юзать универсальные комплектующие и сопутствующее в своих продуктах.
Никто не мешает мне их поставить. А коллекторный мотор или клапан останется им же хоть в Африке, хоть на Марсе.
 

Gex7772

✩✩✩✩✩✩✩
21 Май 2019
22
2
Я тут ещё на просторах паутины отыскал годный проект по этой теме, там у автора и холодильника проект есть.

 
Изменено:

ukrainian

✩✩✩✩✩✩✩
17 Авг 2022
0
1
Нашел "секретную" программу-алгоритм от какой-то стиральной машинкипрограммы работы машинки.png
 
  • Лойс +1
Реакции: Arhat109

Proton

✩✩✩✩✩✩✩
5 Окт 2021
0
0
Приветствую форумчане. Тема изготовления программного устройства к стиральной машине на платформе Arduino интересна. В наличии имеется промышленная ст. м. КП-122-4. Командоаппарат работает с перфокартой. Подумываю как заменить только функцию перфокарты - микроконтроллером.
Пока что только идея.
 

Ярослав93

✩✩✩✩✩✩✩
24 Апр 2025
0
1
Все ж элементарно😂😂😂!
Берем esp32, ее и к сети подключить можно, и пишем примерно такой код:

C++:
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>

// Настройки WiFi
const char* ssid = "мой_сеть_название";
const char* password = "мой_пароль_ниркому_не_расскажу_такой_я_жадина";

// Пины управления
const int waterValvePin = 2;
const int drainValvePin = 4;
const int motorPin = 5;
const int heaterPin = 18;
const int waterLevelSensorPin = 34;

// Состояния стиральной машины
enum WashState {
  IDLE,
  FILLING_WATER,
  HEATING,
  WASHING,
  DRAINING,
  RINSING,
  SPINNING,
  COMPLETED,
  ABORTED
};

// Режимы стирки
enum WashMode {
  MANUAL,
  QUICK,
  NORMAL
};

struct MachineState {
  WashState state;
  WashMode mode;
  bool waterValve;
  bool drainValve;
  bool motor;
  bool heater;
  int waterLevel;
  unsigned long stateStartTime;
  unsigned long stateDuration;
};

MachineState machine = {
  .state = IDLE,
  .mode = MANUAL,
  .waterValve = false,
  .drainValve = false,
  .motor = false,
  .heater = false,
  .waterLevel = 0,
  .stateStartTime = 0,
  .stateDuration = 0
};

WebServer server(80);

void setup() {
  Serial.begin(115200);
 
  // Настройка пинов
  pinMode(waterValvePin, OUTPUT);
  pinMode(drainValvePin, OUTPUT);
  pinMode(motorPin, OUTPUT);
  pinMode(heaterPin, OUTPUT);
  pinMode(waterLevelSensorPin, INPUT);
 
  digitalWrite(waterValvePin, LOW);
  digitalWrite(drainValvePin, LOW);
  digitalWrite(motorPin, LOW);
  digitalWrite(heaterPin, LOW);
 
  // Подключение к WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
 
  // Маршруты веб-сервера
  server.on("/", handleRoot);
  server.on("/control", handleControl);
  server.on("/status", handleStatus);
  server.on("/start_wash", handleStartWash);
  server.on("/abort_wash", handleAbortWash);
 
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  server.handleClient();
  updateSensors();
  updateStateMachine();
  delay(100);
}

void updateSensors() {
  machine.waterLevel = analogRead(waterLevelSensorPin);
}

void updateStateMachine() {
  unsigned long currentTime = millis();
 
  if (machine.state == IDLE || machine.state == COMPLETED || machine.state == ABORTED) {
    return;
  }
 
  if (currentTime - machine.stateStartTime >= machine.stateDuration) {
    switch (machine.state) {
      case FILLING_WATER:
        if (machine.mode == QUICK) transitionState(WASHING);
        else if (machine.mode == NORMAL) transitionState(HEATING);
        break;
      case HEATING:
        transitionState(WASHING);
        break;
      case WASHING:
        transitionState(DRAINING);
        break;
      case DRAINING:
        if (machine.mode == QUICK) transitionState(RINSING);
        else transitionState(COMPLETED);
        break;
      case RINSING:
        transitionState(SPINNING);
        break;
      case SPINNING:
        transitionState(COMPLETED);
        break;
      default:
        break;
    }
  }
}

void transitionState(WashState newState) {
  // Выход из предыдущего состояния
  switch (machine.state) {
    case FILLING_WATER:
      digitalWrite(waterValvePin, LOW);
      machine.waterValve = false;
      break;
    case HEATING:
      digitalWrite(heaterPin, LOW);
      machine.heater = false;
      break;
    case WASHING:
      digitalWrite(motorPin, LOW);
      machine.motor = false;
      break;
    case DRAINING:
      digitalWrite(drainValvePin, LOW);
      machine.drainValve = false;
      break;
    case RINSING:
      digitalWrite(waterValvePin, LOW);
      machine.waterValve = false;
      break;
    case SPINNING:
      digitalWrite(motorPin, LOW);
      machine.motor = false;
      break;
    case ABORTED:
      digitalWrite(waterValvePin, LOW);
      digitalWrite(drainValvePin, LOW);
      digitalWrite(motorPin, LOW);
      digitalWrite(heaterPin, LOW);
      machine.waterValve = false;
      machine.drainValve = false;
      machine.motor = false;
      machine.heater = false;
      break;
    default:
      break;
  }
 
  // Вход в новое состояние
  machine.state = newState;
  machine.stateStartTime = millis();
 
  switch (newState) {
    case IDLE:
      machine.mode = MANUAL;
      machine.stateDuration = 0;
      break;
      
    case FILLING_WATER:
      digitalWrite(waterValvePin, HIGH);
      machine.waterValve = true;
      machine.stateDuration = (machine.mode == QUICK) ? 5000 : 10000;
      break;
      
    case HEATING:
      digitalWrite(heaterPin, HIGH);
      machine.heater = true;
      machine.stateDuration = 15000;
      break;
      
    case WASHING:
      digitalWrite(motorPin, HIGH);
      machine.motor = true;
      machine.stateDuration = (machine.mode == QUICK) ? 10000 : 20000;
      break;
      
    case DRAINING:
      digitalWrite(drainValvePin, HIGH);
      machine.drainValve = true;
      machine.stateDuration = (machine.mode == QUICK) ? 3000 : 5000;
      break;
      
    case RINSING:
      digitalWrite(waterValvePin, HIGH);
      machine.waterValve = true;
      machine.stateDuration = 3000;
      break;
      
    case SPINNING:
      digitalWrite(motorPin, HIGH);
      machine.motor = true;
      machine.stateDuration = 5000;
      break;
      
    case COMPLETED:
      machine.stateDuration = 0;
      machine.mode = MANUAL;
      break;
      
    case ABORTED:
      machine.stateDuration = 0;
      break;
  }
}

void handleRoot() {
  String html = R"=====(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Управление стиральной машинкой</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
    .container { max-width: 800px; margin: 0 auto; }
    .panel { background: #f5f5f5; padding: 15px; margin-bottom: 20px; border-radius: 5px; }
    h1 { color: #333; text-align: center; }
    .btn {
      background-color: #4CAF50;
      border: none;
      color: white;
      padding: 10px 15px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      margin: 4px 2px;
      cursor: pointer;
      border-radius: 5px;
    }
    .btn-off { background-color: #f44336; }
    .btn-wash { background-color: #2196F3; }
    .btn-abort { background-color: #ff9800; }
    .status { font-weight: bold; }
    .sensor-value { font-size: 18px; color: #2196F3; }
    .wash-status { font-size: 18px; color: #4CAF50; margin-top: 10px; }
    .state-display { font-size: 20px; font-weight: bold; margin: 10px 0; }
    select { padding: 8px; border-radius: 4px; border: 1px solid #ddd; }
    .control-group { margin-bottom: 15px; }
  </style>
  <script>
    let currentMode = 'QUICK';
    
    function updateStatus() {
      fetch('/status')
        .then(response => response.json())
        .then(data => {
          // Обновление кнопок ручного управления
          document.getElementById('waterValveBtn').textContent = 'Клапан воды: ' + (data.waterValve ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('waterValveBtn').className = data.waterValve ? 'btn' : 'btn btn-off';
          document.getElementById('waterValveBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          document.getElementById('drainValveBtn').textContent = 'Клапан слива: ' + (data.drainValve ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('drainValveBtn').className = data.drainValve ? 'btn' : 'btn btn-off';
          document.getElementById('drainValveBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          document.getElementById('motorBtn').textContent = 'Мотор: ' + (data.motor ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('motorBtn').className = data.motor ? 'btn' : 'btn btn-off';
          document.getElementById('motorBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          document.getElementById('heaterBtn').textContent = 'Нагреватель: ' + (data.heater ? 'ВКЛ' : 'ВЫКЛ');
          document.getElementById('heaterBtn').className = data.heater ? 'btn' : 'btn btn-off';
          document.getElementById('heaterBtn').disabled = data.state !== 'IDLE' && data.state !== 'COMPLETED' && data.state !== 'ABORTED';
          
          // Обновление показаний датчиков
          document.getElementById('waterLevelValue').textContent = data.waterLevel;
          
          // Обновление статуса
          const stateNames = {
            'IDLE': 'Готов к работе',
            'FILLING_WATER': 'Наполнение водой',
            'HEATING': 'Нагрев воды',
            'WASHING': 'Стирка',
            'DRAINING': 'Слив воды',
            'RINSING': 'Полоскание',
            'SPINNING': 'Отжим',
            'COMPLETED': 'Цикл завершен',
            'ABORTED': 'Цикл прерван'
          };
          
          document.getElementById('currentState').textContent = stateNames[data.state] || data.state;
          
          // Обновление кнопки старта/остановки
          const startBtn = document.getElementById('startAbortBtn');
          if (data.state === 'IDLE' || data.state === 'COMPLETED' || data.state === 'ABORTED') {
            startBtn.textContent = 'Запустить (' + document.getElementById('washMode').value + ')';
            startBtn.className = 'btn btn-wash';
            document.getElementById('washMode').disabled = false;
          } else {
            startBtn.textContent = 'Прервать (' + data.mode + ')';
            startBtn.className = 'btn btn-abort';
            document.getElementById('washMode').disabled = true;
          }
          
          // Прогресс выполнения
          if (data.stateDuration > 0) {
            const elapsed = Date.now() - data.stateStartTime;
            const progress = Math.min(100, (elapsed / data.stateDuration) * 100);
            document.getElementById('progressBar').style.width = progress + '%';
            document.getElementById('progressContainer').style.display = 'block';
          } else {
            document.getElementById('progressContainer').style.display = 'none';
          }
        });
    }
    
    function toggleControl(element) {
      const control = element.id.replace('Btn', '');
      fetch('/control?cmd=' + control)
        .then(() => updateStatus());
    }
    
    function handleStartAbort() {
      const startBtn = document.getElementById('startAbortBtn');
      
      if (startBtn.className.includes('btn-wash')) {
        // Запуск стирки
        const mode = document.getElementById('washMode').value;
        fetch('/start_wash?mode=' + mode)
          .then(() => updateStatus());
      } else {
        // Прерывание стирки
        fetch('/abort_wash')
          .then(() => updateStatus());
      }
    }
    
    function updateButtonText() {
      const startBtn = document.getElementById('startAbortBtn');
      if (startBtn.className.includes('btn-wash')) {
        startBtn.textContent = 'Запустить (' + document.getElementById('washMode').value + ')';
      }
    }
    
    // Обновляем статус каждые 2 секунды
    setInterval(updateStatus, 2000);
    // Инициализация при загрузке
    window.onload = updateStatus;
  </script>
</head>
<body>
  <div class="container">
    <h1>Управление стиральной машинкой</h1>
    
    <div class="panel">
      <h2>Текущее состояние</h2>
      <p>Состояние: <span id="currentState" class="state-display">Готов к работе</span></p>
      <div id="progressContainer" style="display:none; width:100%; background-color:#ddd; border-radius:5px;">
        <div id="progressBar" style="height:20px; width:0%; background-color:#4CAF50; border-radius:5px;"></div>
      </div>
    </div>
    
    <div class="panel">
      <h2>Ручное управление</h2>
      <button id="waterValveBtn" class="btn" onclick="toggleControl(this)">Клапан воды: ВЫКЛ</button>
      <button id="drainValveBtn" class="btn" onclick="toggleControl(this)">Клапан слива: ВЫКЛ</button>
      <button id="motorBtn" class="btn" onclick="toggleControl(this)">Мотор: ВЫКЛ</button>
      <button id="heaterBtn" class="btn" onclick="toggleControl(this)">Нагреватель: ВЫКЛ</button>
    </div>
    
    <div class="panel">
      <h2>Датчики</h2>
      <p>Уровень воды: <span id="waterLevelValue" class="sensor-value">0</span></p>
    </div>
    
    <div class="panel">
      <h2>Управление стиркой</h2>
      <div class="control-group">
        <select id="washMode" onchange="updateButtonText()">
          <option value="QUICK">Быстрая стирка</option>
          <option value="NORMAL">Обычная стирка</option>
        </select>
      </div>
      <button id="startAbortBtn" class="btn btn-wash" onclick="handleStartAbort()">Запустить (Быстрая стирка)</button>
    </div>
  </div>
</body>
</html>
)=====";
 
  server.send(200, "text/html", html);
}

void handleControl() {
  if (machine.state != IDLE && machine.state != COMPLETED && machine.state != ABORTED) {
    server.send(403, "text/plain", "Ручное управление недоступно во время стирки");
    return;
  }
 
  String cmd = server.arg("cmd");
 
  if (cmd == "waterValve") {
    machine.waterValve = !machine.waterValve;
    digitalWrite(waterValvePin, machine.waterValve ? HIGH : LOW);
  } else if (cmd == "drainValve") {
    machine.drainValve = !machine.drainValve;
    digitalWrite(drainValvePin, machine.drainValve ? HIGH : LOW);
  } else if (cmd == "motor") {
    machine.motor = !machine.motor;
    digitalWrite(motorPin, machine.motor ? HIGH : LOW);
  } else if (cmd == "heater") {
    machine.heater = !machine.heater;
    digitalWrite(heaterPin, machine.heater ? HIGH : LOW);
  }
 
  server.send(200, "text/plain", "OK");
}

void handleStatus() {
  StaticJsonDocument<512> doc;
 
  doc["state"] = String(getStateName(machine.state));
  doc["mode"] = String(getModeName(machine.mode));
  doc["waterValve"] = machine.waterValve;
  doc["drainValve"] = machine.drainValve;
  doc["motor"] = machine.motor;
  doc["heater"] = machine.heater;
  doc["waterLevel"] = machine.waterLevel;
  doc["stateStartTime"] = machine.stateStartTime;
  doc["stateDuration"] = machine.stateDuration;
 
  String json;
  serializeJson(doc, json);
  server.send(200, "application/json", json);
}

const char* getStateName(WashState state) {
  switch(state) {
    case IDLE: return "IDLE";
    case FILLING_WATER: return "FILLING_WATER";
    case HEATING: return "HEATING";
    case WASHING: return "WASHING";
    case DRAINING: return "DRAINING";
    case RINSING: return "RINSING";
    case SPINNING: return "SPINNING";
    case COMPLETED: return "COMPLETED";
    case ABORTED: return "ABORTED";
    default: return "UNKNOWN";
  }
}

const char* getModeName(WashMode mode) {
  switch(mode) {
    case MANUAL: return "MANUAL";
    case QUICK: return "QUICK";
    case NORMAL: return "NORMAL";
    default: return "UNKNOWN";
  }
}

void handleStartWash() {
  if (machine.state != IDLE && machine.state != COMPLETED && machine.state != ABORTED) {
    server.send(403, "text/plain", "Стирка уже выполняется");
    return;
  }
 
  String mode = server.arg("mode");
 
  if (mode == "QUICK") {
    machine.mode = QUICK;
    transitionState(FILLING_WATER);
    server.send(200, "text/plain", "Started quick wash");
  } else if (mode == "NORMAL") {
    machine.mode = NORMAL;
    transitionState(FILLING_WATER);
    server.send(200, "text/plain", "Started normal wash");
  } else {
    server.send(400, "text/plain", "Invalid mode");
  }
}

void handleAbortWash() {
  if (machine.state == IDLE || machine.state == COMPLETED || machine.state == ABORTED) {
    server.send(400, "text/plain", "Нет активного процесса стирки");
    return;
  }
 
  transitionState(ABORTED);
  server.send(200, "text/plain", "Стирка прервана");
}
 
  • Лойс +1
Реакции: SlavaZagaynov

crazylord

✩✩✩✩✩✩✩
13 Дек 2019
0
0
А если серьезно, есть у кого для esp32 скетч для управления коллекторным двигателем ?
 

selevo

✩✩✩✩✩✩✩
25 Апр 2020
25
4
думаю над идеей 10лет )))
Самое просто е это написать программы.
У меня стиралка без мозгов без датчиков и стирает как и многие на комадоаппаратах и ничего в разнос не летит кроме контактов в командомаппарате.

Проблему вижу в универсальном управлении.
Хочу сделать управление с любого старого смартфона, приделать его к стиралка в качестве дисплея и управления с бесконечными возможностями.

Использовать для связи с мозгами (на arduino uno) машины, аудиоканал смартфона и обычный HTML файл для меню и настроек.
Причем всю логику можно хранить в HTML файле, т.е web страничке.
Это сведет минимум наклонов и движений по программирования контроллера.
А аппаратная начинка будет лишь по минимуму исполнять комманды и самостоятельно реагировать на процессы требующие быстрой реакции.

Недавно я сделал первый шаг к этому, написал UART SHELL который тут выложил.
Сейчас я лениво занимаюсь соединением смартфон - аудиокабель- микроконтроллер.
Есть уже готовый проект webjack firmata, там используются FSK тон-посылки.
И я изначально хотел так и сделать ибо всё выложено в сеть для arduino но у меня появилась ещё более интересная идея, просто имитировать UART аудиопотоком, безо всякой модуляции, такой проект тоже есть, используется в espruino, там они по аудиокабелю из браузера прошивают и считывают ESP шку.
Я поковырялся в коде , выпилил нужное но пока еще не обкатал с железом.
 
Изменено:

selevo

✩✩✩✩✩✩✩
25 Апр 2020
25
4
@crazylord,
попробуй потестить.
C++:
#include <EEPROM.h>

// --- Конфигурация пинов ---
const int PIN_PWM1 = 9;   
const int PIN_PWM2 = 10;  
const int PIN_ENC_A = 2;  
const int PIN_ENC_B = 3;  
const int PIN_LED = 13;   // Встроенный светодиод для индикации вращения

const int LAST_PROFILE_ADDR = 1023; 

struct Settings {
  int pulsesPerRev;
  int sensorType;
  int acel;
  int stspd;
  int autosave; 
  int plotEnabled; 
  float Kp, Ki, Kd;
} config;

// Глобальные переменные состояний
int targetRPM = 0;
float currentTarget = 0; 
bool isRunning = false;
bool isStopping = false; 
int direction = 0;
int swingTime = 0;
int currentProfile = 1; 

volatile long pulseCount = 0;
volatile bool ledState = false;
long lastUpdate = 0;
float currentRPM = 0;
float smoothedPWM = 0;
unsigned long lastSwingMillis = 0;
bool swingDir = false;

void setup() {
  Serial.begin(9600); 
 
  // Загрузка последнего использованного профиля
  currentProfile = EEPROM.read(LAST_PROFILE_ADDR);
  if (currentProfile < 1 || currentProfile > 15) currentProfile = 1;

  loadProfile(currentProfile);

  pinMode(PIN_PWM1, OUTPUT);
  pinMode(PIN_PWM2, OUTPUT);
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_ENC_A, INPUT_PULLUP);
  pinMode(PIN_ENC_B, INPUT_PULLUP);

  // Прерывание для энкодера
  attachInterrupt(digitalPinToInterrupt(PIN_ENC_A), encoderISR, RISING);
  
  Serial.println(F("\n========================================"));
  Serial.println(F("СИСТЕМА УПРАВЛЕНИЯ ДВИГАТЕЛЕМ ЗАПУЩЕНА"));
  Serial.print(F("Активный профиль: ")); Serial.println(currentProfile);
  Serial.println(F("----------------------------------------"));
  Serial.println(F("Введите: ? для ПОЛНОГО списка команд"));
  Serial.println(F("========================================\n"));
}

void loop() {
  handleSerial();
  calculateRPM();
  updatePID();
  handleSwing();
  // Вывод графиков, если разрешено и мотор в движении
  if (config.plotEnabled && (isRunning || isStopping)) plotData();
}

// Обработка прерывания энкодера
void encoderISR() {
  if (config.sensorType == 1) (digitalRead(PIN_ENC_B) == HIGH) ? pulseCount++ : pulseCount--;
  else pulseCount++;

  // Мигание светодиодом 1 раз за пол-оборота
  int blinkDivider = (config.pulsesPerRev > 4) ? (config.pulsesPerRev / 2) : 1;
  if (abs(pulseCount) % blinkDivider == 0) {
    ledState = !ledState;
    digitalWrite(PIN_LED, ledState);
  }
}

// Расчет реальной скорости (RPM)
void calculateRPM() {
  unsigned long now = millis();
  if (now - lastUpdate >= 100) {
    noInterrupts();
    static long lastCount = 0;
    long diff = pulseCount - lastCount;
    lastCount = pulseCount;
    interrupts();
    currentRPM = (config.pulsesPerRev > 0) ? (diff * 600.0) / config.pulsesPerRev : 0;
    lastUpdate = now;
  }
}

// Основной алгоритм ПИД и разгона/торможения
void updatePID() {
  if (!isRunning && !isStopping) {
    smoothedPWM = 0;
    stopMotor();
    digitalWrite(PIN_LED, LOW);
    return;
  }

  // Расчет шага изменения скорости (рампа)
  float step = (config.acel > 0) ? (config.acel / 2.0) : 500.0; 
  
  if (isStopping) {
    if (config.stspd > 0) {
      float stopStep = config.stspd / 2.0;
      currentTarget -= stopStep;
      if (currentTarget <= 0) { currentTarget = 0; isStopping = false; }
    } else { currentTarget = 0; isStopping = false; }
  } else {
    if (currentTarget < targetRPM) currentTarget = min(currentTarget + step, (float)targetRPM);
    else if (currentTarget > targetRPM) currentTarget = max(currentTarget - step, (float)targetRPM);
  }

  // ПИД расчет
  static float integral = 0;
  static float lastError = 0;
  float error = currentTarget - abs(currentRPM);
  
  if (abs(smoothedPWM) < 255 || (error < 0 && integral > 0) || (error > 0 && integral < 0)) integral += error * 0.1;
  integral = constrain(integral, -150, 150); 
  
  float derivative = (error - lastError) / 0.1;
  float rawOutput = (config.Kp * error) + (config.Ki * integral) + (config.Kd * derivative);
  smoothedPWM = constrain(rawOutput, 0, 255);

  applyMotorPower((int)smoothedPWM, direction ^ swingDir);
  lastError = error;
}

void plotData() {
  Serial.print(targetRPM); Serial.print(" ");
  Serial.print(currentTarget); Serial.print(" ");
  Serial.print(currentRPM); Serial.print(" ");
  Serial.println(smoothedPWM / 5.0); 
}

void applyMotorPower(int pwm, int dir) {
  if (dir == 0) { analogWrite(PIN_PWM1, pwm); analogWrite(PIN_PWM2, 0); }
  else { analogWrite(PIN_PWM1, 0); analogWrite(PIN_PWM2, pwm); }
}

void stopMotor() { analogWrite(PIN_PWM1, 0); analogWrite(PIN_PWM2, 0); }

void handleSwing() {
  if (swingTime > 0 && isRunning) {
    if (millis() - lastSwingMillis >= (unsigned long)swingTime) {
      swingDir = !swingDir;
      lastSwingMillis = millis();
    }
  } else swingDir = false;
}

void checkAutosave() {
  if (config.autosave == 1) {
    int addr = (currentProfile - 1) * sizeof(Settings);
    EEPROM.put(addr, config);
  }
}

void resetAllProfiles() {
  Serial.println(F("Сброс всех 15 профилей..."));
  config = {20, 0, 20, 20, 1, 1, 0.5, 0.1, 0.05}; 
  for (int i = 1; i <= 15; i++) {
    int addr = (i - 1) * sizeof(Settings);
    EEPROM.put(addr, config);
    Serial.print(F("."));
  }
  EEPROM.update(LAST_PROFILE_ADDR, 1);
  currentProfile = 1;
  Serial.println(F("\nГотово. Активен профиль №1."));
}

void saveProfile(int slot) {
  if (slot < 1 || slot > 15) return;
  int addr = (slot - 1) * sizeof(Settings);
  EEPROM.put(addr, config);
  EEPROM.update(LAST_PROFILE_ADDR, slot); 
  currentProfile = slot;
  Serial.print(F("Профиль ")); Serial.print(slot); Serial.println(F(" СОХРАНЕН"));
}

void loadProfile(int slot) {
  if (slot < 1 || slot > 15) return;
  int addr = (slot - 1) * sizeof(Settings);
  EEPROM.get(addr, config);
  // Защита от мусора
  if (config.pulsesPerRev <= 0 || config.pulsesPerRev > 5000) {
    config = {20, 0, 20, 20, 1, 1, 0.5, 0.1, 0.05}; 
  }
  config.acel = constrain(config.acel, 0, 255);
  config.stspd = constrain(config.stspd, 0, 255);
  
  EEPROM.update(LAST_PROFILE_ADDR, slot);
  currentProfile = slot;
  Serial.print(F("Профиль ")); Serial.print(slot); Serial.println(F(" ЗАГРУЖЕН"));
}

void handleSerial() {
  if (Serial.available() > 0) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();

    if (cmd == "?") {
      Serial.println(F("\n======= ПОЛНОЕ ОПИСАНИЕ КОМАНД ======="));
      Serial.println(F("1. УПРАВЛЕНИЕ:"));
      Serial.println(F("  st / stop    - Пуск / Остановка (плавная)"));
      Serial.println(F("  sp [0-10000] - Установить обороты"));
      Serial.println(F("  sp? / rsp?   - Узнать цель / Узнать факт RPM"));
      Serial.println(F("  set?         - Все настройки текущего профиля"));
      Serial.println(F(""));
      Serial.println(F("2. ПАМЯТЬ:"));
      Serial.println(F("  load / save [1-15] - Загрузить/Сохранить профиль"));
      Serial.println(F("  autosave [0/1]     - Автосохранение (0-выкл, 1-вкл)"));
      Serial.println(F("  reset_all          - Сброс всех 15 профилей"));
      Serial.println(F(""));
      Serial.println(F("3. МЕХАНИКА:"));
      Serial.println(F("  acel  [0-255]   - Ускорение (больше = быстрее)"));
      Serial.println(F("  stspd [0-255]   - Торможение (0-выбег, 255-резко)"));
      Serial.println(F("  swi   [0-30000] - Качание (мс, 0-выкл)"));
      Serial.println(F("  tic   [1-5000]  - Импульсов на 1 оборот"));
      Serial.println(F("  sens  [0/1]     - Датчик (0-простой, 1-квадрат)"));
      Serial.println(F("  r     [0/1]     - Направление (0-прямо, 1-реверс)"));
      Serial.println(F("  plot  [0/1]     - Графики (0-выкл, 1-вкл)"));
      Serial.println(F(""));
      Serial.println(F("4. ПИД (дробные):"));
    Serial.println(F("Kp [0.0-100.0] - П-коэффициент (Жесткость)"));
      Serial.println(F("Ki [0.0-100.0] - И-коэффициент (Точность)"));
      Serial.println(F("Kd [0.0-100.0] - Д-коэффициент (Затухание)"));
    //  Serial.println(F("  Kp / Ki / Kd [0.0 - 100.0] - Коэффициенты"));
      Serial.println(F("======================================\n"));
    }
    else if (cmd == "reset_all") resetAllProfiles();
    else if (cmd == "set?") {
      Serial.println(F("\n====== ТЕКУЩИЙ ПРОФИЛЬ ======"));
      Serial.print(F("Номер профиля: ")); Serial.println(currentProfile);
      Serial.print(F("Автосохранение: ")); Serial.println(config.autosave ? F("ВКЛ") : F("ВЫКЛ"));
      Serial.print(F("Графики (plot): ")); Serial.println(config.plotEnabled ? F("ВКЛ") : F("ВЫКЛ"));
      Serial.print(F("Разгон (acel): ")); Serial.println(config.acel);
      Serial.print(F("Тормоз (stspd): ")); Serial.println(config.stspd);
      Serial.print(F("Импульсов (tic): ")); Serial.println(config.pulsesPerRev);
      Serial.print(F("Качание (swi): ")); Serial.print(swingTime); Serial.println(F(" мс"));
      Serial.print(F("Направление (r): ")); Serial.println(direction == 0 ? F("Прямое") : F("Реверс"));
      Serial.print(F("PID (P/I/D): ")); Serial.print(config.Kp); Serial.print("/"); Serial.print(config.Ki); Serial.print("/"); Serial.println(config.Kd);
      Serial.println(F("==============================\n"));
    }
    else if (cmd == "st") { isRunning = true; isStopping = false; Serial.println(F("СТАРТ")); }
    else if (cmd == "stop") { 
      isRunning = false; 
      if (config.stspd > 0) isStopping = true; else { isStopping = false; stopMotor(); }
      Serial.println(F("ОСТАНОВКА..."));
    }
    else if (cmd.startsWith("plot ")) { config.plotEnabled = cmd.substring(5).toInt(); checkAutosave(); }
    else if (cmd.startsWith("autosave ")) { config.autosave = cmd.substring(9).toInt(); checkAutosave(); }
    else if (cmd.startsWith("save ")) saveProfile(cmd.substring(5).toInt());
    else if (cmd.startsWith("load ")) loadProfile(cmd.substring(5).toInt());
    else if (cmd == "sp?") { Serial.print(F("Цель: ")); Serial.println(targetRPM); }
    else if (cmd == "rsp?") { Serial.print(F("Реально: ")); Serial.println(currentRPM); }
    else if (cmd.startsWith("sp ")) { targetRPM = cmd.substring(3).toInt(); checkAutosave(); }
    else if (cmd.startsWith("tic ")) { config.pulsesPerRev = constrain(cmd.substring(4).toInt(), 1, 5000); checkAutosave(); }
    else if (cmd.startsWith("sens ")) { config.sensorType = cmd.substring(5).toInt(); checkAutosave(); }
    else if (cmd.startsWith("acel ")) { config.acel = constrain(cmd.substring(5).toInt(), 0, 255); checkAutosave(); }
    else if (cmd.startsWith("stspd ")) { config.stspd = constrain(cmd.substring(6).toInt(), 0, 255); checkAutosave(); }
    else if (cmd.startsWith("Kp ")) { config.Kp = cmd.substring(3).toFloat(); checkAutosave(); }
    else if (cmd.startsWith("Ki ")) { config.Ki = cmd.substring(3).toFloat(); checkAutosave(); }
    else if (cmd.startsWith("Kd ")) { config.Kd = cmd.substring(3).toFloat(); checkAutosave(); }
    else if (cmd.startsWith("swi ")) { swingTime = cmd.substring(4).toInt(); checkAutosave(); }
    else if (cmd.startsWith("r ")) { direction = cmd.substring(2).toInt(); checkAutosave(); }
  }
}
управляемый по UART ШИМ регулятор скорости коллекторного двигателя с обратной связью с энкодером или таходатчиком.

Профиль 1 ЗАГРУЖЕН

========================================
СИСТЕМА УПРАВЛЕНИЯ ДВИГАТЕЛЕМ ЗАПУЩЕНА
Активный профиль: 1
----------------------------------------
Введите: ? для ПОЛНОГО списка команд
========================================


======= ПОЛНОЕ ОПИСАНИЕ КОМАНД =======
1. УПРАВЛЕНИЕ:
st / stop - Пуск / Остановка (плавная)
sp [0-10000] - Установить обороты
sp? / rsp? - Узнать цель / Узнать факт RPM
set? - Все настройки текущего профиля

2. ПАМЯТЬ:
load / save [1-15] - Загрузить/Сохранить профиль
autosave [0/1] - Автосохранение (0-выкл, 1-вкл)
reset_all - Сброс всех 15 профилей

3. МЕХАНИКА:
acel [0-255] - Ускорение (больше = быстрее)
stspd [0-255] - Торможение (0-выбег, 255-резко)
swi [0-30000] - Качание (мс, 0-выкл)
tic [1-5000] - Импульсов на 1 оборот
sens [0/1] - Датчик (0-простой, 1-квадрат)
r [0/1] - Направление (0-прямо, 1-реверс)
plot [0/1] - Графики (0-выкл, 1-вкл)

4. ПИД (дробные):
Kp [0.0-100.0] - П-коэффициент (Жесткость)
Ki [0.0-100.0] - И-коэффициент (Точность)
Kd [0.0-100.0] - Д-коэффициент (Затухание)
======================================
 
Изменено: