3 мая 2016 г.

Кнопка сброса для поворотного механизма

Вот такое замудреное название, а на самом деле хотел поделиться опытьм создания поворотного механизма как, например, на камерах видеонаблюдения. То есть организовать вращение в вертикальной и горизонтальной плоскостях. Это позволит нам смотреть вверх, вниз и в стороны, что достаточно для подного обзора. Делать это будут 2 сервомотора. Так же будет присутствовать кнопка сброса, т.е. по ее нажатию сервомоторы вернуться в начальное положение. Всю базу мы возьмем из предыдущего поста, но несколько усовершенствуем его.
Итак, в предыдущей статье сервомотор двигался в зависимости от положения стика джойстика. Принцип похож на симулятор водителя от первого лица, но нас это не устраивает. Если подобный механизм установить на квадрокоптер, то мы можем смотреть только вперед и иногда поворачивать голову в каком-то направлении. Да, так же ведет себя пилот. Но мы же углубляемся в робототехнику! Нам надо, что бы после того, как стик джойстика вернулся в центр, то наша камера осталась в повернутом положении. Это позволит нам лететь вперет, а спимать что-то сбоку. Но это не все. Наверняка Вы замечали в компьютерных играх, чем сильнее отклоняешь стик джойстика, тем больше скорость у машины, например. Так вот, в нашем случае мы просто сделаем несколько этапов скорости, что позволит нам более точно позиционировать серву, но при этом будет возможность быстрого ее вращения.
Еще у нас запланирована кнопка возврата на начальное положение. Эта кнопка будет просто позиционировать оба сервомотора в какую-то заданную позицию. И, что бы казаться совсем профессионалами, повесим эту кнопку на прерывание Arduino, что бы она могла работать в любой момент.

Тонкости

У нас все как и ранее - сервомотор (в наличии, к сожалению, пока только один) подключен к пину D9, джойстик отдает только позицию X и подключен к A0. Тонкость в подключении кнопки - она подключена к линии в 5В через подтягивающий резистор на 100 кОм (под рукой другого не было), так мы всегда получаем напряжение в 5В на кнопке. Сигнал от кнопки идет на вход инвертирующего тригера Шмитта, а уже выходной сигнал подается на пин D2, который поддерживает прерывания.

Альтернатива на подумать

Кнопка - это хорошо, но зачем использовать отдельную кнопку, когда на самом джойстике есть своя кнопка? Этот вопрос я обдумываю, поскольку на данный момент эта кнопка постоянно вызывает прерывание и не дает нормально вращать серву.

Фотоотчет



Не было большого триггера Шмитта, пришлось припаять
провода и контакты, что бы можно было воспользоваться
 тем, какой есть

Скетч

/**
 * Вращение сервомотора под управлением джойстика
 */
#include <Servo.h> 
 
#define SERVO_START_ANGLE 90            // Начальный угол сервы
#define SERVO_DEFAULT_ROTATE_SPEED 1    // Скорость вращения по умолчанию
#define SERVO_MAX_LIMIT 180             // Максимальный угол поворота сервы
#define SERVO_MIN_LIMIT 15              // Минимальный угол поворота сервы
#define SERVO_SPEED_COUNT 5             // Количество скоростей поворота сервы (итератор)
 
 
Servo servo;    // Сервомотор
 
int servoPin = 9;   // Пин для сервы
int ledPin = 13;    // Пин светодиода (здесь встроеный светодиод)
//int switchPin = 2;  // Пин кнопки джойстика
int xPin = A0;      // Пин для считывания по оси X
//int yPin = A1;      // Пин для считывания оси Y
int resetBtnPin = 2;    // Пин для кнопки сброса
 
int valPosX = 0;    // Текущее положение X
int valServoAngle = 0;  // Текущий угол сервы
int valCenterX = 0; // Значение центра X (стик по центру)
int valServoSpeed = 1;  // Скорость вращения сервы
 
int arrLimits[SERVO_SPEED_COUNT];   // Соответствие ограничений по уровню скорости
 
void setup() {
    Serial.begin(9600);
 
    // Ставим соответствующие атрибуты для пинов
    pinMode(ledPin, OUTPUT);
    pinMode(xPin, INPUT);
    pinMode(resetBtnPin, INPUT);
 
    // Добавляем прерывание
    attachInterrupt(digitalPinToInterrupt(resetBtnPin), resetServoPosition, RISING);
 
    // подцепляем серву
    servo.attach(servoPin);
 
    // Ждем секунду
    delay(1000);
 
    // Ставим положение сервы на 90 градусов
    // т.к. джойстик по умолчанию стоит по центру
    // (вообще не обязательная команда)
    valServoAngle = SERVO_START_ANGLE;
    servo.write(valServoAngle);
 
    // Установим начальную скорость вращения сервы
    valServoSpeed = SERVO_DEFAULT_ROTATE_SPEED;
 
    // Читаем текущее положение X
    valCenterX = analogRead(xPin);
 
    // Подсчитываем ограничения для уровней скорости
    calculateLimits();
 
    // Моргнем 4 раза, сказав, что мы закончили с первоначальными настройками
    // За это время серва точно доберется до положения в 90 градусов
    for (int i = 0; i < 4; ++i) {
        digitalWrite(ledPin, HIGH);
        delay(200);
        digitalWrite(ledPin, LOW);
        delay(200);
    }
}
 
/**
 * Бесконечный цикл
 */
void loop() {
    // Считываем положение джойстика по X
    int xVal = analogRead(xPin);
 
    // Убедимся, что значение в интервале от 0 до 1023
    xVal = constrain(xVal, 0, 1023);
 
    // Проверяем, что разность от центра до положения больше 5
    if (abs(valCenterX - xVal) > 5) {
        // В таком случае начинаем вращение
 
        // Вычислим скорость вращения
 
        // Возьмем абсолютную величину по X
        int absX = abs(valCenterX - xVal);
 
        // Устанавливаем скорость вращения
        valServoSpeed = getServoSpeed(absX);
 
        // Множитель. Принимает значения 1 или -1
        int mult = (valCenterX - xVal > 0) ? 1 : -1;
 
        // Вычисляем значение конечной позиции
        valServoAngle += valServoSpeed * mult;
 
        // Проверяем, не превысили ли лимит
        valServoAngle = constrain(valServoAngle, SERVO_MIN_LIMIT, SERVO_MAX_LIMIT);
 
        // Поворачиваем серву
        servo.write(valServoAngle);
 
        // Ждем 15 милисекунд, что бы серва успела повернуться
        delay(15);
    }
}
 
/**
 * Вычисление огланичений для уровней скорости
 *
 * Эта функция является частью оптимизации и позволяет сократить объем используемой памяти
 */
void calculateLimits() {
    // Вычислим количество шагов в интервале наклона стика
    int stepCount = valCenterX / SERVO_SPEED_COUNT;
 
    // Заполняем массив ограничений
    for (int i = 0; i < SERVO_SPEED_COUNT; i++) {
        arrLimits[i] = stepCount * (i + 1);
    }
 
    // У последнего значения добавим 1, что бы попало значение максимального наклона стика
    arrLimits[SERVO_SPEED_COUNT - 1] = valCenterX + 1;
}
 
/**
 * Функция возвращает уровень скорости в зависимости от значения наклона стика
 */
int getServoSpeed(int absX) {
    // Цикл по всем ограничениям
    for (int i = 0; i < SERVO_SPEED_COUNT; i++) {
 
        // Минимальная граница
        int min = (i == 0) ? 0 : arrLimits[i - 1];
 
        // Максимальная граница
        int max = arrLimits[i];
 
        // Если значение попадает в этот интервал, то это наш уровень скорости
        if (absX >= min && absX < max) {
            return i + 1;
        }
    }
 
    // Если ничего не получилось, то скорость вращения по умолчанию
    return SERVO_DEFAULT_ROTATE_SPEED;
}
 
/**
 * Сброс позиции сервы на начальный угол
 */
void resetServoPosition() {
    // Вращаем серву
    servo.write(SERVO_START_ANGLE);
}

Комментариев нет:

Отправить комментарий