
Всем хабраконструкторам, привет!
Пришла мне как-то в голову дурацкая мысль: собрать девайс, который бы молотком забивал гвозди. Просто ради демонстрации работы сервопривода. Алгоритм простой: даём команду на поднятие молотка, ждём пока он поднимется, отпускаем молоток; и так пока гвоздь не будет забит. Но как узнать, что молоток поднялся и что гвоздь забит, не пользуясь дополнительными датчиками? Спросить у «глупого» сервопривода! Как именно это сделать — об этом и пойдёт речь в статье.
Что такое сервопривод? Наверное, все знают, но на всякий случай: это привод, который в отличие от мотора постоянного тока не просто крутится пока подаётся напряжение, а стремится повернуться к заданному углу и удержаться в этом положении. Угол устанавливается с помощью ШИМ (PWM)-сигнала. Сервопривод стремится к определённому положению, а следовательно должен знать своё собственное. Перед началом сборки я был уверен, что запросить текущий угол будет проще простого и это возможно «из коробки». Не тут то было. Но обо всём по порядку.
Итак, предполагаемый девайс: сервопривод с прикреплённым к нему молотком на небольшом постаменте для равновесия. Сервопривод подключается к Arduino через IO Shield, а микроконтроллер исполняет алгоритм:
- Установить сервоприводу определённый угол для поднятия молотка
- Бездействовать пока сервопривод не сообщит, что угол достигнут
- Отключить питание сервопривода, чтобы молоток упал на гвоздь
- Прочитать угол в упавшем положении
- Если угол после падения несколько раз подряд не изменился — значит гвоздь перестал вколачиваться. Предположительно он забит — прекращаем исполнение
- Если угол изменился, начинаем сначала
Берём исходные части:

Пилим и скручиваем:

Приступаем к написанию прошивки для Arduino… Довольно быстро становится понятно, что установить определённый угол для сервы — не проблема. В частности, это позволяет сделать стандартная библиотека Servo, которая из заданного в градусах угла формирует соответствующий PWM-сигнал. А вот с чтением — проблема: функции для этого нет.
Быстро погуглив проблему, нашёл кучу сообщений на форумах, где на этот вопрос авторитетно отвечали: «Это не возможно! Сервоприводы — это write-only устройства». Меня это привело в замешательство, я интуитивно чувствовал, что достать эти данные как-то просто можно.
Матчасть
После недолгих поисков в сети можно понять как устроена серва. Это обычный мотор постоянного тока, который соединён с выведенным шпинделем через несколько шестерней, формирующих пониженную передачу. Этот же шпиндель с внутренней стороны физически прикреплён к потенциометру (подстроечному резистору). При вращении мотора шпиндель поворачивается, поворачивается и бегунок потенциометра, выходное напряжение потенциометра меняется, мозги сервы его считывают и если напряжение достигло заданного уровня — цель достигнута, мотор отключается от питания.
То есть, у нас есть потенциометр, по сигналу с которого можно определить текущий угол. Осталось только разобрать сервопривод и подключиться в нужном месте. Разбираем:

Сразу скажу, что сервопривод с фотографии я безвозвратно сломал в процессе разборки. Не нужно было вообще выламывать плату с электроникой, достаточно просто снять заднюю крышку, которая держится на 4-х винтах. Но сразу это было не очевидно, и чтобы понять куда на плате припаян потенциометр, пришлось пожертвовать одним приводом.
Вот как припаян потенциометр на сервоприводах от DFRobot:

Нам нужен сигнал с бегунка, который меняется в зависимости от угла поворота от минимального до максимального напряжения. Берём мультиметр, вращаем шпиндель и смотрим: каким углам какой сигнал соответствует. Для моей сервы углу в 0° соответствует напряжение 0.43 В, а максимальному углу поворота в 180° соответствует напряжение 2.56 В.
Аккуратно припаиваем новый сигнальный провод.

Подключаем его к аналоговому входу A5 на Arduino. Закрываем крышку. Пишем программу:
#include <Servo.h>
// разрешене аналогого порта
#define A_MAX 1024
// опорное напряжение на котором работает серва
#define A_VREF 5
// предельные уровни сигнала с сервы
#define A_VMIN 0.43
#define A_VMAX 2.56
Servo servo;
int lastHitAngle = 0;
int hitAngleMatches = 0;
bool jobDone = false;
/*
* Возвращает текущий угол поворота сервы исходя
* из сигнала с его потенциометра
*/
int realAngle()
{
return map(
analogRead(A5),
A_MAX * A_VMIN / A_VREF,
A_MAX * A_VMAX / A_VREF,
0, 180);
}
void setup()
{
}
void loop()
{
if (jobDone)
return;
// включаем серву и просим повернуться до положения 70°
servo.attach(6);
servo.write(70);
// ждём поворота. 5° запаса на всякие погрешности
while (realAngle() < 65)
;
// бросаем молоток и ждём немного пока он успокоится
servo.detach();
delay(1500);
// запоминаем угол после падения и сопоставляем его с
// предыдущим
int hitAngle = realAngle();
if (hitAngle == lastHitAngle)
++hitAngleMatches;
else {
lastHitAngle = hitAngle;
hitAngleMatches = 0;
}
// если угол не менялся 5 раз — мы закончили
if (hitAngleMatches >= 5)
jobDone = true;
}
Включаем, пробуем, работает!
Что делать с полученным опытом — вариантов много: можно сделать контроллер вроде того, что используется на кораблях для установки тяги (полный вперёд / полный назад); можно использовать серву с обратной связью как элемент автономного рулевого управления какой-нибудь машины; можно много всего. Да прибудет со всеми нами фантазия!