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

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

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


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

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

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

Электронная часть
Перейдем к электронной части.
Пульт управления





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









Последовательные диоды нужны были для понижения напряжения для полезной нагрузки R8, так как у меня не было подходящего стабилизатора в диапазоне меньше 14,8-5V, Q2 можно убрать если вам он не нужен. R8, R6, R9 это любая полезная нагрузка. Но нужно учитывать мощность транзисторов и напряжение стабилизатора.




Код для основного блока (rx.ino)
#include <SPI.h> // Подключаем библиотеку для работы с SPI
#include <nRF24L01.h> // Подключаем библиотеку для работы с радиомодулем nRF24L01
#include <RF24.h> // Подключаем библиотеку для работы с радиомодулем 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.h> // Подключаем библиотеку для работы с SPI
#include <nRF24L01.h> / Подключаем библиотеку для работы с радиомодулем nRF24L01
#include <RF24.h> / Подключаем библиотеку для работы с радиомодулем 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м в открытой местности. Сильно перегреваются драйвера двигателей, их нужно заменить на более мощные. Периодически протекают деидвуды при заднем ходе. Лучше купить готовые решения. Не хватает оборотов у двигателей, нужны более оборотистые. Плюс нужен рефакторинг всего кода.
Автор:
nielsanderson