Машинка собралась за выходные: шасси с двумя моторчиками, драйвер L298N, Arduino, приёмник на пине 2. Кнопки 2, 8, 4 и 6 типового пульта из набора повесились на направления за полчаса, и два вечера я с удовольствием гонял её по коридору. Пока не заметил: отпускаю кнопку — а машинка едет. Ещё метр, ещё полметра, финал — бампер в плинтус на полном ходу.
Пульт честный: отпустил — кадры кончились. Врал скетч: получив команду «вперёд», он включал моторы и держал их до следующей команды. В кадре NEC нет сигнала «кнопку отпустили» — есть только тишина на приёмнике. Поэтому машинке нужен свой детектор отпускания: считать время с последнего кадра и глохнуть, когда кадры перестали приходить.
Здесь собираем управление машинкой целиком: шесть проводов драйвера, скорость от repeat-кадров, стоп по таймауту и питание, после которого плата не перезагружается на поворотах. Repeat-кадры мы уже приручали в уроке про управление светом — там они двигали яркость, здесь будут разгонять колёса.
L298N: два моста и шесть проводов
L298N — два H-моста в одном корпусе: левый мотор живёт на ENA, IN1 и IN2, правый — на ENB, IN3 и IN4. Пара входов направления задаёт полярность: IN1 высоко, IN2 низко — мотор крутится в одну сторону, наоборот — в другую. Скорость делает вывод ENA, на который подаётся ШИМ: 0 — стоит, 255 — максимум. Классическая находка новичка — джамперы на ENA и ENB у свежих модулей: пока они надеты, мотор получает полную скорость всегда, и ваша переменная skorost ничего не решает. Снимаете джампер — провод с ШИМ-пина, и регулировка оживает.
Числа, чтобы не удивляться на трассе. На транзисторах моста падает 1,4-2 вольта: моторчик на 6 В от батареи 7,4 В крутится бодро, а от пяти вольт стабилизатора Arduino — вяло, будто присел. Пара средних моторчиков в пике ест 1-2 ампера — в десять раз больше, чем отдаст стабилизатор платы. Поэтому питание у драйвера всегда своё: 7,4 В от двухбаночной Li-ion или шесть батареек AA, минус общий с Arduino. Щёточные моторы ещё и шумы: конденсатор 0,1 мкФ на клеммы моторчика гасит искрение, из-за которого приёмник ловит фантомные кадры — про фантомы у меня есть целый разбор в уроке про приёмник.
ШИМ для ENA и ENB я беру с пинов 5 и 6 — аппаратные пины широтно-импульсной модуляции, про которые писали в уроке про analogWrite. Если мотор к Arduino вы подключаете впервые, начните с одномоторного урока через транзистор: там арифметика пин-мотор-ток видна в чистом виде, здесь её просто вдвое больше.
Кнопки берём из таблицы кодов, снятой в первом уроке раздела: двойка шлёт команду 25, восьмёрка 28, четвёрка 12, шестёрка 94, кнопка PLAY посередине — 24. На пульте это стрелочная крестовина, и рука привыкает к ней за минуту — та же раскладка, что у брендовых машинок с радиорынка, где честно написано: пульт от аудиосистемы, машина слушает.
Скорость набирается repeat-кадрами: каждый пришедший кадр, первое нажатие или повтор, добавляет к ШИМ шаг 20 с потолком 230. Удержание на реальном пульте шлёт повторы каждые 108-110 мс — около девяти кадров в секунду, то есть 180 единиц ШИМ в секунду: от стартовых 120 до потолка за полсекунды. Отпустили — кадры кончились. Дальше работает детектор отпускания: если millis() минус время последнего кадра больше 300 мс, режим сбрасывается в стоп, скорость — в стартовую. Три сотни миллисекунд взял не с потолка: кадр-повтор ходит каждые 108-110 мс, одна пропажа не глохнет машину, а двойная тишина уже значит «палец убрали».
Заметьте: кнопка «стоп» в этой схеме — страховка, а не основной тормоз. Основной тормоз — таймаут: рука убрала, колёса встали. Точно так же ведёт себя телевизор, когда вы держите громкость: отпустили — и всё, никакой отдельной кнопки тишины у него нет. Метку lastKadr обновляет любой кадр — и первое нажатие, и повтор, поэтому переключение направления на ходу тоже продлевает поездку, а не срезает её.
#include <IRremote.hpp> const int IR_PIN = 2;// левый мотор: ENA - скорость, IN1/IN2 - направление// правый мотор: ENB - скорость, IN3/IN4 - направлениеconst int ENA = 5;const int IN1 = 7;const int IN2 = 8;const int ENB = 6;const int IN3 = 12;const int IN4 = 13; int skorost = 120; // стартовый ШИМint rezhim = 0; // 0 стоп, 1 вперёд, 2 назад, 3 влево, 4 вправоunsigned long lastKadr = 0; void katit() { if (rezhim == 1) { // вперёд digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, skorost); analogWrite(ENB, skorost); } else if (rezhim == 2) { // назад digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); analogWrite(ENA, skorost); analogWrite(ENB, skorost); } else if (rezhim == 3) { // влево: правое колесо быстрее digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, skorost / 3); analogWrite(ENB, skorost); } else if (rezhim == 4) { // вправо: левое колесо быстрее digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); analogWrite(ENA, skorost); analogWrite(ENB, skorost / 3); } else { // стоп analogWrite(ENA, 0); analogWrite(ENB, 0); }} void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); pinMode(ENA, OUTPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); pinMode(ENB, OUTPUT); pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);} void loop() { if (IrReceiver.decode()) { int cmd = IrReceiver.decodedIRData.command; if (cmd == 25) { rezhim = 1; skorost = skorost + 20; } // 2 - вперёд else if (cmd == 28) { rezhim = 2; skorost = skorost + 20; } // 8 - назад else if (cmd == 12) { rezhim = 3; skorost = skorost + 20; } // 4 - влево else if (cmd == 94) { rezhim = 4; skorost = skorost + 20; } // 6 - вправо else if (cmd == 24) { rezhim = 0; skorost = 120; } // 5 - стоп if (skorost > 230) { skorost = 230; } lastKadr = millis(); // любой кадр продлевает жизнь текущему режиму IrReceiver.resume(); katit(); } // отпускание для ИК-пульта - это тишина: кадров нет дольше 300 мс if (rezhim != 0 && millis() - lastKadr > 300) { rezhim = 0; skorost = 120; katit(); }}| Кнопка | command | Колёса L/R | Поведение |
|---|---|---|---|
| 2 | 25 | оба: ШИМ растёт с кадрами | вперёд, разгон за полсекунды удержания |
| 8 | 28 | оба, направления перевернуты | назад |
| 4 | 12 | левое треть, правое полное | дуга влево |
| 6 | 94 | левое полное, правое треть | дуга вправо |
| 5 (PLAY) | 24 | оба ноль | стоп и сброс скорости к стартовой |
Симулятор: два колеса и таймаут
Две оговорки, чтобы симулятор не обманул. Первая: мотор в макетке однонаправленный — назад колёса крутятся так же, как вперёд, и направление видно только в Serial. Реверс делает H-мост на реальном драйвере, и скетч с IN-парами из кода выше честный. Вторая: в симе каждое нажатие кнопки — ровно один кадр, автоповтора нет. Удержание изображается ритмом кликов: пауза между ними меньше таймаута — колёса продолжают крутиться, тишина дольше 400 мс — стоят. На настоящем пульте тот же ритм держит протокол: повтор каждые 108-110 мс.
Скорость в симе копится с каждым кликом до потолка 230 — нажмите двойку трижды и сравните строки shim= в Serial. Сброс — кнопкой 5 или тем же таймаутом. Повороты видно по разнице оборотов: колёса на D10 и D9 крутятся с разной скоростью, как дифференциал у настоящей машинки. Этот узор — замена реального удержания, и он же помогает отладить таймаут без пульта: достаточно смотреть на строки kadrov_net.
- L298N: ENA/IN1-IN2 — левый мотор, ENB/IN3-IN4 — правый; IN-пара — направление, EN — ШИМ скорости
- Джамперы на ENA/ENB дают полную скорость всегда — для регулировки их снимают
- Скорость растёт от repeat-кадров: каждый кадр плюс 20 ШИМ, удержание — около 9 кадров в секунду
- Отпускание — это тишина: millis() с последнего кадра больше 300 мс — стоп и сброс скорости
- Моторы питаются от своего источника 7,4 В, минус общий с платой; на мосту падает 1,4-2 В
- Кнопка «стоп» — страховка; основной тормоз — таймаут по отсутствию кадров
- 1. Кнопку отпустили, колёса крутятся дальше. Как скетч понимает, что кнопку отпустили?
- 2. За что отвечают входы IN1 и IN2 драйвера L298N?
- 3. Как растёт скорость машинки в скетче урока?
- 4. При старте моторов плата перезагружается. Виновник?
- 5. Сколько повторных кадров в секунду шлёт NEC-пульт при удержании кнопки?
Машинка слушается пульта, тормозит сама и не сажает плату — база телематики готова. Дальше по вкусу: серво на поворотное колесо, дальномер перед бампером, реле для клаксона. А детектор отпускания — приём на все случаи пультового хозяйства: любой ИК-прибор можно научить понимать «руку убрали», просто следя за тишиной на приёмнике. Если логика в loop начнёт обрастать таймерами, перечитайте урок про millis вместо delay: детектор отпускания построен ровно на том же приёме сравнения millis с меткой, и таймаутов в одном скетче спокойно живут несколько.
Частые вопросы по теме урока
- Кнопку отпустили, колёса крутятся дальше. Как скетч понимает, что кнопку отпустили?
- В кадре NEC нет признака отпускания. Единственный сигнал — прекращение кадров: таймер с последнего кадра перевалил за 300 мс — режим сбрасывается в стоп. По тишине: millis() с последнего принятого кадра превысил таймаут
- За что отвечают входы IN1 и IN2 драйвера L298N?
- IN1/IN2 — направление левого мотора, IN3/IN4 — правого. Скорость сидит на выводах ENA и ENB, туда идёт ШИМ; джамперы на них заменяют регулировку полной скоростью. За направление левого мотора: пара задаёт полярность
- Как растёт скорость машинки в скетче урока?
- Удержание шлёт повторы каждые 108-110 мс, и каждый кадр поднимает ШИМ на шаг: за полсекунды удержания — от стартовых 120 до потолка 230. Каждый пришедший кадр, включая повторы, добавляет шаг к ШИМ
- При старте моторов плата перезагружается. Виновник?
- Пик старта 0,5-1,5 А на стабилизаторе 5 В — гарантированная просадка. Моторам нужен свой источник, плате — свой, а минусы соединяются в одну точку. Моторы и Arduino сидят на одном стабилизаторе: пик тока роняет напряжение
- Сколько повторных кадров в секунду шлёт NEC-пульт при удержании кнопки?
- Кадр-повтор ходит каждые 108-110 мс, то есть примерно 9 штук в секунду. Отсюда и таймаут отпускания в 300 мс — с запасом на пару пропущенных кадров. Около 9 — каждые 108-110 мс