Первое утро автоматических штор началось с треска. Скетч честно выдал «открыть», мотор пошёл, дошёл до края карниза — и упёрся: счётчик шагов к краю отношения не имеет, а команды «дальше нельзя» в коде не было. Полсекунды редуктор пахал в упор, потом ползунок выскочил из рельсы, и я остался с утренним солнцем на всю стену и со шторой на коленях. С тех пор в любом приводе с конечным ходом первым делом заводится концевик, и шторы здесь образцовый пациент: ход конечный, края хрупкие, а всё происходит по утрам, когда вам ещё не до него.
Второй урок того же утра — рывок на старте. Я дал полный ход сразу на рабочей скорости: штора дёрнулась так, что карниз стукнул о стену, а ползунки перескочили через пару зубьев. Шаговик трогается мгновенно — фазы переключились, ротор встал, и вся инерция висящей шторы легла на пластиковые шестерни. Плавный разгон для карниза — не украшение, а условие долгой жизни механики, и об этом мы тоже поговорим с числами.
Собираем привод целиком: ноль по концевику, программные лимиты хода, расписание по часам DS3231, разгон без рывков и ручной запасной выход. База — знакомый мотор 28BYJ-48 с платой ULN2003, сверху — библиотека AccelStepper вместо Stepper: у неё есть встроенное ускорение, которого стандартной библиотеке не завезли, и для карниза это решающий аргумент.
Ноль по концевику и лимиты хода
Шаговик считает шаги, но не знает, где края, — и после срыва или ручного сдвига его счёт начинает врать, о чём мы говорили в уроке про скорость и позиционирование. Поэтому при подаче питания привод сначала ищет ноль: едет в сторону закрытия мелкими шагами, пока рычажный концевик у края карниза не щёлкнет. Эта позиция становится нулём — setCurrentPosition(0) в AccelStepper, — и вся дальнейшая геометрия отсчитывается от неё. Концевик — обычная кнопка с рычажком на входе с INPUT_PULLUP: разомкнута — HIGH, замкнулась о край — LOW. Стоит копейки, а превращает «примерно» в «точно».
Лимит второго края считаем из геометрии. У меня штора ходит по шнуру, намотанному на шкив диаметром 20 мм: за оборот шкива она проезжает π×20 = 63 мм. Карниз два метра — это 32 оборота, по 2048 шагов на оборот — порядка 65 500 шагов на полный ход. Число становится константой HOD, и каждая команда движения проходит через него как через забор: мотор физически не может запросить позицию за краем, даже если в коде опечатка. Концевик страхует ноль, константа — противоположный край: двух реперов достаточно, чтобы штора жила без присмотра неделями.
Отдельно — про отказ концевика. Если ноль не нашёлся за разумное число шагов, привод обязан остановиться и сказать об этом: искать бесконечно нельзя, мотор иначе досверлит карниз. Мой лимит поиска — полтора хода: если за 98 000 шагов щелчка не было, привод встаёт и печатает строку в Serial. Это тот же скепсис к собственному железу, из-за которого в сетевых скетчах ставят таймауты.
| Привод | Питание | Что тянет | Ловушка |
|---|---|---|---|
| 28BYJ-48 + ULN2003 | 5 В | макет карниза, тюль до 200 г | момент кончается на тяжёлой шторе |
| NEMA17 + A4988 | 12 В | штора до 2-3 кг на шнуре | драйверу нужен радиатор и настройка тока |
| Серво SG90/MG996 | 5 В | только макет: ход 180° | позицию держит, путь считать не умеет |
| DC-мотор + редуктор | 5-12 В | лёгкие шторы, дёшево | нет счёта позиции: концевики на оба края обязательны |
Расписание у меня живёт в часах DS3231: открыто с восьми утра до восьми вечера, остальное время — закрыто. Логика — одно сравнение: now.hour() >= 8 && now.hour() < 20. Подключение модуля, батарейка, которая помнит время без питания, и разбор полей DateTime — в уроке про часы DS3231. Для штор автономность часов критична: после отключения света в квартире скетч обязан знать время с первой секунды, а позицию привода он восстанавливает поиском нуля при старте — концевик и есть тот якорь, который переживает что угодно.
Главное правило расписания — действовать по фронту. Сравнение часа выполняется в loop() десятки раз в секунду, и если на каждый проход дёргать команду движения, команды будут перезаписывать друг друга, а штора — дёргаться. Флаг решает: когда желаемое состояние расходится с фактическим, команда уходит один раз, и до следующей перемены решения привод живёт своей жизнью. Тот же приём я разбирал в свете по расписанию — там вместо привода щёлкает реле, механика фронта та же до последней строки. Кнопка-дублёр у меня тоже есть: ручной режим обязан работать, даже когда часы забыли время.
#include <Wire.h>#include <RTClib.h>#include <AccelStepper.h> const int KONCEVIK = 2; // рычажный концевик у закрытого краяconst long HOD = 65500; // полный ход: 2 м шнура на шкиве d=20 мм AccelStepper shtora(AccelStepper::HALF4WIRE, 8, 10, 9, 11);RTC_DS3231 rtc; int cel = 0; // цель: 0 - закрыть, HOD - открытьint shtoryOtkryty = 0; // фактическое состояние для фронта void setup() { Serial.begin(9600); rtc.begin(); pinMode(KONCEVIK, INPUT_PULLUP); shtora.setMaxSpeed(400); // 400 полушага/с: около 6 RPM shtora.setAcceleration(150); // разгон карниза без рывка homing(); // ноль - до всего остального} void homing() { shtora.setMaxSpeed(200); long shagov = 0; while (digitalRead(KONCEVIK) == HIGH && shagov < 98000) { shtora.moveTo(shtora.currentPosition() - 100); shtora.runToPosition(); // в setup блокировка допустима shagov += 100; } shtora.setCurrentPosition(0); Serial.println("Nol po konceviku");} void loop() { DateTime now = rtc.now(); int nado = 0; if (now.hour() >= 8 && now.hour() < 20) { nado = HOD; } if (nado != shtoryOtkryty) { // фронт: команда уходит один раз shtoryOtkryty = nado; cel = nado; shtora.moveTo(cel); Serial.print("poehali: cel="); Serial.println(cel); } shtora.run(); // неблокирующий вызов в каждом loop}Разгон, питание и ручной запасной выход
За рывок у AccelStepper отвечает setAcceleration: после moveTo привод сам разгоняется и сам тормозит у цели, а loop() обязан вызывать run() в каждом проходе — неблокирующая схема на millis здесь не опция, а требование библиотеки: run() отрабатывает один микрошаг и отдаёт управление. Числа с условиями: setMaxSpeed(400) — это 400 полушагов в секунду, а HALF4WIRE даёт 4096 полушагов на оборот, то есть около 5,9 RPM — медленно, зато с запасом по моменту; setAcceleration(150) выводит привод на рабочую скорость за две-три секунды, и карниз трогается без стука. Момент и редуктор этого мотора я раскладывал в анатомии 28BYJ-48 — для штор на шнуре его хватает именно потому, что шнур ходит легко.
Питание — отдельная строка сметы. Обмотка 28BYJ-48 при 5 В берёт около 60 мА, две включённые — порядка 120 мА: Uno от USB это переварит, но я всё равно веду питание ULN2003 отдельным проводом от источника — штора крутится часами, и дёргать плату по земле мне не нужно. Для NEMA17 с A4988 разговор другой: двенадцать вольт, драйвер с радиатором, ток фазы под стрежнем подстроечника. И не забывайте про аварийный ручной режим — то, о чём все вспоминают после первого отключения света, а надо до.
В симуляторе привод собран на стандартной Stepper-библиотеке: AccelStepper движок не эмулирует, поэтому разгон моделируется темпом порций — у краёв хода мотор идёт медленнее, в середине быстрее. Управление: кнопка D2 — концевик нуля, тумблер D3 — расписание, светодиод D12 горит, когда штора открыта. Ход модели укорочен до 1024 шагов, чтобы демо не растягивалось на минуты.
Прогон: после запуска скетч сразу уходит в поиск нуля мелкими шагами вниз — жмите кнопку D2, и поиск остановится, позиция станет нулём. Дальше включите тумблер расписания: скетч прочитает час на карточке часов и поедет к цели — открытой до восьми вечера, закрытой после. Час можно подводить кнопками плюс-минус час прямо на карточке DS3231: перевалите границу — привод сам тронется в обратную сторону, и строка Serial покажет, куда именно он едет.
- Ноль берут концевиком при старте: мелкие шаги до щелчка, setCurrentPosition(0), вся геометрия — от нуля
- Второй край страхует константа: 2 м хода на шкиве d=20 мм — это 32 оборота, порядка 65 500 шагов
- Команды расписания — по фронту: сравнение часа каждый loop, движение — один раз по смене состояния
- AccelStepper: setAcceleration убирает рывок карниза, run() обязателен в каждом проходе loop()
- В поиске нуля ставьте лимит шагов: концевик без отказа-страховки досверлит карниз
- Ручной аварийный режим — разъём в шнуре и муфта или пружина в передаче — проектируется до, а не после
- 1. Зачем шаговику концевик нуля, если он и так считает шаги?
- 2. Карниз 2 м, шкив d = 20 мм, 2048 шагов на оборот — сколько шагов на полный ход?
- 3. Что произойдёт, если вызывать moveTo() в каждом проходе loop() без флага?
- 4. Почему 28BYJ-48 не тянет настоящую тяжёлую штору?
- 5. Как устроен аварийный ручной режим привода?
Частые вопросы по теме урока
- Зачем шаговику концевик нуля, если он и так считает шаги?
- Счётчик учитывает только те шаги, которые мотор отработал сам. Любой срыв или ручной сдвиг в него не попадает, поэтому ноль привязывают к физическому реперу. После срыва или ручного сдвига счёт врёт — нужен внешний репер
- Карниз 2 м, шкив d = 20 мм, 2048 шагов на оборот — сколько шагов на полный ход?
- За оборот штора проезжает 63 мм; два метра — это около 32 оборотов; 32 × 2048 ≈ 65 500 шагов. Это число и становится лимитом HOD. 65500
- Что произойдёт, если вызывать moveTo() в каждом проходе loop() без флага?
- Команды должны уходить по фронту — один раз при смене состояния. Непрерывный поток moveTo() перезапускает разгон, и штора дёргается у старта. Штора будет дёргаться: каждая команда перезаписывает профиль разгона
- Почему 28BYJ-48 не тянет настоящую тяжёлую штору?
- Момент 28BYJ-48 с её пластиковым редуктором — уровень лёгкой тюли и макетов. Настоящая штора весом в килограммы — это NEMA17 с драйвером на 12 В. Она не умеет менять направление вращения
- Как устроен аварийный ручной режим привода?
- Обесточенный 28BYJ-48 проворачивается пальцем, поэтому достаточно разъёма в шнуре. Тяжёлому мотору с удержанием моментом нужна проскальзывающая муфта. Разъём в шнуре мотора плюс муфта или пружина в передаче: штору ходят рукой