Прикормочный кораблик на arduino

Сборка прикормочного кораблика на радиоуправлении начиналась в рамках моего первого студенческого проекта на arduino. Я жил далеко от городской суеты, поэтому приходилось в основном использовать только те компоненты, которые были на руках. Задача была проста — создать кораблик, который сможет разносить корм для рыбы с полезной нагрузкой около двух килограмм. Что бы достичь своих целей я должен был решить список следующих задач:

  • Сделать корпус и определить габариты.

  • Сделать движители.

  • Выбрать двигатель.

  • Обеспечить радиоуправление.

  • Обеспечить автономность питания.

  • Сборка всего этого.

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

Структурная схема

Структурная схема

Механическая часть

Для изготовления корпуса скачал себе около десяти книг по судостроению и судомоделированию и начал их изучать. В итоге понял, что технический не смогу сделать те вещи, которые были написаны в этих книгах. Для изготовления шпангоутов нужен был ЧПУ фрезер или 3D принтер. Их, к сожалению, у меня не было. Руками все это делать из дерева — то же нелегкая задача. В конце остановился на варианте с катамараном,  так как в интернете нашел много аналогов. Решил все это сделать из подручных средств. Искал что-то обтекаемое,  нетяжелое и нашел решение у себя в гараже. Да,  это были канистры из под масло и чемоданчик от набора инструментов. Поклеил их термо клеим и для надежности усилил деревом и фиксировал саморезом. Покрасил в разные цвета чтобы видно было издалека.

Корпус кораблика

Корпус кораблика

Далее были неудачные попытки расчета гидродинамических характеристик на FlowVision CFD. В итоге остановился на этапе 3D моделирования =)

3D модель на Компас

3D модель на Компас

Сгенирированный чертеж на Компас

Сгенирированный чертеж на Компас

Движитель выбрал основываясь на показателе КПД и самым лучшим в этом плане,  конечно же,  был подводный винт. Винт состоит из ступицы и радиально закреплённых к нему лопастей. Обычно используется 3–4 лопастные винты. Все размеры винтов были рассчитаны по книге «Юный моделист-кораблестроитель». Винт был изготовлен из стальной пластины,  дейдвут из трубы пвх,  а внутренность залита обычным солидолом.

Самодельный движитель

Самодельный движитель

С двигателем аналогичная ситуация. Бесколлекторных у меня не было, до и с Китая они шли очень долго. Нашел где-то и установил коллекторные 20 ваттные двигатели от печки ВАЗ МЭ 255.

МЭ 255

МЭ 255

Для движения механизма сброса корма использовал двухпроводной электропривод Starline SL-2 от замка машины.

Starline SL-2

Starline SL-2

Электронная часть

Перейдем к электронной части. Мозгом всего этого был выбран китайский Arduino Nano 3.0 на базе Atmega328P так как его вычислительных мощностей хватало с запасов и прототипировать на нем то же легко. Далее искал в интернете похожие проекты для разных задач и в youtube нашел интересный проект блока управления часть кода и схем заимствовал оттуда и адаптировал под свои задачи. Радиомодуль использовал NRF24L01+PA+LNA. В открытой местности с ним удалось отправлять пакеты на 700 м. Драйвером электродвигателя использовался L298N правда он сильно грелся из-за того что не рассчитан на эти моторы (лучше брать с запасом), пришлось усилить их радиаторы радиаторами из старых телевизоров. Пульт управления работал на той же связке плюс 2 модуля KY-023.

Пульт управления

Функциональная схема пульта управления

Функциональная схема пульта управления

Принципиальная схема пульта управления(после доработки аккумулятор заменен на 7.4 V и добавлен диод Шоттки для защиты от переполюсовки)

Принципиальная схема пульта управления (после доработки аккумулятор заменен на 7.4 V и добавлен диод Шоттки для защиты от переполюсовки)

Макет пульта управления

Макет пульта управления

Сборка пульта управления

Сборка пульта управления

Готовый пульт после сборки

Готовый пульт после сборки

Основной блок

Функциональная схема основного блока

Функциональная схема основного блока

Arduino и радиомодуль

Arduino и радиомодуль

Arduino и драйвера двигателей

Arduino и драйвера двигателей

Макет с одним мотором для теста

Макет с одним мотором для теста

Arduino и радиомодуль(плюс дополнительный драйвер для мелких моторов при необходимости)

Arduino и радиомодуль (плюс дополнительный драйвер для мелких моторов при необходимости)

Принципиальная схема платы управления механизма сброса корма

Принципиальная схема платы управления механизма сброса корма

Макет платы механизма сброса корма

Макет платы механизма сброса корма

Плата после навесного монтажа

Плата после навесного монтажа

Принципиальная схема для платы дополнительных нагрузок(последовательные диоды нужны были для понижения напряжения, их можно убрать)

Принципиальная схема для платы дополнительных нагрузок (последовательные диоды нужны были для понижения напряжения, их можно убрать)

Плата для дополнительных нагрузок

Плата для дополнительных нагрузок

Усиление радиаторов

Усиление радиаторов

Установка двигателей. Соединение аккумуляторов. Установка двигателя для механизма сброса. Бункер для корма.

Установка двигателей. Соединение аккумуляторов. Установка двигателя для механизма сброса. Бункер для корма.

Винт и защита для винта. Механизм сброса. Установка электроники.

Винт и защита для винта. Механизм сброса. Установка электроники.

Код для основного блока (rx.ino)

#include  // Подключаем библиотеку для работы с SPI
#include  // Подключаем библиотеку для работы с радиомодулем nRF24L01
#include  // Подключаем библиотеку для работы с радиомодулем nRF24L01

const uint64_t pipe = 0xE8E8F0F0E1LL; // Указываем адрес конфигурации радиомодуля для обмена данными
RF24 radio(2, 9); // Инициализируем объект радиомодуля на пинах 2 и 9

int data[4]; // Создаем массив для хранения полученных данных
int reserve=0; // Инициализируем переменную для хранения резервной кнопки, использовать при необходимости
unsigned long motorOnTime; // Инициализируем переменную для хранения времени включения двигателя
byte count = 0; // Инициализируем переменную счетчик для механизма сброса

// В функции setup инициализируются различные пины как входы или выходы, устанавливается канал радио, скорость передачи данных и мощность.
void setup() {
  delay(50); // Небольшая задержка перед началом работы
  radio.begin(); // начало работы с радиомодулем
  radio.setChannel(9); // установка радиоканала
  radio.setDataRate(RF24_250KBPS);        // Установка минимальной скорости;
  radio.setPALevel(RF24_PA_HIGH);         // Установка максимальной мощности;
  radio.openReadingPipe(1,pipe);  // Открытие канала для чтения данных
  radio.startListening(); // Начало прослушивания канала
  pinMode(10, OUTPUT); // Установка пина 10 на вывод
  digitalWrite(10, LOW); // Установка пина 10 в низкий уровень
  pinMode(17, OUTPUT); // Установка пина 17 на вывод
  pinMode(18, OUTPUT); // Установка пина 18 на вывод
  pinMode(19, OUTPUT); // Установка пина 19 на вывод
  pinMode(16, OUTPUT); // Установка пина 16 на вывод
  digitalWrite(16, LOW); // Установка пина 16 в низкий уровень
}

void loop()  {
  if ( radio.available() ){ // Если доступны новые данные от радиомодуля
    bool done = false;
    while (!done){ // Читаем данные, пока все данные не будут прочитаны
      done = radio.read(data, sizeof(data)); // Считываем данные в массив data размером sizeof(data)
      // Проверка полученных данных от левого джойстика (data[0]), управление левым двигателем
      if(data[0]>450 && data[0]<598){ // Если значение элемента массива в заданном диапазоне
        analogWrite(5, 0); // Отключение левого двигателя
        analogWrite(6, 0); // Отключение левого двигателя
        digitalWrite(17,LOW); // Установка пина 17 в низкий уровень
        digitalWrite(10,LOW); // Установка пина 10 в низкий уровень
        digitalWrite(18,LOW); // Установка пина 18 в низкий уровень
        digitalWrite(19,LOW); // Установка пина 19 в низкий уровень
      }
      if(data[0]>598)
      {   
        analogWrite(5,255); // Включение левого двигателя
        analogWrite(6,255); // Включение левого двигателя
        digitalWrite(17,LOW); // Установка пина 17 в низкий уровень
        digitalWrite(10,HIGH); // Установка пина 10 в верхний уровень, направление движения вперед 
        digitalWrite(18,HIGH); // Установка пина 10 в верхний уровень, направление движения вперед
        digitalWrite(19,LOW); // Установка пина 19 в низкий уровень
      }
      if(data[0]< 450)
      {   
        analogWrite(5,255); // Включение левого двигателя
        analogWrite(6,255); // Включение левого двигателя
        digitalWrite(17,HIGH); // Установка пина 17 в верхний уровень, направление движения назад 
        digitalWrite(10,LOW); // Установка пина 10 в низкий уровень
        digitalWrite(18,LOW); // Установка пина 18 в низкий уровень
        digitalWrite(19,HIGH); // Установка пина 19 в верхний уровень, направление движения назад
      }  
      // Проверка полученных данных от правого джойстика (data[1]), управление правым двигателем
      if(data[1]>450 && data[1]<598){ // Если значение первого элемента массива в заданном диапазоне
        digitalWrite(3, LOW); // Отключение правого двигателя
        digitalWrite(8, LOW); // Отключение правого двигателя
        digitalWrite(4,LOW); // Установка пина 4 в низкий уровень
        digitalWrite(7,LOW); // Установка пина 7 в низкий уровень
        digitalWrite(14,LOW); // Установка пина 14 в низкий уровень
        digitalWrite(15,LOW); // Установка пина 15 в низкий уровень
      }
      if(data[1]>598)
      {   
        digitalWrite(3, HIGH); // Включение правого двигателя
        digitalWrite(8,HIGH); // Включение правого двигателя
        digitalWrite(4,LOW); // Установка пина 4 в низкий уровень
        digitalWrite(7,HIGH); // Установка пина 7 в верхний уровень, направление движения вперед 
        digitalWrite(14,HIGH); // Установка пина 14 в верхний уровень, направление движения вперед 
        digitalWrite(15,LOW); // Установка пина 15 в низкий уровень
      }
      if(data[1]< 450)
      {   
        digitalWrite(3,HIGH); // Включение правого двигателя
        digitalWrite(8,HIGH); // Включение правого двигателя
        digitalWrite(4,HIGH); // Установка пина 4 в верхний уровень, направление движения назад
        digitalWrite(7,LOW); // Установка пина 7 в низкий уровень
        digitalWrite(14,LOW); // Установка пина 14 в низкий уровень
        digitalWrite(15,HIGH); // Установка пина 15 в верхний уровень, направление движения назад
      }  
      // Проверка полученных данных от кнопки правого джойстика (data[2]), управление механизмом сброса
      if(data[2] == 1){ // Если равно 0
         if(count < 1){ // Счетчик меньше единицы
            digitalWrite(16, HIGH); // Замыкаем реле запускаем механизм сброса
            delay(500); Ждем 500 миллисекунд
            digitalWrite(16, LOW); // Отключаем
            count++; // Увеличиваем счетчик на единицу 
         }
      }
      else{
        if(count >= 1){ // Если счетчик равно или больше единицы
            digitalWrite(16, HIGH);  // Замыкаем реле запускаем механизм сброса
            delay(500); Ждем 500 миллисекунд
            digitalWrite(16, LOW); // Отключаем
            count--; // Уменьшаем счетчик на единицу 
         }
      }
      reserve = !data[3]; // Резервная кнопка правого джойстика для свободного пользования
    }
  }
}

Код для пульта управления (tx.ino)

#include  // Подключаем библиотеку для работы с SPI
#include  / Подключаем библиотеку для работы с радиомодулем nRF24L01
#include  / Подключаем библиотеку для работы с радиомодулем nRF24L01

const uint64_t pipe = 0xE8E8F0F0E1LL; // Указываем адрес конфигурации радиомодуля для обмена данными
RF24 radio(9,10);// vмодуль на пинах 9 и 10 // Инициализируем объект радиомодуля на пинах 2 и 9

byte pinLeftJoystickX = 14; // Указываем номер пина, на который подключен левый джойстик X
byte pinRightJoystickX = 15; // Указываем номер пина, на который подключен правый джойстик X
byte pinLeftJoystickSwitch = 4; // Указываем номер пина, на который подключена кнопка левого джойстика
byte pinRightJoystickSwitch = 3; // Указываем номер пина, на который подключена кнопка правого джойстика

boolean leftJoystickSwitch = 0; // Объявляем переменную для хранения состояния кнопки левого джойстика
boolean rightJoystickSwitch = 0; // Объявляем переменную для хранения состояния кнопки правого джойстика

boolean leftJoystickSwitchState; // Объявляем переменную для хранения текущего состояния кнопки левого джойстика
boolean rightJoystickSwitchState; // Объявляем переменную для хранения текущего состояния кнопки правого джойстика

int transmitData[4]; // Массив, хранящий передаваемые данные
int latestData[4]; // Массив, хранящий последние переданные данные
boolean flag = 0; // Флаг, указывающий на необходимость отправки данных по радио
boolean leftJoystickSwitchFlag = 0; // Флаг, указывающий на изменение состояния кнопки левого джойстика
boolean rightJoystickSwitchFlag = 0; // Флаг, указывающий на изменение состояния кнопки правого джойстика

unsigned long lastPressLeftJoystickSwitch; // Переменная, хранящая время последнего нажатия кнопки левого джойстика
unsigned long lastPressRightJoystickSwitch; // Переменная, хранящая время последнего нажатия кнопки правого джойстика

void setup() {
  pinMode(pinLeftJoystickSwitch, INPUT_PULLUP); // Устанавливаем пин, на который подключена кнопка левого джойстика, в режим входа с подтяжкой к питанию
  pinMode(pinRightJoystickSwitch, INPUT_PULLUP); // Устанавливаем пин, на который подключена кнопка правого джойстика, в режим входа с подтяжкой к питанию
 
  radio.begin(); // Активировать модуль

  radio.openWritingPipe(pipe);   // Мы - труба 0, открываем канал для передачи данных
  radio.setChannel(9);  // Выбираем канал (в котором нет шумов!)

  radio.setPALevel (RF24_PA_MAX); // Уровень мощности передатчика. На выбор RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
  radio.setDataRate (RF24_250KBPS); // Скорость обмена. На выбор RF24_2MBPS, RF24_1MBPS, RF24_250KBPS
  // Должна быть одинакова на приёмнике и передатчике!
  // При самой низкой скорости имеем самую высокую чувствительность и дальность!!

  radio.powerUp(); // Начать работу
  radio.stopListening(); // Не слушаем радиоэфир, мы передатчик
}

void loop() {
  leftJoystickSwitchState = !digitalRead(pinLeftJoystickSwitch); // Считать состояние переключателя левого джойстика
  rightJoystickSwitchState = !digitalRead(pinRightJoystickSwitch); // Считать состояние переключателя правого джойстика

  if(leftJoystickSwitchState == 1 && leftJoystickSwitchFlag == 0 && millis() - lastPressLeftJoystickSwitch > 50){ // Если нажат переключатель левого джойстика и флаг еще не поднят, и прошло более 50 миллисекунд после последнего нажатия
    leftJoystickSwitchFlag = 1;  // Поднять флаг переключения левого джойстика
    leftJoystickSwitch = !leftJoystickSwitch; // Поменять состояние переключателя левого джойстика
    lastPressLeftJoystickSwitch = millis(); / Запомнить время последнего нажатия переключателя левого джойстика
  }
  if(leftJoystickSwitchState == 0 && leftJoystickSwitchFlag == 1){ // Если переключатель левого джойстика отпущен и флаг поднят
    leftJoystickSwitchFlag = 0; // Опустить флаг переключения левого джойстика
  }
  if(rightJoystickSwitchState == 1 && rightJoystickSwitchFlag == 0 && millis() - lastPressRightJoystickSwitch > 50){ // Если нажат переключатель правого джойстика и флаг еще не поднят, и прошло более 50 миллисекунд после последнего нажатия
    rightJoystickSwitchFlag = 1; // Поднять флаг переключения правого джойстика
    rightJoystickSwitch = !rightJoystickSwitch; // Поменять состояние переключателя правого джойстика
    lastPressRightJoystickSwitch = millis(); // Запомнить время последнего нажатия переключателя правого джойстика
  }
  if(rightJoystickSwitchState == 0 && rightJoystickSwitchFlag == 1){ // Если переключатель правого джойстика отпущен и флаг поднят
    rightJoystickSwitchFlag = 0; // Опустить флаг переключения правого джойстика
  }
  
  transmitData[0] = analogRead(pinLeftJoystickX); // Записать данные с левого джойстика в массив для передачи
  transmitData[1] = analogRead(pinRightJoystickX); // Записать данные с правого джойстика в массив для передачи
  transmitData[2] = leftJoystickSwitch; // Записать состояние переключателя левого джойстика в массив для передачи
  transmitData[3] = rightJoystickSwitch;  // Записать состояние переключателя правого джойстика

  for (int i = 0; i < 4; i++) { // В цикле от 0 до числа каналов
    if (transmitData[i] != latestData[i]) { // Если есть изменения в transmit_data
      flag = 1; // Поднять флаг отправки по радио
      latestData[i] = transmitData[i]; // Запомнить последнее изменение
    }
  }

  if (flag == 1) {
    radio.powerUp(); // Включить передатчик
    radio.write(&transmitData, sizeof(transmitData)); // Отправить по радио
    flag = 0; // Опустить флаг
    radio.powerDown(); // Выключить передатчик
  }
}

Первые испытания

Первые испытания

В заключении тесты показали,  что кораблик развивает скорость 1 м/c с нагрузкой в тихую погоду. Радиоуправление работает до 700 м в открытой местности. Сильно перегреваются драйвера двигателей,  их нужно заменить на более мощные. Периодический протекают деидвуды при заднем ходе. Лучше купить готовые решения. Не хватает оборотов у двигателей, нужны более оборотистые. Плюс нужен рефакторинг всего кода.

© Habrahabr.ru