Skip to content Skip to main navigation Skip to footer

RC машинка. Мотор и сервопривод

Задача


  • Перевести магазинную радиоуправляемую машинку на управление с Arduino по радио 433 МГц
    • Руль – сервомашинка
    • Привод колёс – коллекторный мотор
  • В рамках данного примера брать сигнал управления с руля и педалей для компьютера
    • Потенциометр – руль
    • Потенциометр – газ
    • Потенциометр – тормоз (задний ход)

Базовые уроки


Подключение


Базовая схема передатчика и приёмника:

Библиотеки


  • Gyver433 – библиотека для радиомодулей
  • Servo – встроенная библиотека для управления сервоприводом

Программа


Логика работы следующая: передатчик измеряет показания потенциометров, преобразует в нужный диапазон для управления и отправляет по радио 10 раз в секунду. Приёмник принимает и раздаёт сигналы на мотор и сервопривод.

Библиотека Gyver433 позволяет передавать любые данные, поэтому для удобства и наглядности сделаем структуру. Для скорости используем int, значения в нашем случае не будут превышать -255.. 255. Для поворота руля серво хватит byte, так как диапазон поворота серво составляет 0.. 180 градусов. Структура выглядит так:

// формат пакета для отправки
struct Data {
  int16_t speed;  // -255.. 255
  uint8_t steer;  // 0..180
};

Передатчик


Зададим константами пины подключения, для удобства дальнейшей работы:

#define RADIO_PIN 2     // пин радио
#define THROT_PIN A0    // газ
#define BRAKE_PIN A1    // тормоз
#define STEER_PIN A2    // руль

Далее нам нужны пределы сигналов с потенциометров руля, газа и заднего хода, чтобы масштабировать этот диапазон для отправки:

// пределы сигналов с датчиков
#define STEER_MIN 1023  // руль
#define STEER_MAX 0
#define THROT_MIN 330   // газ
#define THROT_MAX 0
#define BRAKE_MIN 190   // тормоз
#define BRAKE_MAX 1023

Эти значения нужно будет измерить и исправить для своего железа.

У сервы и мотора тоже есть пределы, предлагаю ввести их на стороне передатчика, чтобы для настройки прошивать только его и не разбирать машинку:

// пределы поворота серво
#define SERVO_MIN (90-40)
#define SERVO_MAX (90+40)

// пределы газа и тормоза
#define MOTOR_MAX 250
#define MOTOR_MIN 100

Данные значения опять же настраиваются под своё железо и предпочтения.

Затем подключаем библиотеку и настраиваем радио. Я буду использовать FAST режим и скорость 3000, “зелёные” радиомодули отлично работают в таком режиме.

#define G433_SPEED 3000
#define G433_FAST
#include <Gyver433.h>
Gyver433_TX<RADIO_PIN> tx;  // указали пин

В блоке setup() запустим Serial для вывода сигналов с потенциометров. После настройки Serial можно будет отключить (закомментировать строку):

void setup() {
  Serial.begin(9600);
}

В блоке loop() последовательно выполняем следующие действия:

Читаем аналоговый сигнал со всех трёх элементов управления:

// читаем сигналы с управления
int steer = analogRead(STEER_PIN);
int throt = analogRead(THROT_PIN);
int brake = analogRead(BRAKE_PIN);

Выводим сырые значения в порт, чтобы настроить по ним диапазоны. После настройки вывод можно убрать:

// для отладки
Serial.print(steer);
Serial.print(',');
Serial.print(throt);
Serial.print(',');
Serial.println(brake);

Далее приводим эти значения к указанным диапазонам для серво и мотора, и на всякий случай ограничиваем:

// приводим
steer = map(steer, STEER_MIN, STEER_MAX, SERVO_MIN, SERVO_MAX);
throt = map(throt, THROT_MIN, THROT_MAX, 0, MOTOR_MAX);
brake = map(brake, BRAKE_MIN, BRAKE_MAX, 0, MOTOR_MIN);

// ограничиваем
steer = constrain(steer, SERVO_MIN, SERVO_MAX);
throt = constrain(throt, 0, MOTOR_MAX);
brake = constrain(brake, 0, MOTOR_MIN);

Осталось создать экземпляр структуры, заполнить его данными и отправить по радио. С педалями газ-тормоз поступим следующим образом: вычтем сигнал тормоза (заднего хода) из сигнала “газа”. Это позволит допускать одновременное нажатие педалей, и работать оно будет вполне реалистично: дали газу, чуть поджали тормоз – машинка стала ехать медленнее =)

// готовим к отправке
Data data;
data.steer = steer;

// газ минус тормоз
// Допускает одновременное нажатие
data.speed = throt - brake;

// отправляем
tx.sendData(data);

Слишком часто отправлять нет смысла, поэтому введём задержку на 10 мс (отправка 100 раз в секунду). Если вы будете модифицировать данный проект и задержка будет мешать – отправку всегда можно сделать по таймеру на millis() (см. уроки).

// 100 раз в секунду
delay(10);

Приёмник


В приёмной части тоже задаём пины:

#define RADIO_PIN 2   // пин радио
#define SERVO_PIN 5   // пин серво
#define MOTOR_A 3     // пин мотора
#define MOTOR_B 11    // пин мотора

Также я ввёл такую настройку, как минимальная скорость мотора. Например мы знаем, что машинка начинает ехать при значении сигнала ШИМ больше 45. В дальнейшей программе диапазон управления будет отмасштабирован таким образом, чтобы при небольшом “нажатии на педаль” значение ШИМ росло с 45 (или другого значения):

// мин. скорость мотора (0-255)
// для большей резкости управления
#define MIN_DUTY 45

Далее подключаем библиотеку радио и настраиваем на такую же скорость, как у приёмника. Также у приёмника есть буфер, мы будем передавать 3 байта данных (int + byte), размер буфера ставим 3:

#define G433_SPEED 3000
#include <Gyver433.h>
Gyver433_RX<RADIO_PIN, 3> rx; // размер буфера 3 байта

Подключаем библиотеку серво и создаём себе одну серву:

#include <Servo.h>
Servo servo;

Для мотора я написал отдельный класс, который использует два ШИМ пина для равномерного управления. Он просто размещается в программе:

Класс Motor
// мини "библиотека" мотора
class Motor {
  public:
    Motor(const uint8_t pinA, const uint8_t pinB, const uint8_t minD = 0) :
      _pinA(pinA), _pinB(pinB), _minD(minD) {
      pinMode(_pinA, OUTPUT);
      pinMode(_pinB, OUTPUT);
    }

    void run(int speed) {
      speed = constrain(speed, -255, 255);
      speed = (long)speed * (255 - _minD) / 255;

      if (speed > 0) {
        digitalWrite(_pinA, 1);
        analogWrite(_pinB, 255 - (speed + _minD));
      } else if (speed < 0) {
        digitalWrite(_pinB, 1);
        analogWrite(_pinA, 255 - (-speed + _minD));
      } else {
        digitalWrite(_pinA, 0);
        digitalWrite(_pinB, 0);
      }
    }

  private:
    const uint8_t _pinA, _pinB, _minD;
};

По сути, это мини библиотека. Создаём себе мотор с указанием пинов и минимального сигнала ШИМ:

Motor motor(MOTOR_A, MOTOR_B, MIN_DUTY);

В блоке setup() включаем Serial (если нужен для отладки, в рабочем проекте можно выключить), подключаем серво и разгоняем частоту ШИМ как в этом уроке:

void setup() {
  Serial.begin(9600);

  // подрубаем серво
  servo.attach(SERVO_PIN);
  servo.write(90);

  // разгоняем ШИМ чтобы не гудело
  // Пины D3 и D11 - 8 кГц
  TCCR2B = 0b00000010;  // x8
  TCCR2A = 0b00000011;  // fast pwm
}

В блоке loop() нам нужно опрашивать радио и раздавать сигналы управления на “железо”. Но начнём мы с другого: представьте, что вы ехали на полном газу, и тут вдруг пропал сигнал с радио. Машинка продолжит движение с той же скоростью. Чтобы обезопасить себя от этого, введём таймаут: если сигнала с радио не было дольше определённого количества времени – останавливаем мотор. На RC сленге это называется failsafe:

// таймаут приёма данных
static uint32_t tmr;
// если сигнала с радио нет 200 мс
// выключаем мотор
if (millis() - tmr > 200) {
  motor.run(0);
  tmr = millis();
}

Далее нужно принять сигнал с радио (подробнее см. пример в библиотеке Gyver433). Если данные успешно приняты – сбрасываем таймаут и раздаём сигналы управления:

// приём по радио
if (rx.tick()) {
  Data data;

  // если чтение без ошибок
  if (rx.readData(data)) {
    tmr = millis(); // сброс таймаута
    servo.write(data.steer);
    motor.run(data.speed);
    /*
      // для отладки
      Serial.print(data.steer);
      Serial.print(',');
      Serial.println(data.speed);
    */
  }
}

Также можно вывести отладочную информацию в порт, проверить, работает ли радио. В рабочей программе отладку можно закомментировать.

Полный код программы


Передатчик
#define RADIO_PIN 2     // пин радио
#define THROT_PIN A0    // газ
#define BRAKE_PIN A1    // тормоз
#define STEER_PIN A2    // руль

// пределы сигналов с датчиков
#define STEER_MIN 1023  // руль
#define STEER_MAX 0
#define THROT_MIN 330   // газ
#define THROT_MAX 0
#define BRAKE_MIN 190   // тормоз
#define BRAKE_MAX 1023

// пределы поворота серво
#define SERVO_MIN (90-40)
#define SERVO_MAX (90+40)

// пределы газа и тормоза
#define MOTOR_MAX 250
#define MOTOR_MIN 100

//
#define G433_SPEED 3000
#define G433_FAST
#include <Gyver433.h>
Gyver433_TX<RADIO_PIN> tx;  // указали пин

// формат пакета для отправки
struct Data {
  int16_t speed;  // -255.. 255
  uint8_t steer;  // 0..180
};

void setup() {
  Serial.begin(9600);
}

void loop() {
  // читаем сигналы с управления
  int steer = analogRead(STEER_PIN);
  int throt = analogRead(THROT_PIN);
  int brake = analogRead(BRAKE_PIN);

  // для отладки
  Serial.print(steer);
  Serial.print(',');
  Serial.print(throt);
  Serial.print(',');
  Serial.println(brake);

  // приводим
  steer = map(steer, STEER_MIN, STEER_MAX, SERVO_MIN, SERVO_MAX);
  throt = map(throt, THROT_MIN, THROT_MAX, 0, MOTOR_MAX);
  brake = map(brake, BRAKE_MIN, BRAKE_MAX, 0, MOTOR_MIN);

  // ограничиваем
  steer = constrain(steer, SERVO_MIN, SERVO_MAX);
  throt = constrain(throt, 0, MOTOR_MAX);
  brake = constrain(brake, 0, MOTOR_MIN);

  // готовим к отправке
  Data data;
  data.steer = steer;

  // газ минус тормоз
  // Допускает одновременное нажатие
  data.speed = throt - brake;

  // отправляем
  tx.sendData(data);

  // 100 раз в секунду
  delay(10);
}
Приёмник
#define RADIO_PIN 2   // пин радио
#define SERVO_PIN 5   // пин серво
#define MOTOR_A 3     // пин мотора
#define MOTOR_B 11    // пин мотора

// мин. скорость мотора (0-255)
// для большей резкости управления
#define MIN_DUTY 45

//
#define G433_SPEED 3000
#include <Gyver433.h>
Gyver433_RX<RADIO_PIN, 3> rx; // размер буфера 3 байта

#include <Servo.h>
Servo servo;

// формат пакета для приёма
struct Data {
  int16_t speed;  // -255.. 255
  uint8_t steer;  // 0..180
};

// мини "библиотека" мотора
class Motor {
  public:
    Motor(const uint8_t pinA, const uint8_t pinB, const uint8_t minD = 0) :
      _pinA(pinA), _pinB(pinB), _minD(minD) {
      pinMode(_pinA, OUTPUT);
      pinMode(_pinB, OUTPUT);
    }

    void run(int speed) {
      speed = constrain(speed, -255, 255);
      speed = (long)speed * (255 - _minD) / 255;

      if (speed > 0) {
        digitalWrite(_pinA, 1);
        analogWrite(_pinB, 255 - (speed + _minD));
      } else if (speed < 0) {
        digitalWrite(_pinB, 1);
        analogWrite(_pinA, 255 - (-speed + _minD));
      } else {
        digitalWrite(_pinA, 0);
        digitalWrite(_pinB, 0);
      }
    }

  private:
    const uint8_t _pinA, _pinB, _minD;
};

Motor motor(MOTOR_A, MOTOR_B, MIN_DUTY);

void setup() {
  Serial.begin(9600);

  // подрубаем серво
  servo.attach(SERVO_PIN);
  servo.write(90);

  // разгоняем ШИМ чтобы не гудело
  // Пины D3 и D11 - 8 кГц
  TCCR2B = 0b00000010;  // x8
  TCCR2A = 0b00000011;  // fast pwm
}

void loop() {
  // таймаут приёма данных
  static uint32_t tmr;
  // если сигнала с радио нет 200 мс
  // выключаем мотор
  if (millis() - tmr > 200) {
    motor.run(0);
    tmr = millis();
  }

  // приём по радио
  if (rx.tick()) {
    Data data;

    // если чтение без ошибок
    if (rx.readData(data)) {
      tmr = millis(); // сброс таймаута
      servo.write(data.steer);
      motor.run(data.speed);
      /*
        // для отладки
        Serial.print(data.steer);
        Serial.print(',');
        Serial.println(data.speed);
      */
    }
  }
}

Возможные доработки


Можно оставить только одну “педаль” – газ. В среднем положении потенциометра – стоп, вперёд и назад – соответственно вперёд и назад. Сделать это очень просто: убираем из кода всё что связано с педалью brake, а педаль газа масштабируем как

throt = map(throt, THROT_MIN, THROT_MAX, -MOTOR_MIN, MOTOR_MAX);
throt = constrain(throt, -MOTOR_MIN, MOTOR_MAX);

И отправляем по радио как

data.speed = throt;

И всё!

Видео


Полезный пример?

Похожие примеры
Подписаться
Уведомить о
7 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии