• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Делюсь опытом Была Lego Racers RC, стала Lego Racers Blynk.

immortal

New member
Первые 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. Соответствие положению вала назначениям выводов
Код:
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
Подключаем библиотеки, для управления машиной достаточно
Код:
// библиотеки для управления
#include <Wire.h> // библиотека для работы с шиной i2c
#include <PCF8574.h> // библиотека для PCF8574
у меня установлен датчик HMC5883, подключаем ещё библиотеки для работы с этим датчиком
Код:
// библиотеки для датчика HMC5883
#include <Adafruit_Sensor.h> // библиотека для работы HMC5883
#include <Adafruit_HMC5883_U.h> // библиотека для работы HMC5883
и задаем переменные, для переменных для управления
Код:
// часть переменных для управления
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
для датчиков
Код:
// часть переменных для датчиков
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
Для управления используем несколько функций.
три функции управляют движением руля.

Код:
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;
}
в зависимости от направления (задаваемого программой) и положения вала (значения энкодера) нам надо позиционировать вал, для этого пишем функцию управления рулем.
Код:
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();
          }
        }

    }
}
 
Последнее редактирование:

immortal

New member
Это все функции для управления рулем. Пишем функцию для управление двигателем, поскольку мотор на малых оборотах не может крутить колеса из-за малой мощности, то ограничиваем обороты включения мотора.



Код:
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, заряд батареи и данные магнитометра, добавляем соответствующие функции



Код:
// ---------------------- заряд батареи -------------------------
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
Код:
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);
   }
}
основной цикл



Код:
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 переменные и написать всего пару строк.
добавим подключение библиотек
Код:
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();
  }
}

добавим переменные

Код:
#define BLYNK_PRINT Serial
char auth[] = "токен"; // токен из приложения Blynk
char ssid[] = "SSID";     // параметры wi-fi
char pass[] = "PASSWORD"; // точки доступа

в функцию setup() добавим в начало

Код:
Blynk.begin(auth, ssid, pass);

в функцию loop() добавим в начало



Код:
Blynk.run();
Теперь нам надо определиться с виджетами которые мы будем использовать.
Для управления можно использовать несколько вариантов. самый пожалуй простой для управления это использования джойстика, недостатком является не полная скорость при поворотах, добавляем виждет «джойстик» со следующими параметрами.




В скетч вставим виртуальный пин V1


Код:
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:

Код:
Blynk.virtualWrite(V0,String(Sila));

Скетч управления машинкой можно скачать тут.

Управляем через web интерфейс.

Но что делать если в месте запуска отсутствует интернет? будем делать управление другим способом, управлять будем через web интерфейс, наша машина будет в качестве web сервера к которой мы будем подключаться.

В свой скетч я добавил из 2-х источников скетчи, это скетч с конфигуратором настроек в EEPROM, сервер и клиент и скетч управления кораблем

Скетч, который создает точку доступа и может подключаться к сети wi-fi можно скачать тут.

После загрузки скетча на ESP8266 надо подключиться и настроить подключение к сети wi-fi, пароль SECRET плюс последние 4 символа мак адреса чипа, они так же последние в имени точки доступа. Настройка параметров подробно описано в этой статье.

объединив все три в один я получил скетч управления машиной, который можно скачать тут.



Источник
 
Сверху Снизу