Всем привет!
В этой статье я расскажу, как собрать четырехколесную платформу и управлять ей при помощи движения руки.
Посмотреть как это работает Вы можете, кликнув по ссылке.
Для этого нам понадобится:
1) 4 двигателя с редуктором и 4 колеса, например, отсюда
3) Плата Arduino 2 шт, можно использовать 2 Arduino Uno, но я использую Arduino Mega и Iskra Neo (аналог Leonardo)
4) NRF24L01 2 шт
5)Трехосный акселерометр(я использую LIS331DLH)
6) Преобразователь линейный понижающий на базе ams1117-3.3, 3.3V 1A
7) Arduino proto shield
8) Отсек для батареек на 6/8 шт
9) Соединительные провода
Итак, для начала необходимо собрать мобильную платформу по следующей схеме:
Двигатели подключаем по 2 штуки на каждый канал. Двигатели на одном и том же канале должны вращаться в одну сторону. Если это не так, поменяйте местами провода у одного из двигателей. Поворот/разворот осуществляется тракторным/танковым способом.
Порт драйвера | Порт Arduino |
12V | К «+» батарейного отсека/VIN |
GND | К «-» батарейного отсека/GND |
ENA | 9 |
IN1 | 2 |
IN2 | 3 |
IN3 | 4 |
IN4 | 5 |
ENB | 10 |
Преобразователь | Arduino |
Vin | 5V |
GND | GND |
Vout | В питание NRF24L01 |
Подключение NRF24L01
NRF24L01 | Arduino |
1 | GND |
2 | Vout понижающего преобразователя |
3 | 7 |
4 | 8 |
5 | SCK |
6 | MOSI |
7 | MISO |
8 | Не подключаем |
В итоге у меня это выглядит так:
Далее собираем «пульт» для управления нашей машинкой по следующей схеме:
NRF24L01 подключается аналогично, единственное отличие, в том что второй контакт NRF идет в восьмой порт Arduino, а третий – в девятый.
Акселерометр подключается по шине I2c
SDA SDA
SCL SCL
GND GND
5V 5V
Для того, чтобы закрепить акселерометр на руке можно собрать на отдельной плате этот модуль, а arduino закрепить, скажем, на поясе, либо же собрать схему на proto-шилде и крепить к руке вместе с arduino. Я выбрал второй вариант.
И общий вид:
После сборки нам необходимо соотнести положения руки и показания акселерометра. Я использовал следующую схему управления (Ваши показания могут отличаться):
Положение руки | Действия машинки |
Рука горизонтально | Не двигаемся |
Рука Вверх(от 3 до 6)/Вниз (от -6 до -3) по оси X | Движение вперед/назад |
Рука Влево(от -10 до -3)/Вправо от (3 до 10)по оси Y | Движение против/по часовой стрелки |
Далее определим протокол обмена между пультом и платформой:
NRF модуль отправляет трехбайтовый массив
Data[0] – определяет направление движения 1 – вперед, 2 – назад, 0 – стоим.
Data[1] – определяет скорость движения вперед/назад
Data[2] – определяет направление движения по/против часовой стрелки: 0 – не крутимся, 1 – против часовой 2 – по часовой
Исходные коды скетчей и библиотеки (RF24-master, Troyka-IMU-master) можно найти на Github’е либо в конце статьи.
Код для определения показаний акселерометра:
//ТК я работаю с несколькими платами, и у некоторых из них //аппаратный и программный UART, отличаются я использую следующее "переименование" #define SerialPC Serial // библиотека для работы I²C #include <Wire.h> // библиотека для работы с модулями IMU #include "troyka-imu.h" Accelerometer accel; void setup() { // открываем последовательный порт SerialPC.begin(115200); delay(3000); // пока не появились данные с USB // ждём //while (!SerialUSB.available()) {} // выводим сообщение о начале инициализации SerialPC.println("Begin init..."); // инициализация акселерометра accel.begin(); // выводим сообщение об удачной инициализации SerialPC.println("Init completed"); } void loop() { /* !!!ВАЖНО!!! библиотека возвращает значение приблизительно от -10.00 до +10.00 Причем значения типа float(1.0, 1.53 9.87 и так далее) у меня не было необходимости получать значения с сотыми, Поэтому в данном коде используется тип int, дробная часть отбрасывается */ // Считываем значение по оси X и выводим его в Serial int x = accel.readX_G(); SerialPC.print(x); SerialPC.print("\t"); // Считываем значение по оси Y и выводим его в Serial int y = accel.readY_G(); SerialPC.print(y); SerialPC.print("\t"); //Считываем значение по оси Z и выводим его в Serial int z = accel.readZ_G(); SerialPC.print(z); SerialPC.print("\t\t"); SerialPC.println(""); delay(300); }
Код для пульта, все, что относится к Serial используется только для отладки
#define SerialPC Serial //Минимальное и максимальное значения ШИМ для движения вперед/назад #define MIN_PWM 100 #define MAX_PWM 250 // библиотека для работы I²C #include <Wire.h> // библиотека для работы с модулями IMU #include <troyka-imu.h> //Библиотеки для работы с NRF24 #include <SPI.h> #include "RF24.h" //Переменные, определяющие направление движения bool left = false; bool right = false; bool top = false; bool down = false; // создаём объект для работы с акселерометром Accelerometer accel; /* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 9 & 10 */ RF24 radio(9, 10); const uint64_t pipe = 0xF0F1F2F3F4LL; // индитификатор передачи, "труба" //Массив данных byte data[] = {0, 0, 0}; void setup() { //Задержка перед включением пульта //Нужна была для отладки //delay(5000); //инициализация Serial соединения SerialPC.begin(115200); SerialPC.println("setup"); // инициализация акселерометра SerialPC.println("Init accel"); accel.begin(); SerialPC.println("Accel init"); delay(100); //Инициализация NRF24L01 SerialPC.println("Init radio"); radio.begin(); SerialPC.println("radio init"); delay(100); //Установка параметров NRF24L01 SerialPC.println("Set radio param"); // канал (0-127) radio.setChannel(9); // скорость, RF24_250KBPS, RF24_1MBPS или RF24_2MBPS // RF24_250KBPS на nRF24L01 (без +) неработает. // меньше скорость, выше чувствительность приемника. radio.setDataRate(RF24_1MBPS); // мощность передатчика, RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_MED=-6dBM, radio.setPALevel(RF24_PA_HIGH); // открываем трубу на передачу. radio.openWritingPipe(pipe); SerialPC.println("end set radio param"); delay(100); SerialPC.println("end setup"); } //В этих переменных будем хранить значения от акселерометра int x, y, z; //Скорость int speed = 0; void loop() { SerialPC.println("loop"); //Читаем значения от акселерометра SerialPC.print("read x: "); x = accel.readX_G(); SerialPC.println(x); SerialPC.print("read y: "); y = accel.readY_G(); SerialPC.println(y); SerialPC.print("read z: "); z = accel.readZ_G(); Serial.println(z); SerialPC.println("end read."); //Устанавливаем значения направления //и скорости "по умолчанию" //ТЕ нет поворота, нет движения, скорость = 0 //Нет поворота left = false; right = false; //Нет движения down = false; top = false; speed = 0; //Определяем есть ли поворот руки //Поворот направо if ( y > 3) { right = true; }//Поворот налево else if ( y < -3) { left = true; } //Определяем наклоняется ли рука //Рука вниз if ( x <= -3) { down = true; //Приводим значение x в диапазон от -6 до -3, затем //отображаем это значение на диапазон от MIN_PWM до MAX_PWM speed = map(constrain(x, -6, -3), -3, -6, MIN_PWM, MAX_PWM); }//рука вверх else if (x >= 3) { top = true; //Приводим значение x в диапазон от 3 до 6, затем //отображаем это значение на диапазон от MIN_PWM до MAX_PWM speed = map(constrain(x, 3, 6), 3, 6, MIN_PWM, MAX_PWM); } //Заносим значения в массив, который будем отправлять //Направление вперед/назад //конструкция a = (b) ? 1 : 2; равносильна // if (b) { a = 1; } else {a = 2;} data[0] = (top) ? 1 : (down) ? 2 : 0; //Скорость data[1] = speed; //Движение по/против часовой стрелки data[2] = (left) ? 1 : (right) ? 2 : 0; //Выводим значения перед отправкой в Serial //Для отладки for (int i = 0; i < 3; i++) { SerialPC.print(data[i]); SerialPC.print(" "); } SerialPC.println(""); SerialPC.println("clear buff"); //Отчищаем буфер перед отправкой (необходимо на некоторых модулях) radio.flush_tx(); SerialPC.println("sending..."); //Пытаемся отправить if (!radio.write(data, sizeof(data))) { //Если не удалось отправить выводим в Serial сообщение об ошибке SerialPC.println("fail"); } delay(100); SerialPC.println("end loop"); }
Код для мобильной платформы:
//Serial для соединения с ПК #define SerialPC Serial //Время в мс, в течение которого должна придти хотя бы 1 команда #define TIMEOUT 1000 //Библиотеки для работы с NRF24 #include <SPI.h> #include "RF24.h" /* CE и CSN на 7 и 8 пинах */ RF24 radio(7, 8); //Адрес трубы const uint64_t pipe = 0xF0F1F2F3F4LL; //Пины для управления моторами In1, In2, ENA(PWM) const byte motor1[] = {2, 3, 9}; //Пины для управления моторами In3, In4, ENB (PWM) const byte motor2[] = {4, 5, 10}; //Формат передаваемых данных byte data[] = {0, 0, 0}; //Время прихода последней команды unsigned long lastCommand; void setup() { //Выставляем пины на выход for (int i = 0; i < 3; i++) { pinMode(motor1[i], OUTPUT); pinMode(motor2[i], OUTPUT); digitalWrite(motor1[i], LOW); digitalWrite(motor2[i], LOW); } //Инициализация модуля NRF24 radio.begin(); delay(20); //Настройки модуля // канал (0-127) radio.setChannel(9); // скорость, RF24_250KBPS/RF24_1MBPS/RF24_2MBPS. radio.setDataRate(RF24_1MBPS); //мощность передатчика RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_MED=-6dBM, radio.setPALevel(RF24_PA_HIGH); //Открываем трубу на чтение radio.openReadingPipe(1, pipe); //Начинаем слушать эфир radio.startListening(); //устанавливаем время прихода последней команды lastCommand = millis(); } void loop() { //Если время прошедшее с момента последней команды больше чем TIMEOUT, //считаем, что произошел обрыв связи и останавливаем машинку if (millis() - lastCommand > TIMEOUT) { stoped(); } //Если что-то пришло по радио if (radio.available()) { //Читаем данные radio.read(data, sizeof(data)); //Если 1 - едем вперед, если 2 - едем назад, иначе останавливаемся (data[0] == 1) ? forward() : (data[0] == 2) ? back() : stoped(); //Если разворачиваемся 1 - налево, 2 - направо, 0 ничего не меняем if (data[2] == 1) { left(); } else if (data[2] == 2) { right(); } //Фиксируем время прихода команды lastCommand = millis(); } } //Останавливаемся и блокируем моторы void stoped() { setMotorParam(false, false, false, false, 0, 0); } void forward() { setMotorParam(true, false, true, false, data[1], data[1]); } void back() { setMotorParam(false, true, false, true, data[1], data[1]); } void left() { setMotorParam(true, false, false, true, 255, 255); } void right() { setMotorParam(false, true, true, false, 255, 255); } /* d11 - EN1, d12 - EN2 * d21 - EN3, d22 - EN4 * s1 - ENA, s2 - ENB */ void setMotorParam(bool d11, bool d12, bool d21, bool d22, int s1, int s2) { //Устанавливаем направление движения моторов digitalWrite(motor1[0], d11); digitalWrite(motor1[1], d12); digitalWrite(motor2[0], d21); digitalWrite(motor2[1], d22); //Устанавливаем скорость вращения моторов analogWrite(motor1[2], s1); analogWrite(motor2[2], s2); }