Датчик температуры в теплице я подключил за один вечер, а вот смотреть на него оказалось нечем: монитор с Serial лежал на столе в доме, грядки — в сорока метрах, и ходить с ноутбуком к парнику ради числа с плавающей точкой мне быстро надоело. Цифровой индикатор не спасал: на ярком солнце семисегментная цифра читается с полуметра, а хотелось прибора, который видно от калитки — стрелка, шкала, всё как у барометра на даче у деда. Тогда серво и превратилась для меня из исполнительного механизма в измерительный прибор: платформа с бумажной шкалой, на которой стрелка показывает любое число, которое плата умеет посчитать.
Серво-прибор — переворот привычной роли. В уроке про ручное управление от ручки человек крутил потенциометр, а серво послушно повторяла; здесь всё наоборот — показания текут в скетч, а серво докладывает их стрелкой. Источником может быть что угодно на аналоговом входе: потенциометр, фоторезистор, микрофонный модуль, даже уровень сигнала с соседней платы. Механика у всех одна: analogRead, map() в угол от 0 до 180, write() — и стрелка стоит на своём делении.
В этом уроке соберём сам прибор: шкалу из бумаги с делениями через десять градусов, скетч с усреднением восьми замеров против дребезга и разберём, какой источник сигнала какую шкалу заслуживает. Плавный ход без блокировки loop() уже разобран в уроке про интерполяцию — там его приём возьмём как готовый инструмент и добавим то, чего у него не было: борьбу с шумом на входе прибора.
Стрелка против цифр: что умеет серво-прибор
Настоящий стрелочный вольтметр из радиомагазина — катушка в постоянном магнитном поле и спиральная пружина: ток отклоняет стрелку, пружина возвращает, и равновесие наступает ровно на измеренном значении. У серво-прибора физика другая, а результат тот же: плате не нужен аналоговый выход на стрелку, потому что углом командуют импульсы 50 Гц из урока про подключение SG90. Внутри серво своя электроника и свой потенциометр на валу — прибор собирается за десять минут из деталей стартового набора, тогда как магнитоэлектрическую голову ещё надо поискать.
Арифметику шкалы полезно один раз посчитать руками. Аналоговый вход отдаёт 1024 деления (0-1023), стрелке доступны 180 градусов: 1023 / 180 = 5,7 единицы АЦП на градус. Точность прибора — примерно один градус, и гнаться за половиной градуса бессмысленно: map() работает с целыми и последнюю долю отсекает. А вот шум стоит дороже: показания дрожат на 10-20 единиц даже у неподвижного потенциометра — это два-три градуса дребезга стрелки, и без фильтра прибор выглядит нервным, а не измерительным.
Скоростной потолок задаёт сама серво: SG90 проходит 60 градусов за 0,1 секунды, кадр обновления — 20 мс. Для уровня света в комнате, температуры или напряжения это с запасом — значения меняются за секунды. А вот спектру звука серво-стрелка не по зубам: на 50 Гц обновления быстрый сигнал превращается в дрожание у нуля шкалы. Моё правило: если явление меняется медленнее, чем за полсекунды — серво-прибор покажет его честно; быстрее — нужен светодиод, плоттер или экран.
#include <Servo.h> Servo srv; const int PIN_POT = A0; // источник сигналаconst int N = 8; // замеров на одно показание void setup() { srv.attach(11); Serial.begin(9600); Serial.println("Strelka: srednee iz 8 zamerov");} void loop() { long sum = 0; for (int i = 0; i < N; i = i + 1) { sum = sum + analogRead(PIN_POT); delay(5); // окно измерения 40 мс } int val = sum / N; // деление на 2^3 — быстрое int angle = map(val, 0, 1023, 0, 180); srv.write(angle); Serial.print("val="); Serial.print(val); Serial.print(" ugol="); Serial.println(angle); delay(100);}Вся фильтрация здесь — четыре строки. Сумму я сразу держу в long: для восьми замеров хватило бы и int (8 × 1023 = 8184), но привычка страховаться бесплатна — поднимете N до сорока, и сорок значений по 1023 дадут 40920, а потолок int — 32767. Переполнение при этом молчаливо: прибор не сообщит ничего, просто стрелка уедет на ноль, и вы полчаса будете искать обрыв в исправной цепи. Среднее sum / 8 — деление на степень двойки, компилятор превращает его в сдвиг: быстро и без библиотек.
Пять миллисекунд между замерами растягивают окно измерения до 40 мс: медленные наводки — лампа, тень руки, провод от фена соседа — усредняются, а прибор отвечает на ручку без ощутимой задержки. Число замеров — единственная ручка настройки фильтра. Восемь — мой компромисс для комнаты: стрелка живая, дребезга нет. Шестнадцать делают её сонной: ручка повернулась, а прибор доезжает полсекунды — для термометра нормально, для индикатора уровня уже нет. Четыре — дрожит у края. Посмотреть на шум в чистом виде удобно графиком плоттера: сырой канал против усреднённого рисует две линии, и зазор между ними — ровно работа фильтра.
| Источник сигнала | Вход | Что на шкале | Нюанс |
|---|---|---|---|
| Потенциометр | A0 | положение ручки: 0-1023 в 0-180° | эталон для калибровки нового прибора |
| Фоторезистор | A1 | освещённость: тёмный угол — светлый | шкала неравномерна — LDR нелинеен |
| Микрофонный модуль | A0 | средний уровень шума в комнате | пики короче полсекунды стрелка не поймает |
| ШИМ соседней платы | D2 | скважность 0-255 в угол | тема следующего урока — там pulseIn |
Шкала из бумаги и плавный ход
Шкалу я печатаю из транспортира на пол-листа: сектор 0-180°, риски через каждые десять градусов, цифры через тридцать. Бумага клеится на картонную платформу, качалка серво несёт стрелку — кусок велосипедной спицы или плотной проволоки на капле термоклея; как насаживать качалки без люфта, разобрано в уроке про механику рычагов. Вечер работы с ножницами — и на столе стоит прибор, который гости принимают за заводской.
Усреднение глушит мелкую дрожь, но крупный прыжок сигнала — ручку крутанули резко — стрелка всё равно пролетит рывком. Если хочется хода, как у дорогого прибора, поверх фильтра ставится интерполяция из урока про плавное движение без delay: усреднённое значение становится целью, а вал едет к ней по градусу за тик. Прибор получается двухступенчатым — фильтр убирает шум, интерполяция убирает механический рывок, и loop() при этом остаётся свободным для кнопок и монитора.
У приборной задачи есть подвох, которого не было у ручки управления: источник сигнала редко занимает весь диапазон 0-1023. Фоторезистор в комнате выдавал мне 80-400, и вся стрелка жила на первой трети шкалы — цена деления падала до семнадцати единиц АЦП на градус, и прибор никуда не годился. Лечение — растянуть фактический диапазон границами map(): map(val, 80, 400, 0, 180), где 80 и 400 сняты с реального сигнала, а constrain() после map() страхует от вылета, если сигнал когда-нибудь выскочит за снятые границы.
Границы при этом не высечены в камне: днём и вечером диапазон фоторезистора разный, и прибор, откалиброванный при лампе, ночью врёт. Мой рабочий приём — двухточечная калибровка перед серией измерений: накрыть датчик колпаком и записать минимум, посветить фонариком в упор и записать максимум, вписать два числа в map(). Потенциометру всё это не нужно — его диапазон всегда полный, поэтому каждый новый стрелочный прибор я и начинаю именно с него, а датчик подключаю вторым этапом.
Куда такой прибор встаёт в доме: указатель уровня воды в бочке на улице (глаз виден из окна), стрелка загрузки ночного рендера на старом компьютере, индикатор сигнала Wi-Fi у дачного роутера — всё, где число важнее глазами, чем цифрами. Мой тепличный термометр живёт с бумажной шкалой третий сезон и пережил двух претендентов с экранами: у экрана садится питание, а стрелке хватает той же платы, что и датчику.
Тот же поток значений стоит показывать и цифровым близнецом: плоттер Serial рисует те же значения живой линией, и пара стрелка-график закрывает оба сценария — график для отладки за столом, стрелка для наблюдения издалека. При отладке я смотрю именно на график: скачок, которого стрелка не заметила из-за усреднения, на линии виден моментально.
- Серво-прибор показывает любое число стрелкой: analogRead, map в 0-180, write — цена шкалы 5,7 единицы АЦП на градус
- Сумму замеров держите в long: сорок значений по 1023 дают 40920, int лопается молча, и стрелка уезжает на ноль
- Усреднение восьми замеров по 5 мс глушит шум ±10-20 единиц; больше восьми — стрелка сонная, меньше — дрожит
- Диапазон источника редко полный: границы map снимают с реального сигнала, constrain страхует вылет
- Servo обновляется 50 Гц и проходит 60° за 0,1 с: медленные явления показывает честно, быстрый звук — нет
- 1. Что вернёт map(300, 80, 400, 0, 180)?
- 2. Почему сумма аналоговых замеров в скетче урока объявлена long?
- 3. Стрелка прибора дрожит на месте на два-три градуса. Первое действие?
- 4. Фоторезистор в комнате выдаёт 80-400. Какую шкалу сделать прибору?
- 5. Какое явление серво-стрелка покажет честно?
Частые вопросы по теме урока
- Что вернёт map(300, 80, 400, 0, 180)?
- Формула map: (300 − 80) × 180 / 320 = 123,75, а map работает с целыми и хвост отбрасывает — 123. Точность шкалы — один градус, о половине градуса речи нет. 123 — целочисленное деление отсекает хвост 123,75
- Почему сумма аналоговых замеров в скетче урока объявлена long?
- Для восьми замеров хватило бы int (8184), но привычка страховаться бесплатна: 40 × 1023 = 40920 против потолка int в 32767. Переполнение молчаливо — стрелка просто уедет на ноль. с запасом на рост фильтра: сорок замеров дают 40920 и переполняют int
- Стрелка прибора дрожит на месте на два-три градуса. Первое действие?
- Дрожь на два-три градуса — это шум ±10-20 единиц АЦП, и лечится он фильтром: восемь замеров по 5 мс превращают дрожь в покой. Питание и пин тут ни при чём. поднять число усредняемых замеров
- Фоторезистор в комнате выдаёт 80-400. Какую шкалу сделать прибору?
- Рабочий диапазон датчика растягивают на всю шкалу границами, снятыми с реального сигнала, — иначе стрелка живёт на трети шкалы и цена деления падает втрое. Constrain страхует вылет за снятый диапазон. map(val, 80, 400, 0, 180) и constrain после
- Какое явление серво-стрелка покажет честно?
- Правило прибора: меняется медленнее полусекунды — серво покажет честно. Температура за минуты — да; вибрация, звук и пульс быстрее её возможностей. температуру, которая меняется за минуты