Машинка на ESP8266 своими руками

Машинка на ESP8266 своими руками

Переделка машинки на управление по Wi-Fi с помощью модуля ESP-01 на чипе ESP8266.

Была машинка от которой утерян пульт. Решил я ее переделать на Wi-Fi с управлением со смартфона. Так как есть несколько плат ESP-01 на чипе ESP8266, которые я не знаю куда применить, выбор пал на них.

Модуль ESP-01 пришлось доработать из за нехватки пинов. Подробнее в статье Дополнительные ноги на плате ESP-01. В результате мы имеем 5 дополнительных ног.

esp-01 esp8266 additional pins esp-01 esp8266 доработка модуля

Машинка питалась от 4 батарей AAA. Напряжение 6 Вольт. Питание микросхемы ESP8266 заявлено 3-3.6V. Максимально модуль потребляет 200мА. Если поставить стабилизатор типа ams1117-3.3, мы получим расход заряда батарей на стабилизацию. По этому я решил собрать импульсный стабилизатор на микросхеме MC34063.

Схема понижающего преобразователя до ~3.3В на MC34063.

Схема понижающего преобразователя до 3.3В на MC34063 Резистор R3 можно поставить 0.3-0.5 Ом. От него зависит максимальный выходной ток понижающего преобразователя. L1 я поставил на 470uH. Минимальный рекомендуемый рекомендуемый от 120uH. Конденсаторы C1, C4 - танталовые на 10В. D1 - диод Шоттки.

Преобразователь питает только Wi-Fi модуль ESP-01.

В машинки были два моторчика, а под рукой драйвер двигателя постоянного тока L293D в корпусе DIP-16. Питание на него подано напрямую с батарей. По Datasheet рекомендовано подключать на 16 ногу конденсатор 100nF а на 8 ногу 1uF.

Я решил модернизировать машинку добавив свет и фонари заднего хода.

Фонари заднего хода я подключил на 10 ногу L293. На эту ногу подается положительное питание когда машинка едет назад. С нее через резистор 4.7кОм открывается NPN транзистор. Я поставил какие были под рукой - BC817. Шунтирующий резистор можно не ставить, так как если на базе не напряжение питания - значит там земля, но я привык, по этому по привычке развел и под них. Светодиода два, белые, в корпусе 0805. На каждый поставил резистор на 1кОм.

модернизация машинки свет Свет подключен аналогичным способом, только транзистор открывает вывод с модуля. Светодиоды в фарах белые, круглые, 5мм. Резисторы по 240 Ом. В фонарях - красные, в корпусе 0805. Светодиоды фонарей я подключил к светодиодам фар, но через дополнительный резистор в 1кОм, так как фонари должны светится не так ярко. То есть красные светодиоды подключены через резистор 240 + 1000 Ом.
В корпусе машинки просверлил отверстия, и приклеил светодиоды с помощью клеевого пистолета.

Печатная плата для Sprint layout 6.

печатная плата для wi fi машинки на esp8266 pcb esp8266 car android wi fi android car esp-01

Управление ESP8266 через приложение под Android.

Это был мой первый опыт написания прошивки в срезе Arduino и первый опыт написания прошивки для данного чипа, я еще не оптимизировал, выкладываю как есть :)

Вот код с комментариями:

#include 

// Управляющие пины GPIO ESP8266
#define PIN_BACK 16
#define PIN_MOVE 4
#define PIN_RIGHT 13
#define PIN_LEFT 12
#define PIN_LIGHT 2
#define PIN_LOAD 14

// Флаги состояния машинки
enum MoveFlags {
  FORWARD,
  BACKWARD,
  LEFT,
  RIGHT,
  LIGHT,
  LOAD
};

// TCP порт нашей машинки
WiFiServer server(8888);

void stop() {
  digitalWrite(PIN_BACK, LOW);
  digitalWrite(PIN_MOVE, LOW);
  digitalWrite(PIN_RIGHT, LOW);
  digitalWrite(PIN_LEFT, LOW);
  digitalWrite(PIN_LIGHT, LOW);
  digitalWrite(PIN_LOAD, LOW);
}

void setup() {
  // IP адрес нашей машинки
  IPAddress apIP(192,168,0,1);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  // режим точки доступа
  WiFi.mode(WIFI_AP);
  // Название сети и пароль для подключения к машинке
  WiFi.softAP("CarAP", "1234567890");
  server.begin();

  // настраиваем выводы на выход
  pinMode(PIN_MOVE, OUTPUT);
  pinMode(PIN_BACK, OUTPUT);
  pinMode(PIN_RIGHT, OUTPUT);
  pinMode(PIN_LEFT, OUTPUT);
  pinMode(PIN_LIGHT, OUTPUT);
  pinMode(PIN_LOAD, OUTPUT);
}

char data;
// счетчик между пакетами, о нем ниже в цикле
unsigned long diff = 0;

WiFiClient client;
void loop() {
  // если нет подключенного пульта к машинке
  if (!client.connected()) {
    // останавливаем ее
    stop();
    // ждем клиента
    client = server.available();
  } else {
    // если пульт подключен

    // если пришли данные
    if (client.available() > 0) {
      // сбрасываем счетчик
      diff = 0;
      // читаем что пришло
      data = char(client.read());

      // задаем состояние нужного пина от флага
      digitalWrite(PIN_MOVE, data & (1 << FORWARD) ? 1 : 0);
      digitalWrite(PIN_BACK, data & (1 << BACKWARD) ? 1 : 0);
      digitalWrite(PIN_LEFT, data & (1 << LEFT) ? 1 : 0);
      digitalWrite(PIN_RIGHT, data & (1 << RIGHT) ? 1 : 0);
      digitalWrite(PIN_LIGHT, data & (1 << LIGHT) ? 1 : 0);
      digitalWrite(PIN_LOAD, data & (1 << LOAD) ? 1 : 0);
    } else {
      // если данные не пришли на текущей итерации
      // увеличиваем счетчик
      if (++diff > 400000) {
        // если натикало примерно 5 секунд
        // а сигнал не поступил, то останавливаем машинку
        stop();
        diff = 0;
      }
    }
  }
}

Так как Wi-Fi может отвалиться, я засекаю сколько прошло итераций между пакетами, приложение шлет пакет раз в три секунды, если за ~5 сек пакета не было, то мы потеряли связь и что бы машинка не уехала слишком далеко я ее останавливаю.

Вывод LOAD на данной плате не разведен, это резерв для включения сигнала.

Код приложения под андроид для управления машинкой

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    // IP машинки
    public static final String SERVER_IP = "192.168.0.1";
    // Порт машинки
    public static final int SERVER_PORT = 8888;

    // Флаги управления
    public static final int FLAG_FORWARD = 0;
    public static final int FLAG_BACKWARD = 1;
    public static final int FLAG_LEFT = 2;
    public static final int FLAG_RIGHT = 3;
    public static final int FLAG_LIGHT = 5;
    public static final int FLAG_LOAD = 4;

    private Socket sock = null;
    private int moveFlag = 0;
    private long lastUpdate = 0;
    private byte[] b = new byte[1];
    private boolean force = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        if (getActionBar() != null) {
            getActionBar().hide();
        }
        if (getSupportActionBar() != null) {
            getSupportActionBar().hide();
        }
        
        binding.upBtn.setOnTouchListener((view, event)->{
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // если нажата кнопка вверх
                    // убираем флаг движения назад и ставим флаг движения вперед
                    moveFlag |= 1 << FLAG_FORWARD;
                    moveFlag &= ~(1 << FLAG_BACKWARD);
                    // была нажата кнопка, шлем сразу новое состояние
                    force = true;
                    return true;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    // убираем флаг движения
                    moveFlag &= ~(1 << FLAG_FORWARD);
                    force = true;
                    return true;
            }
            return false;
        });

        binding.downBtn.setOnTouchListener((view, event)->{
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    moveFlag |= 1 << FLAG_BACKWARD;
                    moveFlag &= ~(1 << FLAG_FORWARD);
                    force = true;
                    return true;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    moveFlag &= ~(1 << FLAG_BACKWARD);
                    force = true;
                    return true;
            }
            return false;
        });

        binding.leftBtn.setOnTouchListener((view, event)->{
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    moveFlag |= 1 << FLAG_LEFT;
                    moveFlag &= ~(1 << FLAG_RIGHT);
                    force = true;
                    return true;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    moveFlag &= ~(1 << FLAG_LEFT);
                    force = true;
                    return true;
            }
            return false;
        });

        binding.rightBtn.setOnTouchListener((view, event)->{
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    moveFlag |= 1 << FLAG_RIGHT;
                    moveFlag &= ~(1 << FLAG_LEFT);
                    force = true;
                    return true;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    moveFlag &= ~(1 << FLAG_RIGHT);
                    force = true;
                    return true;
            }
            return false;
        });

        binding.lightBtn.setOnClickListener((v) -> {
            if ((moveFlag & (1 << FLAG_LIGHT)) != 0) {
                moveFlag &= ~(1 << FLAG_LIGHT);
                // если свет выключен, меняем картинку кнопки и фон
                binding.lightBtn.setImageDrawable(getDrawable(R.drawable.ic_highlight_black_24dp));
                binding.lightBtn.setBackground(getDrawable(R.drawable.btn_simple));
            } else {
                // если свет включен, меняем картинку кнопки и фон
                moveFlag |= 1 << FLAG_LIGHT;
                binding.lightBtn.setImageDrawable(getDrawable(R.drawable.ic_highlight_off_black_24dp));
                binding.lightBtn.setBackground(getDrawable(R.drawable.btn_simple_hl));
            }
            force = true;
        });

        // работа с сетью в отдельном потоке
        Thread t = new Thread() {
            @Override
            public void run() {
                super.run();

                while (true) {
                    // повторяем отправка раз в 3 секунды
                    // или если была нажата какая либо кнопка
                    if (force || System.currentTimeMillis() - lastUpdate > 3000) {
                        update();
                        force = false;
                        lastUpdate = System.currentTimeMillis();
                    }
                }
            }
        };

        t.start();
    }

    private void update() {
        if (sock == null || !sock.isConnected() || sock.isClosed()) {
            binding.statusImg.setImageDrawable(getDrawable(R.drawable.ic_wifi_off));
            try {
                sock = new Socket();
                InetSocketAddress addr = new InetSocketAddress(SERVER_IP, SERVER_PORT);
                sock.connect(addr, 500);
            } catch (IOException e) {
                sock = null;
                e.printStackTrace();
            }
        } else if (sock.isConnected()) {
            binding.statusImg.setImageDrawable(getDrawable(R.drawable.ic_wifi_24dp));
            b[0] = (byte)moveFlag;
            try {
                sock.getOutputStream().write(b, 0, 1);
            } catch (IOException e) {
                e.printStackTrace();
                try {
                    sock.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                sock = null;
            }
        }
    }
}

Скачать готовое приложение можно по ссылке.

Если вдруг приложение не видит модуль ESP8266 попробуйте выключить передачу данных.

Вот так выглядет мое приложение

приложение для android esp8266

Результат на видео

При копировании материалов ссылка на https://terraideas.ru/ обязательна

Комментарии к статье: Машинка на ESP8266 своими руками

Нет ни одного комментария. Будьте первым!