Собрал я как-то поворотную платформу для камеры — серво на подставке, всё честно. А потом понял: чтобы дёрнуть платформу на десять градусов, мне надо тянуться к столу, тыкаться в ноутбук, вводить угол в мониторе. Камера живёт своей жизнью, человек должен держать пульт. Пульт от машинки CAR MP3 стоит копейки, приёмник — тоже, и через вечер серво слушала кнопки с дивана. Это тот случай, когда дешёвая периферия меняет ощущение от проекта целиком: шлагбаум на столе, шторка на окне, кормушка с кнопкой «ещё» — всё это одна и та же пара «пульт плюс серво», меняется только механика на валу.
По дороге я собрал полный набор граблей. Первый приёмник воткнул ногами наоборот — корпус успел погреться до неприличного, жив он чудом. Потом скетч отзывался ровно на одну кнопку и замолкал — неделю я грешил на батарейки, а виноват был забытый вызов в одной строке. Обе истории уже разобраны по косточкам в разделе про ИК, поэтому здесь займёмся связкой: приёмник на пин 11, серво — рядом на 10, и три кнопки, которые ведут угол вверх, вниз и домой. Те два урока стоит пробежать перед этим текстом, но если времени нет — всё нужное для серво я повторю на месте.
Как команда доезжает от пульта до серво
Цепочка короткая. Пульт шлёт посылку по протоколу NEC: адрес устройства и восьмибитная команда кнопки. Приёмник — металлическая баночка с тремя ногами, TSOP или VS1838B — снимает несущую 38 кГц и отдаёт на свой выход уже готовую последовательность импульсов. Библиотека IRremote декодирует её в число, и вам остаётся один разглядеть команду: IrReceiver.decodedIRData.command. У пультов CAR MP3 адреса гуляют от партии к партии, а команды стабильны: в симуляторном пульте OK — это 67, VOL+ — 21, VOL- — 7. Свою таблицу кодов вы уже умеете собирать из урока про приём кодов, а механика кнопок, посылок и повторов лежит в уроке про управление по ИК — не поленитесь их прочесть, связка станет заметно прозрачнее.
Удержание кнопки — отдельное удовольствие: пульт шлёт повторы примерно каждые 108–110 мс, и каждый повтор библиотека отдаёт вам как новое декодирование. Для скетча это значит: держите VOL+ — угол будет шагать на +15° каждые 110 миллисекунд, и за секунду-полторы серво доедет до края вилки. Это не баг, а бесплатное непрерывное управление. По схеме: сигнальная нога приёмника идёт на пин 11 — в симуляторе макетка сама ставит ИК-приёмник на 11; серво рядом занимает следующий свободный ШИМ-пин, D10, и это видно в скетче строкой srv.attach(10). Дальность по прямой у связки честные 8–10 метров, но окно приёмника узкое — принимаемая зона лежит в пределах примерно ±35° от оси; не пытайтесь командовать платформой из-за спины. Питание у обоих общее 5 В — если хотите освежить распределение проводов, оно разложено по полочкам в уроке про провода и питание SG90.
Пара слов про монтаж, чтобы связка не рассыпалась на столе. Приёмник любит смотреть на пользователя: окно баночки выводите к краю корпуса и ничем не закрывайте — металл и тонированное стекло глушат сигнал наглухо. Провода у приёмника короткие, сантиметров пятнадцать-двадцать, дальше начинаются пропуски декодирования; если нужно дальше — экранированный кабель. Серво на подставке гудит и вибрирует — не крепите её на ту же площадку, что и приёмник: вибрация расшатывает ноги в макетке, и связь живёт с перерывами. И про питание: серво на рывке берёт сотни миллиампер, приёмнику хватает десятков, но просадка от общего провода бьёт по приёмнику первым делом — при симптомах «пульт работает через раз» смотрите питание, и только потом код. И если серво стоит далеко, сигнальный провод удлиняйте цельным, без скруток в середине: каждая скрутка — маленькая антенна, и ловите наводки от мотора самой серво.
| Кнопка CAR MP3 | Команда | Что делает скетч урока |
|---|---|---|
| OK | 67 | домой: угол 90° |
| VOL+ | 21 | плюс 15° к текущему углу |
| VOL- | 7 | минус 15° к текущему углу |
| CH | 71 | пока ничего — задание из упражнений |
| EQ | 9 | пока ничего — ваша фантазия |
Скетч: три кнопки, один угол
Ключевая идея скетча — угол живёт в переменной, которую ведём сами. Команда VOL+ не знает, где качалка сейчас, — она знает только «плюс пятнадцать»; сложение с текущим значением делаем мы, и там же стоит constrain: без него восемь нажатий VOL+ подряд кончились бы штурмом упора на 180°. Спрашивать у библиотеки через read() последний записанный угол можно, но я привык держать состояние в своей переменной: когда серво станет две, а потом шесть, один источник правды сэкономит вам вечер. Для сверки read() вызывайте сколько угодно — библиотека просто вспомнит последний записанный угол, ничего лишнего.
Шаг в 15° — не догма, а мой рабочий компромисс: 180° / 15° — двенадцать нажатий на весь ход, мелко для шлагбаума и нормально для платформы камеры. Хотите грубее — ставьте 30, тоньше — 5; константа одна. И не волнуйтесь, что серво не успевает за кнопками: приезд на 140° занимает 250–400 мс, а повторы прилетают каждые 110 мс, то есть писать вы будете быстрее, чем серво едет. Ничего не теряется: write() задаёт не очередь команд, а цель, и серво просто едет к последней названной. Порядок внутри decode-ветки жёсткий: прочли команду, отработали, записали угол в серво, напечатали ugol=, и только потом resume(). Поменять местами два последних шага — и вы начнёте терять посылки на ровном месте: приёмник должен быть отпущен до следующей посылки, а не после того, как вы закончили думать. Печать парами ugol=105 здесь не украшение: по ней в мониторе видно каждый шаг, а на реальном пульте — лесенку из повторов при удержании.
Ещё в скетче нет ни одного delay(): decode() сама решает, есть ли что читать, и loop крутится свободно. Если добавите сюда вторую задачу — мигающий светодиод, опрос кнопки — она поместится в тот же loop без конфликтов: приёмник живёт на своём прерывании, серво — на своём таймере. Единственное, чего делать не стоит, — тяжёлых пауз между decode и resume: в это окно посылки теряются, и удержание кнопки начнёт «проглатывать» шаги. А отладочный приём из прошлого урока здесь работает как есть: отправьте угол из монитора — и получите независимый канал проверки, что механика доезжает до краёв, прежде чем доверять пульту.
#include <Servo.h>#include <IRremote.h> Servo srv;int ugol = 90; // состояние держим сами void setup() { Serial.begin(9600); srv.attach(10); // ИК занял пин 11, серво — следующий, 10 IrReceiver.begin(11, ENABLE_LED_FEEDBACK); Serial.println("ugol=90");} void loop() { if (IrReceiver.decode()) { int cmd = IrReceiver.decodedIRData.command; if (cmd == 21) { // VOL+ — плюс 15 градусов ugol = constrain(ugol + 15, 0, 180); } if (cmd == 7) { // VOL- — минус 15 градусов ugol = constrain(ugol - 15, 0, 180); } if (cmd == 67) { // OK — домой, в 90 ugol = 90; } srv.write(ugol); Serial.print("ugol="); Serial.println(ugol); IrReceiver.resume(); // отпустили приёмник — без него тишина }}В симуляторе пульт лежит рядом с платой, кнопки подписаны и шлют свои коды — приёмник на карточке подсвечивает последний принятый. Упражнений четыре. Первое: повесьте на CH (команда 71) парковку — угол 0°, одна строка по образцу OK. Второе: добавьте EQ (9) как кнопку-качели — серво ходит между 60° и 120°, два if и переменная-цель. Третье — про реальную жизнь: в комнате с ярким солнцем и галогенными лампами приёмник ловит ложные посылки, серво дёргается сама; лечится направлением окна приёмника от источника света и фильтром по адресу посылки, приём которого разобран в уроке про приём кодов. Мелким дёрганьям серво при этом не удивляйтесь — мы разбирали их причины отдельно в уроке про дрожание и помехи, и ИК-ложняки — очередная строка в том же списке. Между командами серво можно усыпить detach(), чтобы не гудела в ожидании: когда будить и что при этом учесть, написано в уроке про attach, write и detach. А отладить сами углы удобнее всего числами из порта — приём из предыдущего урока отлично живёт рядом с пультом. И четвёртое, на реальной плате: возьмите пульт от другой машинки и понажимайте — чаще всего никак, потому что адрес не совпал, и это лучший фильтр соседей с такими же машинками. И не спешите чинить скетч, если серво будто «догоняет» команду: приезд на большой угол занимает свои 250–400 мс, это инерция редуктора, а не зависание.
- 1. Серво отзывается на первую кнопку и молчит на все следующие. Причина?
- 2. Настоящий пульт при удержании VOL+ шлёт повторы примерно каждые 110 мс. Что будет с углом?
- 3. Что вернёт IrReceiver.decodedIRData.command после декодирования VOL+ на симуляторном пульте?
- 4. Приёмник нагрелся за полминуты после подачи питания. Наиболее вероятная причина?
- 5. Что сделает constrain(ugol + 15, 0, 180) при ugol = 175?
- Цепочка: пульт NEC — приёмник на 3 ногах — decodedIRData.command — ваше число.
- resume() после каждой decode() — забыт, и приёмник молчит после первой кнопки.
- Угол — в своей переменной, каждая прибавка — через constrain(0, 180).
- Удержание кнопки = повторы каждые 110 мс: непрерывное управление даром.
- Приёмник на пин 11, серво на следующий ШИМ — D10: srv.attach(10).
Частые вопросы по теме урока
- Серво отзывается на первую кнопку и молчит на все следующие. Причина?
- После decode() приёмник зажат до resume(). Без него библиотека отдаст ровно одну посылку — дальше тишина при живых батарейках и целой цепочке. Забыт IrReceiver.resume() после decode()
- Настоящий пульт при удержании VOL+ шлёт повторы примерно каждые 110 мс. Что будет с углом?
- Каждый повтор — новое декодирование и плюс 15°: за секунду-полторы серво упрётся в границу вилки, и это норма — так работает непрерывная подстройка, а не поломка. Угол дойдёт до упора: каждые 110 мс плюс 15°
- Что вернёт IrReceiver.decodedIRData.command после декодирования VOL+ на симуляторном пульте?
- В нашем пульте VOL+ — команда 21. В коде её можно писать и как 0x15 — это то же число, но сверяйтесь со своей таблицей кодов. 21
- Приёмник нагрелся за полминуты после подачи питания. Наиболее вероятная причина?
- Перевёрнутый приёмник греется и умирает за минуту. Распиновку сверяют с даташитом до подачи питания — и это не перестраховка. Ноги перепутаны: плюс попал не на VCC
- Что сделает constrain(ugol + 15, 0, 180) при ugol = 175?
- Сложение даст 190, constrain зажмёт в границу 180. Без него write(190) приказал бы серво биться в упор каждые 20 мс — пластиковые шестерни такого режима не прощают. 180