
Дело было к вечеру, делать было нечего. Загорелся я как-то созданием универсальной гусеничной платформы для изучения Tenserflow. Причем хотелось сделать управление через ESP32, которая будет регулировать моторчики, даст в случае желание управление через браузер в локальной сети, а Raspberry Pi будет управлять ей, имитируя собственно меня. Плюс возможность запаять навечно ESP32 и обновлять по воздуху. Здесь я хочу изложить примерную хронологию создания.
Полный список запчастей:
Raspberry Pi 4b - 1 шт. // для машинного зрения.
Камера для Raspberry Pi - 1 шт.
ESP32 - 1 шт. // МК для управления моторчиками и в будущем для считывания показаний с датчиков движения.
TT мотор с металлической шестеренкой - 2 шт.
L298N - 1 шт. //драйвер мотора - можно, даже нужно использовать что-то компактней и свежей, у меня же он валялся без дела.
Гусеничные траки - 2 шт.
Аккумуляторные батареи типа 18650 - 2 шт.
Бокс для батареек - 1 шт.
Провода
Винты, гайки и стойки M2, M2.5 и M3.
Сказано - сделано, из Aliexpres были добыты вот такие гусеничные траки, под ТТ мотор в комплекте идет распечатанная втулка, которая совсем не подходит по размера - но это не проблема, есть 3д принтер.

Почему выбраны были именно гусеничные траки? Хотелось бы сказать, что мечтал о высокой проходимости, показателях сцепления с поверхностью, но не буду лгать, модель небольшого размера и мне просто хотелось именно на гусеницах.
Определившись со всеми комплектующими начал проектировать в Fusion 360. Главная цель была - минимализм, хотел поделку небольшого размера, с четко распланированным рабочим пространством, чего мне кажется я смог добиться, но судить не мне.
Отдельно хочется обратить ваше внимание на некоторые детали рамы:
Вот так выглядит отдельно рама:


Пришлось усилить раму (выделил красным) иначе под натяжением траков раму гнуло. Естественно всему виной материал из которого я печатал данную модель - обычная фотополимерная смола от Anycubic.

:Так же в самой модели предусмотрел быстрое снятие/установку траков с натяжением, а выглядит оно проще некуда:

В нижнюю левую часть вставляется опорный вал и ведется по пазу и вставляется винт, который с обратной стороны фиксируется гайкой.
Ну, а остальное обыденно - просто разъемы под все на свете:
Кнопку, разъемы TYPE-C, каналы для проводов, на всякий площадка под серву SG90 (дань китайским машинкам с HC-SR04).
Соответственно все детали под печать выглядят так:

Печатал все на Anycubic M3. Напечатав первую модель я сразу же решил реализовать ручное управление через телефон. Так как я далеко не программист, решил использовать готовую библиотеку, выбор пал на GyverPortal. Выглядит все вот так:

Да, неказисто, но оно работает, а это самое главное, потом можно все доделать/переделать, сейчас же цель придти к работающему опытному образцу. Код тоже явно далек от идеала, но он работает.
Код от пульта:
код пульта
#define AP_SSID "" #define AP_PASS "" #include <GyverPortal.h> GyverPortal ui; // Motor A int motor1Pin1 = 27; int motor1Pin2 = 26; int enable1Pin = 14; // Motor B int motor2Pin1 = 25; int motor2Pin2 = 33; int enable2Pin = 32; int valSlider; int push=0; int speed=0; void setup() { pinMode(motor1Pin1, OUTPUT); pinMode(motor1Pin2, OUTPUT); pinMode(enable1Pin, OUTPUT); pinMode(motor2Pin1, OUTPUT); pinMode(motor2Pin2, OUTPUT); pinMode(enable2Pin, OUTPUT); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); startup(); // подключаем конструктор и запускаем ui.attachBuild(build); ui.attach(action); ui.start(); } void build() { GP.BUILD_BEGIN(); //GP.THEME(GP_DARK); GP.THEME(GP_LIGHT); GP.TITLE("ESP32 ROBOT"); GP.HR(); M_BLOCK( GP.LABEL("Статус системы"); GP.BREAK(); GP.LABEL("19%?"); ); M_BLOCK_THIN_TAB( "Ручное Управление", GP.LABEL("Пульт"); M_BOX( //GP_CENTER, GP.BUTTON("btntopleft", " ⬉ "); GP.BUTTON("btntop", " △ "); GP.BUTTON("btntopright", " ⬈ "); ); GP.BREAK(); M_BOX( //GP_LEFT, GP.BUTTON("btnleft", " ◁ "); GP.BUTTON("btnactive", " ? "); GP.BUTTON("btnright", " ▷ "); ); GP.BREAK(); M_BOX( //GP_CENTER, GP.BUTTON("btnbottomleft", " ⬋ "); GP.BUTTON("btnbottom", " ▽ "); GP.BUTTON("btnbottomright", " ⬊ "); ); M_BOX(GP.LABEL("Скорость"); GP.SLIDER("sld", valSlider, 0, 255); ); ); GP.BUILD_END(); } void action() { // обработчик изменения состояния кнопок if (ui.clickInt("sld", valSlider)) { Serial.print("Slider: "); Serial.println(valSlider); } } void loop() { push=push-1; speed=valSlider; if (push<=0){ digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); analogWrite(enable1Pin, 0); analogWrite(enable2Pin, 0); } ui.tick(); // проверяем через свой флаг if (ui.hold("btntopleft")){ push=4; digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); analogWrite(enable1Pin, 255); analogWrite(enable2Pin, 140); } if (ui.hold("btntop")){ push=4; digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); analogWrite(enable1Pin, speed); analogWrite(enable2Pin, speed); } if (ui.hold("btntopright")){ push=4; digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); analogWrite(enable2Pin, 255); analogWrite(enable1Pin, 140); } if (ui.hold("btnleft")){ push=4; digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, LOW); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, HIGH); analogWrite(enable1Pin, speed); analogWrite(enable2Pin, speed); } if (ui.hold("btnactive")){ push=0; analogWrite(enable1Pin, 0); analogWrite(enable2Pin, 0); } if (ui.hold("btnright")){ push=4; digitalWrite(motor2Pin1, LOW); digitalWrite(motor2Pin2, HIGH); digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); analogWrite(enable1Pin, speed); analogWrite(enable2Pin, speed); } if (ui.hold("btnbottomleft")){ push=4; digitalWrite(motor2Pin1, HIGH); digitalWrite(motor2Pin2, LOW); digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); analogWrite(enable1Pin, 255); analogWrite(enable2Pin, 140); } if (ui.hold("btnbottom")){ push=4; digitalWrite(motor2Pin1, HIGH); digitalWrite(motor2Pin2, LOW); digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); analogWrite(enable1Pin, speed); analogWrite(enable2Pin, speed); } if (ui.hold("btnbottomright")){ push=4; digitalWrite(motor2Pin1, HIGH); digitalWrite(motor2Pin2, LOW); digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); analogWrite(enable2Pin, 255); analogWrite(enable1Pin, 140); } } // пишет в порт по своему таймеру void asyncPrint(const char* str) { static uint32_t tmr; if (millis() - tmr >= 500) { tmr = millis(); Serial.println(str); } } void startup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(AP_SSID, AP_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(WiFi.localIP()); }
На момент первых тестов, все выглядело примерно вот так:
Оно ездит, значит можно идти дальше. Далее была спроектирована крышка-крепеж для Raspberry Pi, на которую крепится 5В кулер и камера, цели закрыть от всего мира ее нет, по крайней мере пока что.
Вот пока и все для первой части, в ближайшее время планирую поменять бокс батареек на китайскую самоделку со встроенной зарядкой и регулятором напряжение, но про это и про мучения с Raspberry Pi второй части, если первая будет кому-нибудь интересна.




Если кого-нибудь заинтересовал данный проект, то я готов выложить исходники модели с соответствующими дальнейшими обновлениями. Так же готов к критике/вопросам/предложениям. Спасибо за прочтение!
