Глубокая модернизация. Была Lego Racers RC, стала Lego Racers Blynk.
Первые 40 лет детства самые сложные в жизни мужчины.
/правда жизни/
Достались мне несколько конструкторов Lego с различными моторчиками, но без пультов управления. Один из них Lego Racers RC (номер 8475). Ну что же, будем делать из него Lego Racers Blynk ESP8266.
Набор содержит блок управления в котором контроллер с мотором управления рулем, к блоку управления подключаются 2 детали — двигатели. Положение руля контролирует энкодер.
Поскольку у ESP8266 мало выводов я дополнительно применил микросхему PCF8574, хотя в принципе достаточно было бы и существующих выводов, но я планировал добавить навесное оборудование типа датчика расстояния или оптических датчиков. Так же поставил магнитометр HMC5883, пока правда не придумал как его применить кроме вывода информации с него. В итоге получилась вот такая схема (на схеме нет магнитометра, но думаю не составит труда понять как он подключается)
Внутренности.
плату драйверов пришлось немного модернизировать чтобы запихать в корпус и питать esp8266, для этого были выпаяны все разъемы, снят радиатор, заменен преобразователь на 5 вольт преобразователем на 3,3 вольта (разъем 5 вольт выдает 3,3В), конденсатора заменены на танталовые smd. Через резистор 10K выведен светодиод в место куда вставлялась антенна.
Энкодер у данного конструктора Lego формирует 3 бита, выводы 1,2,3 через резистор 10К подключены к 3,3В и когда внутри энкодера не замыкается на GND то формируется логическая единица, если замыкается на GND то формируется логический 0. Соответствие положению вала назначениям выводов
1 2 3 4 5 6 7 |
0 0 0 - 0 - влево 1 0 0 - 1 - 0,33 влево 1 0 1 - 5 - 0,66 влево 0 0 1 - 4 - прямо 0 1 1 - 6 - 0,66 право 0 1 0 - 2 - 0,33 право 1 1 0 - 3 - право |
При этом между положениями контакты не касаются GND, то есть получаем значение 1 1 1 или 7 в десятичной системе, а это значит надо учитывать это.
Обозначу положение руля следующим образом: 0 — влево, 1 — 0,66 влево, 2 — 0,33 — влево, 3 — прямо, 4 — 0,33 право, 5 — 0,66 право, 6 — право.
Энкодер подключен к выводам 0,1 и 2 PCF8574, я использовал библиотеку PCF8574
Подключаем библиотеки, для управления машиной достаточно
1 2 3 |
// библиотеки для управления #include <Wire.h> // библиотека для работы с шиной i2c #include <PCF8574.h> // библиотека для PCF8574 |
у меня установлен датчик HMC5883, подключаем ещё библиотеки для работы с этим датчиком
1 2 3 |
// библиотеки для датчика HMC5883 #include <Adafruit_Sensor.h> // библиотека для работы HMC5883 #include <Adafruit_HMC5883_U.h> // библиотека для работы HMC5883 |
и задаем переменные, для переменных для управления
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// часть переменных для управления PCF8574 pcf20(0x20); // задаем параметры PCF8574, адрес 0х20 byte pcf_byte0; // значение энкодера 1 контакт byte pcf_byte1; // значение энкодера 2 контакт byte pcf_byte2; // значение энкодера 3 контакт int pcf_command; // десятичное значение энкодера int OUT1=13; // OUT1 - шим двигателя int OUT2=12; // OUT2 - шим руля int skorost; // скорость двигателей int revers; // направление движение (1 - назад, 0 - вперед) // направление движения // 0 - влево, 1 - 0,3 влево, 2 - 0,6 - влево, // 3 - прямо, 4 - 0,6 право, 5 - 0,3 право, 6 - право. int Napravlenie = 3; int MotorStatus = 0; // статус мотора руля (0 - выключен, 1 крутиться вправо, 2 - крутиться влево) int millis1 = 0; // прерывание 1 int millis2 = 0; // прерывание 2 int millis3 = 0; // прерывание 3 |
для датчиков
1 2 3 4 5 6 7 8 9 10 |
// часть переменных для датчиков int BAT=17; // BAT - номер контакта ADC батареи int Sila, SilaLow = -43; // Контроль силы сигнала WiFi int Batareya, BatLow = 400; //Контроль напряжения батареи String Bat, SilaW; // переменные для величины батареи и сигнала Wi-Fi Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345); // переменная для HMC5883 float mag_x; // X HMC5883 float mag_y; // Y HMC5883 float mag_z; // Z HMC5883 float mag_k; // Курс HMC5883 |
Для управления используем несколько функций.
три функции управляют движением руля.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void ruleLeft() { // руль влево analogWrite(OUT2, 800); pcf20.write(4, HIGH); pcf20.write(5, LOW); MotorStatus = 2; } void ruleRight() { // руль вправо analogWrite(OUT2, 800); pcf20.write(4, LOW); pcf20.write(5, HIGH); MotorStatus = 1; } void ruleStop() { // остановить поворот руля analogWrite(OUT2, 0); pcf20.write(4, LOW); pcf20.write(5, LOW); MotorStatus = 0; } |
в зависимости от направления (задаваемого программой) и положения вала (значения энкодера) нам надо позиционировать вал, для этого пишем функцию управления рулем.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
void processRule() { pcf_byte0 = pcf20.readButton(0); // данные положение руля конт 1 pcf_byte1 = pcf20.readButton(1); // данные положение руля конт 2 pcf_byte2 = pcf20.readButton(2); // данные положение руля конт 3 pcf_command = pcf_byte0 + pcf_byte1 * 2 + pcf_byte2 * 4; // перевод в десятичную систему if (Napravlenie == 6) { // направо - 3 if (pcf_command == 3) { // мотор уже в правом положении if(MotorStatus != 0) { ruleStop(); } } else { // мотор не в правом положении, крутим вправо if(MotorStatus != 1) { ruleRight(); } } } else if (Napravlenie == 0) { // налево - 0 if (pcf_command == 0) { // мотор уже в левом положении if(MotorStatus != 0) { ruleStop(); } } else { // мотор не в левом положении, крутим налево if(MotorStatus != 2) { ruleLeft(); } } } else if (Napravlenie == 5) { // 0.6 - 2 if (pcf_command == 2) { // мотор уже в положении 0,6 вправо, останавливаем if(MotorStatus != 0) { ruleStop(); } } else if (pcf_command == 3) { // мотор справа, крутим влево if(MotorStatus != 2) { ruleLeft(); } } else if (pcf_command == 0 || pcf_command == 1 || pcf_command == 5 || pcf_command == 4 || pcf_command == 6) { // мотор слева, крутим вправо if(MotorStatus != 1) { ruleRight(); } } else { //энкодер стоит между контактами и мотор не крутиться, крутим if (pcf_command == 7 && MotorStatus == 0) { ruleRight(); } } } else if (Napravlenie == 4) { // 0.3 направо - 6 if (pcf_command == 6) { // мотор уже в положении 0,3 вправо if(MotorStatus != 0) { ruleStop(); } } else if (pcf_command == 3 || pcf_command == 2) { // мотор справа, крутим налево if(MotorStatus != 2) { ruleLeft(); } } else if (pcf_command == 0 || pcf_command == 1 || pcf_command == 5 || pcf_command == 4) { // мотор слева, крутим вправо if(MotorStatus != 1) { ruleRight(); } } else { // энкодер стоит между контактами и мотор не крутиться, крутим if (pcf_command == 7 && MotorStatus == 0) { ruleRight(); } } } else if (Napravlenie == 1) { // 0.6 налево - 1 if (pcf_command == 1) { // мотор уже в положении 0,6 лево if(MotorStatus != 0) { ruleStop(); } } else if (pcf_command == 0) { // мотор в левом положении, крутим вправо if(MotorStatus != 1) { ruleRight(); } } else if (pcf_command == 2 || pcf_command == 3 || pcf_command == 4 || pcf_command == 5 || pcf_command == 6){ // мотор правее, крутим влево if(MotorStatus != 2) { ruleLeft(); } } else { // энкодер стоит между контактами и мотор не крутиться, крутим if (pcf_command == 7 && MotorStatus == 0) { ruleLeft(); } } } else if (Napravlenie == 2) { // 0.3 налево - 5 if (pcf_command == 5) { // мотор уже в положении 0,3 лево if(MotorStatus != 0) { ruleStop(); } } else if (pcf_command == 0 || pcf_command == 1) { // мотор в левом положении, крутим вправо if(MotorStatus != 1) { ruleRight(); } } else if (pcf_command == 2 || pcf_command == 3 || pcf_command == 4 || pcf_command == 6){ // мотор правее, крутим влево if(MotorStatus != 2) { ruleLeft(); } } else { // энкодер стоит между контактами и мотор не крутиться, крутим if (pcf_command == 7 && MotorStatus == 0) { ruleLeft(); } } } else { // прямо - 4 if (pcf_command == 4) { // руль стоит прямо, выключаем мотор if(MotorStatus != 0) { // если мотор не выключен, то выключаем ruleStop(); } } else if (pcf_command == 3 || pcf_command == 2 || pcf_command == 6) { // руль находиться вправо, крутим налево if(MotorStatus != 2) { ruleLeft(); } } else if (pcf_command == 0 || pcf_command == 1 || pcf_command == 5) { // руль находиться влево, крутим направо if(MotorStatus != 1) { ruleRight(); } } else { // энкодер стоит между контактами и мотор не крутиться, крутим if (pcf_command == 7 && MotorStatus == 0) { ruleLeft(); } } } } |
Это все функции для управления рулем. Пишем функцию для управление двигателем, поскольку мотор на малых оборотах не может крутить колеса из-за малой мощности, то ограничиваем обороты включения мотора.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void processMotor() { if (skorost > 200){ // Если слайдер скорости > 200 analogWrite(OUT1, skorost); if (revers == 1){ pcf20.write(6, LOW); pcf20.write(7, HIGH); } else { pcf20.write(6, HIGH); pcf20.write(7, LOW); } } else { // Если слайдер скорости < 200 - выключить моторы analogWrite(OUT1, 0); pcf20.write(6, LOW); pcf20.write(7, LOW); } } |
Это всё что необходимо для управления машиной (естественно за исключением основных функций: setup() и loop()), но у нас ещё предполагается измерять сигнал wi-fi, заряд батареи и данные магнитометра, добавляем соответствующие функции
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// ---------------------- заряд батареи ------------------------- void Batare(){ Batareya = analogRead(BAT); // Меряем напряжение батареи if (Batareya < BatLow){ // Если напряжение батареи меньше порогового значения Bat = "Батарея разряжена!!!"; // Пишем предупреждение } else{ // Если норма Bat = ""; // Ничего не пишем } } // ------------------------- Wi-Fi сигнал ---------------------------- void SilaWifi(){ Sila = WiFi.RSSI(); // Меряем силу сигнала WiFi if (Sila < SilaLow){ SilaW = "Сигнал слабый!!!"; } else{ // Если норма SilaW = ""; // Ничего не пишем } } // ------------------сенсор HMC5883----------------------- void SensorMag() { sensors_event_t event; mag.getEvent(&event); mag_x = event.magnetic.x; // получаем значение X mag_y = event.magnetic.y; // получаем значение Y mag_z = event.magnetic.z; // получаем значение Z // вычисляем курс float heading = atan2(event.magnetic.y, event.magnetic.x); if(heading < 0) heading += 2*PI; if(heading > 2*PI) heading -= 2*PI; mag_k = heading * 180/M_PI; } |
теперь нам надо заполнить функции setup и loop
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void setup() { Serial.begin(115200); // для управления pcf20.begin(); .// инициализация PCF8574 ruleStop(); // останавливаем двигатели // для датчиков pinMode(BAT,INPUT); // инициализация порта для измерения заряда батареи if(!mag.begin()) // инициализация HMC5883 { Serial.println("Ooops, no HMC5883 detected ... Check your wiring!"); while(1); } } |
основной цикл
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void loop() { // управление машиной // часть управления рулем if(millis() >= millis1){ millis1 = millis()+10; //Обновляем значения раз в 10 миллисекунд processRule(); } // управление двигателем if(millis() > millis2){ millis2=millis()+300; //Обновляем значения раз в 300 миллисекунд processMotor(); } // конец управления машиной // считывание с датчиков if(millis() > millis3){ millis3=millis()+1000; //Обновляем значения раз в 1 секунду Batare(); SilaWifi(); SensorMag(); } } |
Это всё что необходимо для управления, однако у нас нет ещё самого главного: интерфейса управления
Blynk
Самое простое это реализация управления через Blynk, нам надо подключить библиотеки, задать 3 переменные и написать всего пару строк.
добавим подключение библиотек
1 2 |
#include <ESP8266WiFi.h> #include <BlynkSimpleEsp8266.h> |
добавим переменные
1 2 3 4 |
#define BLYNK_PRINT Serial char auth[] = "токен"; // токен из приложения Blynk char ssid[] = "SSID"; // параметры wi-fi char pass[] = "PASSWORD"; // точки доступа |
в функцию setup() добавим в начало
1 |
Blynk.begin(auth, ssid, pass); |
в функцию loop() добавим в начало
1 |
Blynk.run(); |
Теперь нам надо определиться с виджетами которые мы будем использовать.
Для управления можно использовать несколько вариантов. самый пожалуй простой для управления это использования джойстика, недостатком является не полная скорость при поворотах, добавляем виждет «джойстик» со следующими параметрами.

В скетч вставим виртуальный пин V1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
BLYNK_WRITE(V1) { // параметр джойстика 0 // руль, направление движения // 0 - лево, 1 - 0,3 лево, 2 - 0,6 - лево, // 3 - прямо // 4 - 0,6 право, 5 - 0,3 право, 6 - право. Napravlenie = param[0].asInt(); // параметр джойстика 1 // управление двигателем int coord_y = param[1].asInt(); // мотор -1024 - +1024 skorost = abs (coord_y); if (coord_y < 0) { revers = 1; } else { revers = 0; } } |
Заливаем скетч в ESP8266, запускаем и управляем машиной из приложения.
Для отображения данных с датчиков думаю не возникнет проблем, используйте переменные: Sila, Batareya, mag_x, mag_y, mag_z, mag_k, главное переводите их в строку, например для вывода данных о батареи в виртуальный пин V0:
1 |
Blynk.virtualWrite(V0,String(Sila)); |
Скетч управления машинкой можно скачать тут.
Управляем через web интерфейс.
Но что делать если в месте запуска отсутствует интернет? будем делать управление другим способом, управлять будем через web интерфейс, наша машина будет в качестве web сервера к которой мы будем подключаться.
В свой скетч я добавил из 2-х источников скетчи, это скетч с конфигуратором настроек в EEPROM, сервер и клиент и скетч управления кораблем
Скетч, который создает точку доступа и может подключаться к сети wi-fi можно скачать тут.
После загрузки скетча на ESP8266 надо подключиться и настроить подключение к сети wi-fi, пароль SECRET плюс последние 4 символа мак адреса чипа, они так же последние в имени точки доступа. Настройка параметров подробно описано в этой статье.
объединив все три в один я получил скетч управления машиной, который можно скачать тут.