Рулонная штора на шнуре — почти идеальный объект для серводвигателя: ход короткий, нагрузка лёгкая, крутить надо один барабан. Я начал с идеи «повернул рог — штора едет» и за вечер узнал, что без автомата состояний пульт превращает штору в дёрганую заглушку: кнопка жмётся второй раз, ход стартует заново, удержание гонит серво девятью повторами в секунду. Красивый проект на фото, кошмар в жизни.
Механика простая: серводвигатель SG90 или MG996R крутит барабан, шнур шторы наматывается на него — полный ход укладывается в поворот на 150–170 градусов. Для лёгкой рулонной шторы хватает SG90 с плечом в несколько сантиметров; тяжёлые портьеры серводвигатель не потянет — там редуктор и шаговик, у серводвигателя упрёшься в ток. Подбор двигателя и ток удержания я разбирал в уроке про ток и нагрев серводвигателя.
О железе подробнее, чтобы ты не повторял мою первую кривую версию. Барабан я сделал из катушки от ниток, посадил прямо на качалку серводвигателя — шнур шторы обходит его полтора витка и держится трением. Полный ход шторы лёг в 160 градусов поворота; если твоя штора длиннее, подбирай диаметр барабана, а не увеличивай угол: рог за 180 градусов не ходит, и попытка намотать больше даст мёртвую точку в крайнем положении, когда шнур сваливается с барабана. Крепление качалки — на шлицах с винтом, не на трении: мой первый рог выскользнул из кулисы ровно посреди первого хода.
План урока: автомат из состояний open, closed, opening и closing, кнопка OK ставит штору на паузу посреди хода и отпускает её дальше, ход идёт по градусу на millis без единого delay, а крайние положения защищены ограничениями по углу — серводвигатель не бьётся в упор и не греется.
Автомат состояний: почему не два флага
Первая версия у меня была на булевых флагах isMoving и direction — и рассыпалась на паузе. Остановка в середине хода создаёт пятое состояние, «остановлена на полпути», которого во флагах нет: серводвигатель стоит, штора висит на угле 96°, и код должен помнить, куда продолжать движение. Проще назвать вещи именами: open, closed, opening, closing, paused — состояние переменной, событие кнопки переводит автомат из состояния в состояние. Переключение режимов кнопками я уже трогал в уроке про меню режимов, здесь механика та же, только состояний меньше.
Кнопки пульта распределил так: CH+ открывает, CH- закрывает, OK — пауза и продолжение. Распределение не случайное: CH+ и CH- интуитивны как «больше/меньше шторы», а OK под рукой — им и остановишь. Повторы при удержании — главная ловушка пультового интерфейса: кадр-повтор приходит каждые 108–110 мс, и без фильтра штора дёргается на каждом кадре. Режется это до автомата — в уроке про фильтр удержания я показывал оба способа, здесь беру debounce по времени.
Ход — часть состояния, а не отдельная функция с delay. Каждые двенадцать миллисекунд угол сдвигается на один градус, и loop всё это время живёт: ловит кнопки, проверяет крайние углы. Темп хода настраивается одной константой STEP_MS — про пошаговое движение серводвигателя на millis у меня есть отдельный урок про шаг по градусу, а почему delay в таком скетче ломает всё — в базовом уроке про millis.
Состояния у меня живут числами с комментарием-легендой: 0 closed, 1 opening, 2 open, 3 closing, 4 paused — enum в C++ тоже сработал бы, но число в печати монитора читается без таблицы. Дисциплина автомата: события кнопок не трогают угол — CH+ в состоянии opening глохнет в пустоту, автомат уже едет. Такая строгость спасает от пультовой болезни, когда каждая кнопка прямо дёргает привод и порядок нажатий рождает состояния, которых ты не планировал.
// Серво на пине 9 (ШИМ), приёмник TSOP на пине 11 — пины не смешиваются.#include <Servo.h>#include <IRremote.hpp> Servo shtora; const int IR_PIN = 11;const int SERVO_PIN = 9;const int ANGLE_CLOSED = 10; // штора собранаconst int ANGLE_OPEN = 170; // штора распахнутаconst unsigned long STEP_MS = 12; // один градус за 12 мс: полный ход за 1.9 с int angle = ANGLE_CLOSED;int state = 0; // 0 closed, 1 opening, 2 open, 3 closing, 4 pausedint lastDir = 1; // куда продолжать после паузыunsigned long tStep = 0; void setup() { Serial.begin(9600); shtora.attach(SERVO_PIN); shtora.write(angle); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); Serial.println("Shtora: closed. CH+ otkryt, CH- zakryt, OK pauza");} void loop() { unsigned long now = millis(); if (IrReceiver.decode()) { int cmd = IrReceiver.decodedIRData.command; if (cmd == 69 && state != 1 && state != 2) { // CH+ state = 1; lastDir = 1; Serial.println("opening: otkryvayu"); } if (cmd == 70 && state != 3 && state != 0) { // CH- state = 3; lastDir = -1; Serial.println("closing: zakryvayu"); } if (cmd == 67) { // OK: пауза и продолжение if (state == 1 || state == 3) { state = 4; Serial.print("paused na ugle "); Serial.println(angle); } else if (state == 4) { state = (lastDir == 1) ? 1 : 3; Serial.println("prodolzhayu"); } } IrReceiver.resume(); } if (now - tStep >= STEP_MS) { tStep = now; if (state == 1 && angle < ANGLE_OPEN) { angle++; shtora.write(angle); if (angle >= ANGLE_OPEN) { state = 2; Serial.println("open: doehali"); } } else if (state == 3 && angle > ANGLE_CLOSED) { angle--; shtora.write(angle); if (angle <= ANGLE_CLOSED) { state = 0; Serial.println("closed: doehali"); } } }}Читаем автомат. События кнопок меняют только состояние — угол их не трогает. Ход делает таймер: раз в двенадцать миллисекунд шаг на градус в сторону, которую диктует состояние, и на крайних углах автомат сам переходит в open или closed. Полный ход 160 градусов занимает около двух секунд — для шторы комфортный темп, быстрее только шумнее; если захочется плавнее, ставь STEP_MS 20 и получишь три секунды с меньшим пусковым током на каждый шаг. Шаг именно в один градус — не эстетика, а защита механики: крупными порциями по пять градусов штора ходит рывками, и каждый рывок бьёт по качалке и шнуру. Мелкий шаг серводвигатель отыгрывает почти непрерывным вращением, а заодно loop успевает ловить кнопки между шагами — пауза срабатывает в тот же кадр, когда нажал.
Крайние положения — 10 и 170, а не 0 и 180. У рога и кулисы есть мёртвые зоны по краям, и серводвигатель, упёршийся в механику, продолжает жрать ток и греться: подробности в уроке по ссылке выше. Концевые защиты здесь программные — по углу; для тяжёлых штор к ним добавляют физические концевики, но для рулонной шторы с барабаном границ по углу хватает.
Питание — отдельная песня. Серводвигатель не кормят от пина 5V платы: при пуске SG90 съедает до 650–700 мА, MG996R до двух с половиной ампер, и просадка роняет плату в перезагрузку — я это проходил в уроке про внешнее питание серводвигателя. Блок на 5–6 вольт с током от двух ампер, земля общая с платой. Конденсатор на 470–1000 мкФ между питанием и землёй у серводвигателя гасит пусковой рывок — дешёвая страховка от самопроизвольных перезапусков в самый неподходящий момент.
Момент проверь до сверления стены. Лёгкая рулонная штора тянет шнур с силой в пару сотен граммов, на барабане радиусом сантиметр это две-три десятых килограммометра — SG90 со своими 1.8 кг·см везёт такую штору с запасом впятеро-вшестеро. MG996R с девятью с половиной нужен, только когда штора тяжёлая или барабан крупный: момент растёт от радиуса, и удвоенный диаметр съедает вдвое больше запаса. Мой первый барабан был диаметром сорок миллиметров — ход вышел короче при том же угле, но SG90 уже кряхтел на старте; вернулся к двадцати и успокоился.
Печатай переходы и угол в Serial хотя бы на время отладки: строка paused na ugle 96 стоит дороже пяти минут с мультиметром. Мой первый запуск не печатал ничего, и я полчаса подозревал пульт, пока не заметил, что автомат честно дошёл до open — а рог просто выскользнул из кулисы. Крепление качалки на шлицах с винтом, а не на трении, с тех пор — железное правило.
Пауза посреди хода и жизнь после неё
Пауза — самая вкусная часть автомата. Штора замирает на угле 96°, состояние paused, и lastDir помнит, куда шла: после продолжения ход возобновится в ту же сторону, а не начнёт жизнь заново. Такой сценарий я пользуюсь ежедневно: открыл штору на треть, чтобы солнце не било в монитор, — один OK посреди хода. Утром схема та же, только направление обратное, и никакие новые ветки кода для этого не нужны.
Но пауза под нагрузкой греет серводвигатель: он продолжает держать угол и жрёт ток все те часы, пока штора висит на полпути. Лечение — detach: после хода или на паузе отключаю серводвигатель, шнур держит трение барабана, а перед следующим шагом attach обратно. Как отучить серводвигатель дрожать и шуметь — в уроке про detach и дрожь. Если барабан без импульсов разматывается под весом, значит механика недоделана — трещотка или фиксатор решают это лучше, чем ток удержания, который греет корпус до неприличных температур.
В симуляторе ниже вся логика видна вживую: серводвигатель ходит по градусу, кнопки пульта открывают, закрывают и ставят на паузу, автомат печатает переходы в монитор. На макетке серводвигатель встаёт на соседний ШИМ-пин, приёмник остаётся на своём.
| Состояние | Событие | Новый статус | Что делает серво |
|---|---|---|---|
| closed (угол 10) | кнопка CH+ | opening | шаг +1° каждые 12 мс до 170, ход около двух секунд |
| opening | кнопка OK | paused | стоп на текущем угле, lastDir помнит направление вверх |
| paused | кнопка OK | opening или closing | продолжает прежнее направление, не возвращаясь к краям |
| opening | угол дошёл до 170 | open | стоит, ход закончен, качалку можно отпустить detach |
Кнопка → 32-битный кадр: AGC, инверсии и repeat видны глазом; включите помехи и поймайте бит, который не прошёл проверку.
Подгружаем интерактив…
- Автомат из состояний open, closed, opening, closing и paused честнее двух флагов: пауза посреди хода — полноценное состояние, а не ошибка
- CH+ открывает, CH- закрывает, OK ставит паузу и отпускает: lastDir помнит направление после паузы
- Ход по градусу на millis: STEP_MS 12 мс даёт полный ход 160° за 1.9 секунды и живой loop без delay
- Крайние углы 10 и 170, а не 0 и 180: упёршийся в механику серводвигатель жрёт ток и греется
- SG90 для лёгкой рулонной шторы, MG996R для тяжёлой; питание — отдельный блок от 2 А с общей землёй
- Повторы удержания 9 кадров в секунду режутся debounce до автомата
- 1. Почему автомат состояний честнее двух флагов isMoving и direction?
- 2. Полный ход 160 градусов, шаг 1° каждые 12 мс. Сколько идёт штора?
- 3. Почему крайние углы 10 и 170, а не 0 и 180?
- 4. OK во время opening ставит паузу. Что делает повторный OK на paused?
- 5. Как питают серводвигатель такой шторы?
Штора слушается пульта, стоит на паузе посреди хода и не жжёт серводвигатель на краях — интерфейс готов. Дальше остаётся связать её с мостом из предыдущего урока: топик в MQTT, автоматизация в Home Assistant по расписанию — и штора открывается сама к будильнику. А если захочется разложить сценарий на больше состояний — утро, вечер, кино, — заходи в урок про меню режимов: структура там та же самая.
Частые вопросы по теме урока
- Почему автомат состояний честнее двух флагов isMoving и direction?
- Остановка на угле 96° требует помнить и то, что стоим, и куда продолжать. В автомате это состояние paused и lastDir, во флагах — комбинаторика, которая растёт с каждым сценарием. Пауза посреди хода создаёт пятое состояние «остановлена на полпути», которого во флагах нет
- Полный ход 160 градусов, шаг 1° каждые 12 мс. Сколько идёт штора?
- 160 шагов по 12 мс — 1920 мс, около двух секунд на полный ход. Темп регулируется одной константой STEP_MS. Около двух секунд
- Почему крайние углы 10 и 170, а не 0 и 180?
- У кулисы и рога есть мёртвые зоны, и серводвигатель в упоре держит максимальный ток без движения. Программные границы по углу — концевые защиты этого скетча. На краях хода серводвигатель упирается в механику, продолжает жрать ток и греется
- OK во время opening ставит паузу. Что делает повторный OK на paused?
- lastDir помнит, куда шёл ход до паузы: продолжение — то же состояние opening или closing, без возврата к краям. Продолжает движение в прежнюю сторону по lastDir
- Как питают серводвигатель такой шторы?
- Пуск SG90 — до 650–700 мА, MG996R — до 2.5 А: просадка от пина платы роняет Uno в перезагрузку ровно в момент старта хода. От отдельного блока 5–6 В с током от двух ампер, земля общая с платой