
Переделка машинки на управление по Wi-Fi с помощью модуля ESP-01 на чипе ESP8266.
Была машинка от которой утерян пульт. Решил я ее переделать на Wi-Fi с управлением со смартфона. Так как есть несколько плат ESP-01 на чипе ESP8266, которые я не знаю куда применить, выбор пал на них.
Модуль ESP-01 пришлось доработать из за нехватки пинов. Подробнее в статье Дополнительные ноги на плате ESP-01. В результате мы имеем 5 дополнительных ног.
Машинка питалась от 4 батарей AAA. Напряжение 6 Вольт. Питание микросхемы ESP8266 заявлено 3-3.6V. Максимально модуль потребляет 200мА. Если поставить стабилизатор типа ams1117-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.
Управление 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 попробуйте выключить передачу данных.
Вот так выглядет мое приложение
Результат на видео
Комментарии к статье: Машинка на ESP8266 своими руками