Добрый день, друзья! Эта статья посвящена электронной поделке, которую давно хотелось реализовать — и вот дошли руки. Идея этой поделки проста — сделать маленькую четырехколесную машинку, на ней стоит робо-рука и их движениями можно управлять с телефона или планшета через wifi.
Компоненты, которые было решено использовать для этой задачи следующие:
1) корпус — пластиковые детали, напечатаные на 3D принтере+картон;
2) робо-рука — робот-манипулятор MeArm;
3) электроника — плата NodeMCU, плата ArduinoNano, желтые мотор-редукторы для Arduino, драйвер моторов на основе микросхемы L298N, шесть батареек типа AA на 1.2 В, кнопка включения-выключения.
Давайте все по-порядку.
Корпус
Тележка имеет очень простое строение — кусок картона, с прикрепленными к нему по краям пластиковыми деталями, к которым присоединяются два подшипника с передней стороны и два мотора с задней. К моторам и подшипникам прикручиваются колеса.
Модель тележки была разработана в программе трехмерного моделирования — Blender3D.
Затем все необходимые пластиковые детали были напечатаны на 3D принтере.
Сначала из куска картона (пятислойного, профиль В), вырезаем прямоугольник, размером 140/180 мм.
Затем из напечатанных на 3D принтере деталей нужно собрать такую конструкцию:
То есть в каждое из 4х пластиковых колес вставляется диск, у которого с одной стороны - отверстие для винтика (диаметром 3мм), которым колесо будет прикручиваться к пластиковой детали-переходнику, с другой стороны - отверстие в форме шестигранника.
Пластиковые детали-переходники, в свою очередь имеют форму шестигранника с одной стороны, и форму цилиндра с другой, а также внутри отверстие диаметром 3мм. Концом-шестигранником деталь вставляется в диск колеса и с обратной стороны диска прикручивается винтиком.
Эти детали-переходники для передних и задних колес немного отличаются по форме и размерам. Задние колеса присоединяются к моторам, поэтому на том конце, который имеет форму цилиндра есть углубление по форме повторяющее вал мотора. То есть эта деталь нанизывается на вал с этого конца. И по размерам, вся эта деталь поменьше. Деталь для передних колес цилиндрической частью вставляется в подшипник (диаметр внешний — 22 мм, толщина 7 мм, диаметр внутреннего отверстия — 8 мм).
Подшипник вставлен в дополнительную пластиковую деталь, которая поддерживает его и прикрепляется к тележке.
Колеса крепятся к двум пластиковым деталям имеющим горизонтальные пластинки с отверстиями для винтиков, с помощью которых они прикручиваются справа и слева к куску картона.
От них вниз отходят вертикальными пластины, спроектированные так, чтобы к задним двум прикручивался мотор, а к двум передним пластиковая деталь, в которую вставляется подшипник передних колес.
Таким образом, размер всей тележки получается 140/180/75 мм.
Все эти детали печатаются и собираются вместе.
Моторы
Два задних колеса тележки присоединяются к моторам-редукторам для Arduino.
У них есть возможность реверсивного вращения, то есть и в одну и в другую сторону. Просто подключив моторы к плате Arduino или NodeMCU мы можем запрограммировать их на вращение либо в одну сторону, либо в другую, в зависимости от подключения контактов, но реверсивное вращение обеспечить не сможем. Причем NodeMCU выдает только 3.3В — этого не хватит, чтобы запустить моторы. От Arduino, выдающей 5В — моторы можно запустить, но лучше этого не делать, так как в моменты запуска и остановки моторов могут происходить броски тока, превышающие 5B.
Поэтому, для подключения моторов и управления ими нужен дополнительный модуль — драйвер, который называется H-мостом. С его помощью можно обеспечить реверсивное вращение и питание моторов. В проектах для Arduino часто используется драйвер для мотора на основе микросхемы L298N.
Вот схема ее контактов:
Что мы видим на этой плате:
1) Клемма мотора A (OUT1 и OUT2) — к этим клеммам подключается первый мотор-редуктор.
2) Клемма мотора В (OUT3 и OUT4) — к этим клеммам подключается второй мотор-редуктор.
3) Питание моторов (VSS) — вход для подключения внешнего источника питания (максимально допустимое напряжение +35 В), в нашем случае это блок батареек на 4.8 В.
4) Земля (GND) — общий провод, к которому подключается отрицательный полюс внешнего источника питания, а также нужно соединить его с GND контактом платы NodeMCU или Arduino, смотря какая из них выбрана для управления моторами. У нас управление моторов будет осуществляться через плату NodeMCU, а управление робо-рукой через Arduino.
5) Питание логики (Vs) (+5V). Через эту клемму осуществляется питание микросхемы L298N. То есть этот контакт нужно присоединить к контакту +5В платы Arduino. Но если в схеме используется плата NodeMCU, то она выдает только 3.3В — этого не хватает для питания логики L298N. В этом случае есть способ питания логики от стабилизатора напряжения, который располагается на плате драйвера. Он выдает 5В. Тогда клемма Vs никуда не подключается, а подается только ток на клемму VSS, но при этом должна быть установлена перемычка питания от стабилизатора, которая обозначена на картинке выше. Питание на плату NodeMCU при этом должно подаваться не более 12В от внешнего источника, подключив его «+» полюс к контакту VIN.
6) Управление мотором A (IN1, IN2) — контакты, управляющие направлениями вращения первого мотора. Подключаются к цифровым контактом управляющей платы (NodeMCU или Arduino). Напряжение на одном вращает мотор в одну сторону, на другом в другую.
7) Управление скоростью мотора А (ENA). Подключается к аналоговым контактам управляющей платы и скорость задается цифрой от 0 до 255.
8) Управление мотором В (IN3, IN4), управление скоростью мотора В (ENВ) — то же самое, но для второго мотора.
Схема подключения моторов через драйвер к плате NodeMCU:
В нашем роботе эта схема выглядит иначе. У нас была отдельно микросхема L298N, которую отковыряли из какого-то прибора. С ее помощью мы спаяли свой вариант драйвера моторов.
Этот драйвер отличается от рассмотренного тем, что в нем отсутствует микросхема 78M05 (стабилизатор напряжения). Роль этого стабилизатора в нашей схеме выполняет плата ArduinoNano. Она подключается к драйверу (VIN и GND), тем самым запитывается энергией и 5В от Arduino подается на питание логики драйвера. В остальном то же самое.
Вот наша схема:
ArduinoNano
Платы NodeMCU и ArduinoNano предназначены для управления различными электронными схемами.
ArduinoNano построена на микроконтроллере ATmega328.
На ней есть 14 цифровых контактов и 8 аналоговых.
Программируется эта плата при помощи ArduinoIDE — среды разработки программ под Arduino, которую можно скачать с официального сайта и установить. Она содержит в себе список всех возможных видов плат Arduino, какие бывают, так что для программирования ArduinoNano не нужно устанавливать дополнительно никаких библиотек. Нужно только подключить плату к компьютеру через MiniUSB кабель, запустить среду разработки, найти в вписке плат «Инструменты → Плата → Arduino Nano».
Также нужно указать последовательный порт «Инструменты → Порт», корорый появится в списке после присоединения платы к компьютеру. И плата готова к программированию.
На данном этапе, в вышеприведенных схемах плата Arduino ничего не делает. В первой схеме с драйвером L298N она вообще отсутствует так как не нужна, в схеме с самодельным драйвером она выполняет роль стабилизатора напряжения и включена в схему, чтобы питать логику микросхемы L298N.
Предлагаю сначала запустить машинку и сделать так, чтобы ее движения управлялись с телефона, а вторым этапом, если все получится, приделать робо-руку. Робо рука будет управляться через плату Arduino, поэтому мы резервируем для нее место в схеме, но пока что она никакой код выполнять не будет. А плата NodeMCU будет управлять движением моторов, так что запрограммируем сейчас ее.
NodeMCU
Плата NodeMCU основана на микросхеме ESP8266MOD, содержащей встроенный Wifi модуль. Благодаря этому факту выбор и пал на нее. Она обеспечивает возможность посылать сигналы в нашу схему по Wifi через программу на телефоне или планшете.
Немного о Wifi подключениях...
Устройства, которые подключаются к сетям Wi-Fi, называются станциями (STA). Подключение к Wi-Fi обеспечивается точкой доступа (AP), которая выступает в качестве концентратора для одной или нескольких станций. Точка доступа вашей домашней сети на другом конце подключена к проводной сети. Она обычно интегрируется с маршрутизатором для обеспечения доступа из сети Wi-Fi к Интернету. Каждая точка доступа распознается по SSID (Service Set IDentifier), который по сути является именем сети, которую вы выбираете при подключении устройства (станции) к Wi-Fi.
Модули ESP8266 могут работать как станции, поэтому мы можем подключить их к сети Wi-Fi. Он также может работать как программная точка доступа (soft-AP) для создания собственной сети Wi-Fi. Когда модуль ESP8266 работает как мягкая точка доступа (программная точка доступа), мы можем подключить другие станции к модулю ESP8266.
Вот эту возможность мы и используем в программе. Наша плата NodeMCU при включении будет создавать точку доступа Wifi с возможностью подключения к ней станций (телефонов, планшетов, компьютеров, ноутбуков). Но количество подключений ограничим одним. Мы же не хотим, чтобы к роботу подключились три человека и одновременно начали вращать колесами?!
После создания точки доступа Wifi нужно с помощью специальной библиотеки кода создать веб-сервер на этой точке доступа и программным путем сгенерировать html страницу, отображающую кнопки управления моторами. Устройство-клиент подключается к данному Wifi, затем с этого устройства заходим на сервер по специальному IP адресу (по умолчанию это 192.168.4.1) и эта html страница c отображается. Нажатие на кнопки на этой странице передает информацию в программу, прошитую в NodeMCU и плата меняет состояние контактов, управляющих вращением моторов.
Чтобы запрограммировать плату NodeMCU, нужно установить дополнительную библиотеку — ESP8266WiFi. Она предоставляет широкий набор методов (функций) и свойств C++ для настройки и работы модуля ESP8266 в режиме станции и / или программной точки доступа. NodeMCU
появится в списке среды разработки ArduinoIDE только после установки этой библиотеки.
О подробностях установки можно узнать на сайте http://arduino.esp8266.com
На этом ресурсе дается ссылка на GitHub:
http://github.com/esp8266/Arduino
Там написано как осуществляется установка библиотеки при помощи Менеджера плат.
1. Нужно установить текущую интегрированную среду разработки Arduino версии 1.8.9 или выше. Текущая версия находится на сайте Arduino.
2. Запустить Arduino и открыть окно настроек «Файл → Настройки».
Ввести: «https://arduino.esp8266.com/stable/package_esp8266com_index.json» в поле URL-адреса диспетчера дополнительных плат.
3. Открыть Менеджер плат из меню «Инструменты → Плата».
В поле поиска нужно вбить esp8266, при этом в списке должен появиться этот модуль.
Затем нужно нажать кнопку «Установка». Какое-то время будет происходить установка библиотеки.
После этого можно подключать плату NodeMCU через MicroUSB к компьютеру, в среде программирования Arduino выбираем порт «Инструменты → Порт» и выбираем плату «NodeMCU 1.0 (ESP-12E Module)», которая появится в списке «Инструменты → Плата».
Плата готова к программированию.
Заливаем в нее такой код (к статье прилагается файл с кодом «sketch_RobokarForNodeMCU.ino»):
/*Программа для платы NodeMCU. К плате подключаются два мотор-редуктора, вращением которых управляют контакты D0-D3. NodeMCU создает программную точку доступа Wifi и запускает сервер, генерит html страницу на этом сервере, содержащую кнопки управления моторами. Через телефон или планшет происходит подключение по Wifi к плате и через эту страницу на сервере управляются моторы*/ #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> //контакты, управляющие вращением моторов #define pin_Moror1_front 16 //D0 NodeMCU #define pin_Moror1_back 5 //D1 #define pin_Moror2_front 4 //D2 #define pin_Moror2_back 0 //D3 //название и пароль создаваемой программной точки доступа Wifi const char* ssid = "ESP8266Net"; const char* password = "ESP12345"; //Переменная строка, указывающая какая кнопка нажата в данный момент String CurentButtonActive = "stop"; // Устанавливаем свой веб сервер на порт 80 ESP8266WebServer server(80); /* По-умолчанию сервер доступен по IP http://192.168.4.1 */ void setup() { delay(100); /*Устанавливаем параметры контактов - выходный и выключены*/ pinMode(pin_Moror1_front, OUTPUT); pinMode(pin_Moror1_back, OUTPUT); pinMode(pin_Moror2_front, OUTPUT); pinMode(pin_Moror2_back, OUTPUT); digitalWrite(pin_Moror1_front, LOW); digitalWrite(pin_Moror1_back, LOW); digitalWrite(pin_Moror2_front, LOW); digitalWrite(pin_Moror2_back, LOW); /*Отладочный код нужен, чтобы смотреть надписи, которые выводятся на порт ввода-вывода Открываем последовательное соединение через порт со скоростью передачи данных 115200 И выводим надпись. Раскомментировать при необходимости*/ /*Serial.begin(115200); Serial.println(); Serial.print("Configuring access point...");*/ /* Создаем мягкую точку доступа Wifi. WiFi.softAP(ssid, password, channel, hidden, max_connection) ssid - строка, содержащая сетевоt имя Wifi (макс. 31 символ) password - необязательная символьная строка с паролем. channel - необязательный параметр для установки канала Wi-Fi, от 1 до 13. Канал по умолчанию = 1. hidden - необязательный параметр, если установлено значение true, будет скрывать SSID. max_connection - необязательный параметр для установки макс. одновременных подключенных станций, от 0 до 8. По умолчанию 4. После достижения максимального числа любая другая станция, |
Коробка для электроники
После того, как программа зашита в NodeMCU, настало время соединить все детали в схему, описанную выше и разместить ее на тележке.
Для удобного размещения электроники я использовала такую макетную плату.
К ней припаяны разъемы, в которые втыкаются контакты плат Arduino и NodeMCU.
Так что можно собрать всю схему и воткнуть нужные провода в разъемы рядом с контактами платы. Платы, таким образом, свободно можно вытаскивать и прошивать в них программы без необходимости постоянно подключать и отключать провода от их контактов.
Вот вся наша электроника в собранном по схеме выше виде:
Теперь нужно как-то аккуратно и красиво разместить ее на тележке.
Для этого из куска картона делаем коробочку с нужными размерами:
140/110/60.
Вот что получается в результате.
Включаем механизм...
Берем мобильный телефон и находим в нем сеть Wifi «ESP8266Net». Коннектимся к ней, указав пароль «ESP12345».
Открываем в телефоне броузер и в строке адреса набираем номер IP: «192.168.4.1».
Открывается такая страница:
Если при нажатии на кнопки машинка реагирует и двигается в нужную сторону — все правильно и можно переходить к установке робо-руки.
Робот-манипулятор MeArm
Это довольно известный робот-манипулятор, который можно купить в магазинах робототехники, радиорынках, Алиэкспрессе и т. д.
Он представляет из себя конструктор из деталей, которые соединяются между собой винтиками по схеме. Им управляют четыре сервомотора «Tower Pro Mikro Servo 9G SG90».
Один мотор осуществляет круговое вращение, второй — движение вверх-вниз, третий — вперед-назад, четвёртый — открывает-закрывает клешню.
От каждого из них отходят три провода.
Красный — питание.
Коричневый — земля.
Желтый — сигнальный или информационный провод, по которому подаются команды на какой угол совершить оборот двигателю, а значит должы присоединяться к аналоговым контактам.
Питаются моторы от 5В.
Таким образом, теоретически можно их запитать от контакта Arduino +5V. Но на практике этого делать не рекомендуется, потому, что при включении и выключении Arduino могут быть резкие скачки напряжения, а также они наблюдаются если моторы дошли до максимального или минимального угла поворота, а программа подает сигнал вращаться. Чтобы избежать перегорания моторов подключим их к батарейке через стабилизатор.
Мы его сами спаяли. Это стабилизатор LM7805. У него 3 контакта. На первый подаем напряжение 7.2В от шести батареек, 2 — GND, 3 — выходной контакт, дающий 5 В, от которого питаем все 4 мотора роборуки. Между 1 и 2 контактом припаян конденсатор на 47мФ, между 2 и 3 на 100мФ.
Теперь нужно проверить работу всех моторов роборуки, прежде чем включать ее в схему с тележкой.
Для этого мы берем только плату Arduino, роборуку MeArm, блок батареек на 7.2 В и описанный выше стабилизатор.
Собираем с ними такую схему:
Заливаем в Arduino программу, по которой роборука по-очереди вращает все моторы. Сначала вращается в одну сторону, потом в другую, потом поднимается вверх, опускается вниз, потом выдвигается вперед-назад, открывает клешню и закрывает (к статье прилагается файл с кодом «sketch_MeArmServoTest.ino»):
/*Программа для робота-манипулятора MeArm. Получает данные от четырёх аналоговых контактов платы Arduino. Каждый из этих контактов задает вращение сервомоторов, управляющих роборукой.*/ //подключаем библиотеку servo #include <Servo.h> /*определяем ключевые слова (INI — начальное положение мотора, MAX — максимальное положение мотора, MIN — минимальное положение мотора, CUR — текущее положение мотора), которые будут использоваться как индексы для доступа к цифрам, своим для каждого мотора*/ #define INI 0 #define MAX 1 #define MIN 2 #define CUR 3 /*константные глобальные переменные хранящие время задержки в мс*/ const int delay_move = 50; const int delay_setup = 100; const int motor_step = 1; /*Структура, хранящая всю необходимую информацию о моторе*/ struct SERVO_MOTOR{ //объект мотора servo Servo servo_obj; /*массив целочисленных переменных, у которого 0й элемент (соответствует INI) — это начальное положение мотора, 1й (MAX) — максимально возможное, 2й (MIN) — минимально возможное, 3й (CUR) — текущее положение.*/ int positions_data[4] = {0,0,0,0}; }; //создаём четыре объекта структуры SERVO_MOTOR SERVO_MOTOR servo_round, servo_up, servo_front, servo_hand; /*функция InitServoObjects содержит начальную инициализацию всех четырёх моторов*/ void InitServoObjects() { //Инициируем объект servo_round servo_round.positions_data[INI] = 90; //начальный угол поворота servo_round.positions_data[MAX] = 170; //максимальный угол поворота servo_round.positions_data[MIN] = 30; //минимальный угол поворот servo_round.positions_data[CUR] = servo_round.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino servo_round.servo_obj.attach(A0); //устанавливаем мотор на начальный угол поворота servo_round.servo_obj.write(servo_round.positions_data[INI]); //задержка delay(delay_setup); //Инициируем объект servo_up servo_up.positions_data[INI] = 110; //начальный угол поворота servo_up.positions_data[MAX] = 160; //максимальный угол поворота servo_up.positions_data[MIN] = 80; //минимальный угол поворот servo_up.positions_data[CUR] = servo_round.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino servo_up.servo_obj.attach(A1); //устанавливаем мотор на начальный угол поворота servo_up.servo_obj.write(servo_up.positions_data[INI]); //задержка delay(delay_setup); //Инициируем объект servo_front servo_front.positions_data[INI] = 90; //начальный угол поворота servo_front.positions_data[MAX] = 160; //максимальный угол поворота servo_front.positions_data[MIN] = 90; //минимальный угол поворот servo_front.positions_data[CUR] = servo_round.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino servo_front.servo_obj.attach(A2); //устанавливаем мотор на начальный угол поворота servo_front.servo_obj.write(servo_front.positions_data[INI]); //задержка delay(delay_setup); //Инициируем объект servo_hand servo_hand.positions_data[INI] = 40; //начальный угол поворота servo_hand.positions_data[MAX] = 40; //максимальный угол поворота servo_hand.positions_data[MIN] = 0; //минимальный угол поворот servo_hand.positions_data[CUR] = servo_round.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino D7 servo_hand.servo_obj.attach(A3); //устанавливаем мотор на начальный угол поворота servo_hand.servo_obj.write(servo_hand.positions_data[INI]); //задержка delay(delay_setup); } /*стандартная функция с начальной инициализацией всех компонентов программы*/ void setup() { //инициируем все объекты в отдельной функции InitServoObjects(); } /*стандартная функция с реализацией программы, в которой все действия циклически повторяются*/ void loop(){ for(int i = servo_round.positions_data[INI]; i <= servo_round.positions_data[MAX]; i++) { servo_round.servo_obj.write(i); delay(delay_move); } for(int i = servo_round.positions_data[MAX]; i >= servo_round.positions_data[MIN]; i--) { servo_round.servo_obj.write(i); delay(delay_move); } for(int i = servo_round.positions_data[MIN]; i <= servo_round.positions_data[INI]; i++) { servo_round.servo_obj.write(i); delay(delay_move); } for(int i = servo_up.positions_data[INI]; i <= servo_up.positions_data[MAX]; i++) { servo_up.servo_obj.write(i); delay(delay_move); } for(int i = servo_up.positions_data[MAX]; i >= servo_up.positions_data[MIN]; i--) { servo_up.servo_obj.write(i); delay(delay_move); } for(int i = servo_up.positions_data[MIN]; i <= servo_up.positions_data[INI]; i++) { servo_up.servo_obj.write(i); delay(delay_move); } for(int i = servo_front.positions_data[INI]; i <= servo_front.positions_data[MAX]; i++) { servo_front.servo_obj.write(i); delay(delay_move); } for(int i = servo_front.positions_data[MAX]; i >= servo_front.positions_data[MIN]; i--) { servo_front.servo_obj.write(i); delay(delay_move); } for(int i = servo_hand.positions_data[INI]; i >= servo_hand.positions_data[MIN]; i--) { servo_hand.servo_obj.write(i); delay(delay_move); } for(int i = servo_hand.positions_data[MIN]; i <= servo_hand.positions_data[MAX]; i++) { servo_hand.servo_obj.write(i); delay(delay_move); } } |
Если роборука отрабатывает программу, значит все работает хорошо и пора переходить к программированию передачи сигналов от NodeMCU к Arduino для управления робо-рукой.
SPI интерфейс передачи данных
SPI (serial peripheral interface, последовательный периферийный интерфейс) — это протокол подключения к шине, передающей данные по типу «ведущий-ведомый». Он был разработан корпорацией Motorola. Шина содержит 4 провода для связи. По ней могут взаимодействовать одновременно только 1 ведущее (master) и 1 ведомое устройство (slave). Соответственно, устройства с поддержкой SPI могут работать в одном из двух режимов — Master mode и Slave mode. Главное устройство инициирует связь , генерирует последовательный счетчик времени для синхронной передачи данных и может работать с несколькими ведомыми устройствами, выбирая одно из них.
И NodeMCU и Arduino поддерживают этот интерфейс. У NodeMCU есть две шины SPI — которые называются SPI и HSPI. SPI используется процессором для доступа к флешу с прошивкой, HSPI может использоваться для других устройств.
Связь Arduino и NodeMCU через интерфейс SPI осуществляется через следующие четыре контакта:
Название контакта | Описание | Контакт ArduinoNano | Контакт NodeMCU |
MISO |
Master In — Slave Out. Ведущее устройство получает данные, а ведомое передает через этот контакт |
D12 | D6 |
MOSI |
Master Out — Slave In. Ведущее устройство передает данные, а ведомое получает через этот контакт |
D11 | D7 |
SCLK |
Последовательные часы. Только ведущее устройство может генерировать эти часы для связи, которая используется ведомыми устройствами |
D13 | D5 |
CS |
Chip Select. Через этот контакт ведущее устройство может выбрать ведомое и начать с ним связь |
D10 | D8 |
Для обеспечения связи по SPI интерфейсу соединяются соответствующие контакты NodeMCU и Arduino (D5-D13, D6 — D12, D7-D11, D8-D10, GND-GND).
Теперь, прежде чем собирать окончательную схему и заливать программы, нужно проверить как работает SPI связь.
Заливаем программу в NodeMCU (к статье прилагается файл с кодом «sketch_SPITestMaster.ino»):
/*Программа для проверки работы SPI интерфейса. NodeMCU (Master) - посылает сообщения Arduino (Slave) Arduino выводит сообщения на монитор порта*/ //подключаем библиотеку SPI #include<SPI.h> const int delay_messg = 1000; void setup() { SPI.begin(); /* запускаем SPI */ } void loop() { /*Оправляем один за другим несколько сообщений с задержкой в 1 сек*/ onSendStop(); delay(delay_messg); OnSendRound1(); delay(delay_messg); OnSendRound2(); delay(delay_messg); OnSendUp(); delay(delay_messg); OnSendDown(); delay(delay_messg); OnSendFront(); delay(delay_messg); OnSendBack(); delay(delay_messg); OnSendOpen(); delay(delay_messg); OnSendClose(); delay(delay_messg); } void onSendStop() { char buff[]="stp\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendRound1() { char buff[]="rd1\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendRound2() { char buff[]="rd2\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendUp() { char buff[]="up\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendDown() { char buff[]="dwn\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendFront() { char buff[]="frt\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendBack() { char buff[]="bck\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendOpen() { char buff[]="opn\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } void OnSendClose() { char buff[]="cls\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); } |
Заливаем программу в ArduinoUNO (к статье прилагается файл с кодом «sketch_SPITestSlave.ino»):
/*Программа для проверки работы SPI интерфейса. NodeMCU (Master) - посылает сообщения Arduino (Slave) Arduino выводит сообщения на монитор порта*/ //подключаем библиотеку SPI #include <SPI.h> //создаем массив символов для получения сообщения char buff [5]; //текущий, записываемый индекс массива buff volatile byte index; //флаг, означающий, что сообщение получено volatile bool receivedone; /* use reception complete flag */ //строка, хранящая полученную команду String commandSPI = ""; void setup (void) { //Открываем последовательное соединение через порт со скоростью передачи данных 9600 Serial.begin (9600); SPCR |= bit(SPE); /* Включаем SPI */ pinMode(MISO, OUTPUT); /* Устанавливаем MISO пин как выходной - OUTPUT */ index = 0; receivedone = false; SPI.attachInterrupt(); /* добавляем прерывание по приходу сигнала по SPI*/ } void loop (void) { if (receivedone) /* Если сообщение получено */ { buff[index] = 0; commandSPI = buff; //присваиваем строке сообщение if(commandSPI == "stp") { Serial.println("stop"); } else if(commandSPI == "rd1") { Serial.println("round1"); } else if(commandSPI == "rd2") { Serial.println("round2"); } else if(commandSPI == "up") { Serial.println("up"); } else if(commandSPI == "dwn") { Serial.println("down"); } else if(commandSPI == "frt") { Serial.println("front"); } else if(commandSPI == "bck") { Serial.println("back"); } else if(commandSPI == "opn") { Serial.println("open"); } else if(commandSPI == "cls") { Serial.println("close"); } index = 0; receivedone = false; } } // Функция-обработчик прерывания ISR (SPI_STC_vect) { uint8_t oldsrg = SREG; cli(); char c = SPDR; if (index <sizeof buff) { /* Проверяем, получен ли символ перехода на новую строку, который оканчивает сообщение*/ if (c == '\n'){ receivedone = true; } else buff [index++] = c; } SREG = oldsrg; } |
Собираем схему:
Затем подключаем обе платы по мини и микро USB к компьютеру.
Открываем Монитор порта для Arduino.
На нем должны отображаться команды, передаваемые с платы NodeMCU через SPI шину каждую секунду.
Если все отображается, значит SPI интерфейс работает как надо и можно переходить к окончательному сбору схемы робота и его программированию.
Окончательная схема
Окончательная схема робомашинки с самодельным драйвером моторов выглядит так:
Та же схема, но с драйвером L298N который можно купить на рынке:
Программа для Arduino (к статье прилагается файл с кодом «sketch_RobocarMeArm_ForArduino.ino»):
/*Программа для робота-манипулятора MeArm. Получает данные от четырёх аналоговых контактов платы NodeMCU. Каждый из этих контактов задает вращение сервомоторов, управляющих роборукой.*/ //подключаем библиотеку servo #include <Servo.h> #include <SPI.h> /*определяем ключевые слова (INI — начальное положение мотора, MAX — максимальное положение мотора, MIN — минимальное положение мотора, CUR — текущее положение мотора), которые будут использоваться как индексы для доступа к цифрам, своим для каждого мотора*/ #define INI 0 #define MAX 1 #define MIN 2 #define CUR 3 /*константные глобальные переменные хранящие время задержки в мс*/ const int delay_move = 10; const int delay_setup = 100; const int motor_step = 1; char buff [5]; volatile byte index; volatile bool receivedone; /* use reception complete flag */ String commandSPI = ""; /*Структура, хранящая всю необходимую информацию о моторе*/ struct SERVO_MOTOR{ //объект мотора servo Servo servo_obj; /*массив целочисленных переменных, у которого 0й элемент (соответствует INI) — это начальное положение мотора, 1й (MAX) — максимально возможное, 2й (MIN) — минимально возможное, 3й (STP) — единицы соответствующие шагу в 1 градус, 4й (CUR) — текущее положение.*/ int positions_data[5] = {0,0,0,0}; }; //создаём четыре объекта структуры SERVO_MOTOR SERVO_MOTOR servo_round, servo_up, servo_front, servo_hand; /*функция InitServoObjects содержит начальную инициализацию всех четырёх моторов*/ void InitServoObjects() { //Инициируем объект servo_round servo_round.positions_data[INI] = 90; //начальный угол поворота servo_round.positions_data[MAX] = 180; //максимальный угол поворота servo_round.positions_data[MIN] = 0; //минимальный угол поворот servo_round.positions_data[CUR] = servo_round.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino servo_round.servo_obj.attach(A0); //устанавливаем мотор на начальный угол поворота servo_round.servo_obj.write(servo_round.positions_data[INI]); //задержка delay(delay_setup); //Инициируем объект servo_up servo_up.positions_data[INI] = 100; //начальный угол поворота servo_up.positions_data[MAX] = 160; //максимальный угол поворота servo_up.positions_data[MIN] = 60; //минимальный угол поворот servo_up.positions_data[CUR] = servo_up.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino servo_up.servo_obj.attach(A1); //устанавливаем мотор на начальный угол поворота servo_up.servo_obj.write(servo_up.positions_data[INI]); //задержка delay(delay_setup); //Инициируем объект servo_front servo_front.positions_data[INI] = 90; //начальный угол поворота servo_front.positions_data[MAX] = 160; //максимальный угол поворота servo_front.positions_data[MIN] = 90; //минимальный угол поворот servo_front.positions_data[CUR] = servo_front.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino servo_front.servo_obj.attach(A2); //устанавливаем мотор на начальный угол поворота servo_front.servo_obj.write(servo_front.positions_data[INI]); //задержка delay(delay_setup); //Инициируем объект servo_hand servo_hand.positions_data[INI] = 40; //начальный угол поворота servo_hand.positions_data[MAX] = 40; //максимальный угол поворота servo_hand.positions_data[MIN] = 0; //минимальный угол поворот servo_hand.positions_data[CUR] = servo_hand.positions_data[INI]; //текущий угол поворота //назначаем объекту класса servo контакт Arduino D7 servo_hand.servo_obj.attach(A3); //устанавливаем мотор на начальный угол поворота servo_hand.servo_obj.write(servo_hand.positions_data[INI]); //задержка delay(delay_setup); } /*стандартная функция с начальной инициализацией всех компонентов программы*/ void setup() { //инициируем все объекты в отдельной функции InitServoObjects(); SPCR |= bit(SPE); /* Enable SPI */ pinMode(MISO, OUTPUT); /* Make MISO pin as OUTPUT */ index = 0; receivedone = false; SPI.attachInterrupt(); /* Attach SPI interrupt */ } /*стандартная функция с реализацией программы, в которой все действия циклически повторяются*/ void loop(){ if (receivedone) { buff[index] = 0; commandSPI = buff; index = 0; receivedone = false; } if(commandSPI == "stp") { } else if(commandSPI == "rd1") { if( (servo_round.positions_data[CUR] + motor_step) <= servo_round.positions_data[MAX] ) { servo_round.positions_data[CUR] = servo_round.positions_data[CUR] + motor_step; servo_round.servo_obj.write(servo_round.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "rd2") { if( (servo_round.positions_data[CUR] - motor_step) >= servo_round.positions_data[MIN] ) { servo_round.positions_data[CUR] = servo_round.positions_data[CUR] - motor_step; servo_round.servo_obj.write(servo_round.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "up") { if( (servo_up.positions_data[CUR] + motor_step) <= servo_up.positions_data[MAX] ) { servo_up.positions_data[CUR] = servo_up.positions_data[CUR] + motor_step; servo_up.servo_obj.write(servo_up.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "dwn") { if( (servo_up.positions_data[CUR] - motor_step) >= servo_up.positions_data[MIN] ) { servo_up.positions_data[CUR] = servo_up.positions_data[CUR] - motor_step; servo_up.servo_obj.write(servo_up.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "frt") { if( (servo_front.positions_data[CUR] + motor_step) <= servo_front.positions_data[MAX] ) { servo_front.positions_data[CUR] = servo_front.positions_data[CUR] + motor_step; servo_front.servo_obj.write(servo_front.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "bck") { if( (servo_front.positions_data[CUR] - motor_step) >= servo_front.positions_data[MIN] ) { servo_front.positions_data[CUR] = servo_front.positions_data[CUR] - motor_step; servo_front.servo_obj.write(servo_front.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "opn") { if( (servo_hand.positions_data[CUR] - motor_step) >= servo_hand.positions_data[MIN] ) { servo_hand.positions_data[CUR] = servo_hand.positions_data[CUR] - motor_step; servo_hand.servo_obj.write(servo_hand.positions_data[CUR]); } else commandSPI = "stp"; } else if(commandSPI == "cls") { if( (servo_hand.positions_data[CUR] + motor_step) <= servo_hand.positions_data[MAX] ) { servo_hand.positions_data[CUR] = servo_hand.positions_data[CUR] + motor_step; servo_hand.servo_obj.write(servo_hand.positions_data[CUR]); } else commandSPI = "stp"; } delay(delay_move); } // Обработчик прерывания по SPI ISR (SPI_STC_vect) { uint8_t oldsrg = SREG; cli(); char c = SPDR; if (index <sizeof buff) { if (c == '\n'){ /* Check for newline character as end of msg */ receivedone = true; } else buff [index++] = c; } SREG = oldsrg; } |
Программа для NodeMCU (к статье прилагается файл с кодом «sketch_RobocarMeArm_ForNodeMCU.ino»):
/*Программа для робота-машинки. Вращается машинка благодаря двум моторам-редукторам, управляемым этой программой для NodeMCU (контакты D0-D3). Еще программа передает команды по SPI интерфейсу плате Arduino для управления четырьмя моторами робота-руки MeArm. NodeMCU создает программную точку доступа Wifi и запускает сервер, генерит html страницу на этом сервере, содержащую кнопки управления моторами. Через телефон или планшет происходит подключение по Wifi к плате и через эту страницу на сервере управляются моторы*/ /*Подключаем библиотеки для Wifi, сервера, SPI интерфейса*/ #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include<SPI.h> //контакты, управляющие вращением моторов тележки #define pin_Moror1_front 16 //D0 NodeMCU #define pin_Moror1_back 5 //D1 #define pin_Moror2_front 4 //D2 #define pin_Moror2_back 0 //D3 //название и пароль создаваемой программной точки доступа Wifi const char* ssid = "ESP8266Net"; const char* password = "ESP12345"; //Переменные строки, указывающие какая кнопка нажата в данный момент String CurentButtonActive = "stop"; String CurentArmButtonActive = "stop"; /* Устанавливаем свой веб сервер на порт 80*/ /* По-умолчанию сервер доступен по IP http://192.168.4.1 */ ESP8266WebServer server(80); void setup() { /*Устанавливаем параметры контактов - выходный и выключены*/ pinMode(pin_Moror1_front, OUTPUT); pinMode(pin_Moror1_back, OUTPUT); pinMode(pin_Moror2_front, OUTPUT); pinMode(pin_Moror2_back, OUTPUT); digitalWrite(pin_Moror1_front, LOW); digitalWrite(pin_Moror1_back, LOW); digitalWrite(pin_Moror2_front, LOW); digitalWrite(pin_Moror2_back, LOW); /* Создаем мягкую точку доступа Wifi. WiFi.softAP(ssid, password, channel, hidden, max_connection) ssid - строка, содержащая сетевоt имя Wifi (макс. 31 символ) password - необязательная символьная строка с паролем. channel - необязательный параметр для установки канала Wi-Fi, от 1 до 13. Канал по умолчанию = 1. hidden - необязательный параметр, если установлено значение true, будет скрывать SSID. max_connection - необязательный параметр для установки макс. одновременных подключенных станций, от 0 до 8. По умолчанию 4. После достижения максимального числа любая другая станция, которая хочет подключиться, будет вынуждена ждать, пока уже подключенная станция не отключится. */ WiFi.softAP(ssid, password,1,0,1); //Привязываем функции-обработчики полученных команд от колес server.on("/", onRestart); server.on("/stop", onStop); server.on("/front", onFront); server.on("/back", onBack); server.on("/left", onLeft); server.on("/right", onRight); //Привязываем функции-обработчики полученных команд от роборуки server.on("/armstop", onArmStop); server.on("/armround1", onArmRound1); server.on("/armround2", onArmRound2); server.on("/armup", onArmUp); server.on("/armdwn", onArmDown); server.on("/armfront", onArmFront); server.on("/armback", onArmBack); server.on("/armopn", onArmOpen); server.on("/armcls", onArmClose); //Запускаем сервер server.begin(); //Запускаем SPI интерфейс SPI.begin(); } void loop() { //Прослушивание клиентов server.handleClient(); } void onSendWebPage() { /*Вызываем функцию, перерисовывающую html страницу*/ server.send(200, "text/html", createWebPage()); delay(5); } void onRestart() { digitalWrite(pin_Moror1_front, LOW); digitalWrite(pin_Moror1_back, LOW); digitalWrite(pin_Moror2_front, LOW); digitalWrite(pin_Moror2_back, LOW); CurentButtonActive = "stop"; CurentArmButtonActive = "stop"; char buff[]="armstop\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); onSendWebPage(); } void onStop() { digitalWrite(pin_Moror1_front, LOW); digitalWrite(pin_Moror1_back, LOW); digitalWrite(pin_Moror2_front, LOW); digitalWrite(pin_Moror2_back, LOW); CurentButtonActive = "stop"; onSendWebPage(); } void onFront() { digitalWrite(pin_Moror1_front, HIGH); digitalWrite(pin_Moror1_back, LOW); digitalWrite(pin_Moror2_front, HIGH); digitalWrite(pin_Moror2_back, LOW); CurentButtonActive = "front"; onSendWebPage(); } void onBack() { digitalWrite(pin_Moror1_front, LOW); digitalWrite(pin_Moror1_back, HIGH); digitalWrite(pin_Moror2_front, LOW); digitalWrite(pin_Moror2_back, HIGH); CurentButtonActive = "back"; onSendWebPage(); } void onLeft() { digitalWrite(pin_Moror1_front, LOW); digitalWrite(pin_Moror1_back, HIGH); digitalWrite(pin_Moror2_front, HIGH); digitalWrite(pin_Moror2_back, LOW); CurentButtonActive = "left"; onSendWebPage(); } void onRight() { digitalWrite(pin_Moror1_front, HIGH); digitalWrite(pin_Moror1_back, LOW); digitalWrite(pin_Moror2_front, LOW); digitalWrite(pin_Moror2_back, HIGH); CurentButtonActive = "right"; onSendWebPage(); } void onArmStop() { char buff[]="stp\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "stop"; onSendWebPage(); } void onArmRound1() { char buff[]="rd1\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "round1"; onSendWebPage(); } void onArmRound2() { char buff[]="rd2\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "round2"; onSendWebPage(); } void onArmUp() { char buff[]="up\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "up"; onSendWebPage(); } void onArmDown() { char buff[]="dwn\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "down"; onSendWebPage(); } void onArmFront() { char buff[]="frt\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "front"; onSendWebPage(); } void onArmBack() { char buff[]="bck\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "back"; onSendWebPage(); } void onArmOpen() { char buff[]="opn\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "open"; onSendWebPage(); } void onArmClose() { char buff[]="cls\n"; for(int i=0; i<sizeof buff; i++) SPI.transfer(buff[i]); CurentArmButtonActive = "close"; onSendWebPage(); } String createWebPage() { /*Создается html страница, которая будет открываться на установленном сервере*/ String WebPage = ""; // Отображаем страницу HTML WebPage += "<!DOCTYPE html><html>"; WebPage += "<head><meta name='viewport' content='width=device-width, initial-scale=1'>"; // CSS для стиля кнопок on/off WebPage += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"; WebPage += ".button { background-color: #195B6A; border: none; color: white; width: 80px; padding: 7px 0px;"; WebPage += " text-decoration: none; font-size: 15px; margin: 2px; cursor: pointer;}"; WebPage += ".button2 {background-color: #77878A;}</style>"; // Заголовок для страницы WebPage += "</head><body><div align='center'>"; WebPage += "<table align='center' valign = 'top'><tr><td>"; WebPage += "<table align='center' valign = 'top'><tr><td>"; WebPage += "<table align='center' valign = 'top'><tr><td>"; WebPage += "<a href='/armround1'><button class="; if(CurentArmButtonActive == "round1") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Round1</button></a>"; WebPage += "</td><td>"; WebPage += "<a href='/armround2'><button class="; if(CurentArmButtonActive == "round2") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Round2</button></a>"; WebPage += "</td><tr><td>"; WebPage += "<a href='/armup'><button class="; if(CurentArmButtonActive == "up") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Up</button></a>"; WebPage += "</td><td>"; WebPage += "<a href='/armdwn'><button class="; if(CurentArmButtonActive == "down") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Down</button></a>"; WebPage += "</td></tr></table>"; WebPage += "</td></tr><tr><td align ='center'>"; WebPage += "<a href='/armstop'><button class="; if(CurentArmButtonActive == "stop") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Stop</button></a>"; WebPage += "</td></tr><tr><td>"; WebPage += "<table align='center' valign = 'top'><tr><td>"; WebPage += "<a href='/armfront'><button class="; if(CurentArmButtonActive == "front") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Front</button></a>"; WebPage += "</td><td>"; WebPage += "<a href='/armback'><button class="; if(CurentArmButtonActive == "back") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Back</button></a>"; WebPage += "</td><tr><td>"; WebPage += "<a href='/armopn'><button class="; if(CurentArmButtonActive == "open") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Open</button></a>"; WebPage += "</td><td>"; WebPage += "<a href='/armcls'><button class="; if(CurentArmButtonActive == "close") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Close</button></a>"; WebPage += "</td></tr></table>"; WebPage += "</td></tr></table>"; WebPage += "</td><td><table>"; WebPage += "<tr><td></td><td>"; WebPage += "<a href='/front'><button class="; if(CurentButtonActive == "front") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Front</button></a>"; WebPage += "</td><td></td></tr><tr><td>"; WebPage += "<a href='/left'><button class="; if(CurentButtonActive == "left") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Left</button></a>"; WebPage += "</td><td>"; WebPage += "<a href='/stop'><button class="; if(CurentButtonActive == "stop") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Stop</button></a>"; WebPage += "</td><td>"; WebPage += "<a href='/right'><button class="; if(CurentButtonActive == "right") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Right</button></a>"; WebPage += "</td></tr><tr><td></td><td>"; WebPage += "<a href='/back'><button class="; if(CurentButtonActive == "back") WebPage += "'button'"; else WebPage += "'button button2'"; WebPage += ">Back</button></a>"; WebPage += "</td><td></td></tr></table>"; WebPage += "</td></tr></table>"; WebPage += "</div></body></html>"; return WebPage; } |
Я спаяла схему на макетной плате, соединив нужные контакты с обратной стороны.
Подключаем все по схеме, включаем робота, при этом роборука должна перейти в исходное положение.
Затем берем мобильный телефон, идем в поиск сетей Wifi. Находим сеть «ESP8266Net», подключаемся введя пароль «ESP12345».
Открываем броузер и в строке адреса вводим: «192.168.4.1».
Отобразится html страница:
Справа кнопки управляют колесами машины, слева — роборукой.
Робомашинка готова к использованию.