Modul Praktikum Universitas Pertahanan Minggu ke-4
Differential Steering & Ackermann Steering
Pada minggu sebelumnya, kita telah mempelajari jenis-jenis steering yang ada pada UGV (unmanned ground vehicle). Kali ini, kita akan memperdalam modifikasi dalam skid steering. Skid steering, atau dapat juga dikenal dengan differential steering, merupakan jenis steering sebuah kendaraan dengan memberikan torsi lebih besar kepada satu sisi penggerak kendaraan dibandingkan dengan sisi lainnya.
Kombinasi antara differential steering dengan Ackermann steering akan memiliki beberapa keuntungan, seperti:
1. Putaran yang lebih halus
Kombinasi differential steering dengan Ackermann steering memungkinkan kendaraan untuk berputar lebih halus tanpa selip
2. Handling yang lebih baik
Kombinasi differential steering dengan Ackermann steering dapat memberikan pengendalian yang lebih baik dalam belokan akibat tidak adanya selip.
3. Meminimalisir aus pada roda penggerak kendaraan
Akibat kombinasi skid steering dengan Ackermann steering, area roda yang mengalami selip lebih kecil dibandingkan differential steering konvensional. Dengan demikian, umur roda akan menjadi lebih panjang.
Pada praktikum ini, kita akan menggunakan line-follower robot dengan menggunakan kombinasi kedua jenis steering yang sudah disebutkan sebelumnya.
Pinout Line Follower Robot
Dari diagram yang diberikan, didapatkan define pinout sebagai berikut,
#define MLENCA 33
#define MLENCB 35
#define MRENCA 32
#define MRENCB 34
#define PPRGREY ???
#define PPRORANGE ???
#define SRVX 2 // Untuk ackermen / buzzer
#define MLEN 13
#define MLIN1 17 //5
#define MLIN2 18
#define MREN 27
#define MRIN1 19
#define MRIN2 23
#define SSA 15
#define SSB 4
#define SSC 16
#define SSD 5 // 17
#define SZ 39
Pinout ini nantinya akan digunakan dalam pemrograman untuk line follower robot.
Pemrograman Line Follower
Pada praktikum ini, kita akan melakukan praktik untuk pemrograman line follower. Untuk melakukan pemrograman ini, diperlukan beberapa software berikut, seperti:
1. Visual Studio Code (https://code.visualstudio.com/) 2. PlatformIO
Setup
Untuk melakukan visual studio code (VSC), Anda dapat membuka tautan yang telah diberikan di atas.
Lalu, untuk menambahkan PlatformIO, Anda dapat menambahkannya pada tab extension di sebelah kiri VSC
Setelah itu, carilah PlatformIO dan klik install. Jika PlatformIO sudah sukses terinstall, akan terlihat lambang berikut.
Pembuatan project folder
Setelah menginstall PlatformIO, Anda akan diminta untuk menambahkan projek baru untuk platform.io. Untuk melakukan hal tersebut, Anda dapat memilih tab PlatformIO dan memilih PIO Hom pada Quick Acces
Setelah itu, Anda dapat memilih “New Project” seperti gambar berikut
dan sesuaikan seperti format berikut sesuai dengan kebutuhan kita
Struktur folder project PlatformIO
Untuk memprogram lebih lanjut, Anda harus memahami struktur dari folder project platformIO.
Terdapat folder library untuk menambahkan library. Lalu, terdapat folder src untuk menambahkan file-file yang akan dicompile nantinya. Lalu, terdapat juga platformio.ini yang merupakan file initialize dari project platformio. Anda dapat menambahkan project dependencies pada file platfornio.ini ini.
Penambahan Library/Dependencies
Anda akan membutuhkan beberapa library Arduino/dependencies. Dependencies ini dapat ditambahkan pada tab PlatformIO. Lalu, pilih bagian “Libraries” pada quick access tab PlatformIO
Akan tetapi, untuk praktikum ini, Anda tidak diperlukan untuk menambahkan library secara manual.
Anda dapat menginstall library dengan mengubah initialize file “platformio.ini” seperti berikut,
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html [env:esp32doit-devkit-v1]
platform = espressif32 board = esp32doit-devkit-v1 framework = arduino
monitor_speed = 115200 lib_deps =
ayushsharma82/AsyncElegantOTA@^2.2.6 esphome/AsyncTCP-esphome@^1.2.2
ottowinter/ESPAsyncWebServer-esphome@^3.0.0 adafruit/Adafruit GFX Library@^1.10.13 bblanchon/ArduinoJson@^6.19.4
Lalu, Anda dapat menambahkan main.cpp pada folder src dan menyalin program berikut,
#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <Adafruit_GFX.h>
// install lib berikut di platformio (buka terminal dari menu platformio, jalankan pio lib -g install https://github.com ...)
#include <Adafruit_SH1106.h> //https://github.com/nhatuan84/esp32-sh1106-oled
#include <ArduinoJson.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include "SPIFFS.h"
#define CHN_MTRDC 1
#define CHN_MTRDCR 2
#define CHN_MTRSRV 4
#define PWMRES 8
#define MLENCA 33
#define MLENCB 35
#define MRENCA 32
#define MRENCB 34
#define PPRGREY 40//???
#define PPRORANGE 40//???
#define SRVX 2 // Untuk ackermen / buzzer
//#define MLEN 13
#define MLIN1 17 //5
#define MLIN2 18 //#define MREN 27
#define MRIN1 19
#define MRIN2 23
#define SSA 15
#define SSB 4
#define SSC 16
#define SSD 5 // 17
#define SZ 39
#define R 0.03
#define W 0.17
Adafruit_SH1106 display(SDA, SCL);
// Create AsyncWebServer object on port 80 AsyncWebServer server(80);
String ssid="loop-1";
String password="12345678";
IPAddress IP_ap = {192, 168, 1, 1};
IPAddress gateway_ap = {192, 168, 1, 1};
IPAddress NMask_ap = {255, 255, 255, 0};
int btn1,btn2,btn3;
int mtrSRV_freq = 50;
int mtrSRV_res = 16;
int mtrDC_freq = 50;
int mtrDC_res = 8;
int mtrDC_dutycycle = 0;
int mtrDC_dutycycleR = 0;
int mtrSRV_dutycycle = 0;
int mtrSRV_deg = 0;
int delayControl = 1000;
long countL=0,countR=0;
float x_awal = 0, y_awal = 0, shi_init = 0.0;
float x_final = 0, y_final = 0;
float x_d, y_d, dx_d, dy_d, shi_d, dx_ref, dy_ref, dshi_ref, dqr_ref, dql_ref;
float x=0.0, y=0.0, x_err=0.0, y_err=0.0, shi=0.0, dshi=0.0, v=0.0, shi_old=0.0;
float dx=0.0, dy=0.0;
float Kp = 1.0;
float ql = 0, qr = 0;
float dql = 0, dqr = 0;
float Tfinal = 0;
float t = 0;
float dt = 0.01;
int mode_run = 0;
bool running=false;
float err=0;
byte bacasensor(int index) { // if (index==8) index=16;
// else if(index==16) index = 8;
digitalWrite(SSA, (index & 0x01));
digitalWrite(SSB, ((index>>1) & 0x01));
digitalWrite(SSC, ((index>>2) & 0x01));
digitalWrite(SSD, ((index>>3) & 0x01));
return (digitalRead(SZ))? 1:0;
}
void SetServoPos(float pos) {
uint32_t duty = (((pos/180.0)
*2000)/20000.0*65536.0) + 1634;
// convert 0-180 degrees to 0-65536
ledcWrite(CHN_MTRSRV,duty);
// set channel to pos }
void handleWeb( void * pvParameters ){
printf("handleWeb running on core %d\n",xPortGetCoreID()); // on Serial int i;
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("request web /");
request->send(SPIFFS, "/index.html", "text/html",false);
});
server.on("/pavicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(404, "text/plain", "Not found");
});
server.on("/nama", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(404, "text/plain", "ABDUL MUIS");
});
server.on("/lastdata", HTTP_GET, [](AsyncWebServerRequest *request){
String sensor="";
err = 0;
int baca=0;
int temp =0;
int n=0;
for (int i=15; i>=0; i--) { temp = bacasensor(i);
if (temp) { n++;
baca +=i+1;
}
// if ((i==7)||(i==15)) baca=0;
sensor=sensor+" "+String(bacasensor(i));
}
err = (float)baca/(float)n - 8.5;
StaticJsonDocument<2000> myArray;
myArray["sensor"]=sensor;
myArray["err"]=err;
myArray["left"]["dutycycle"]=mtrDC_dutycycle;
myArray["right"]["dutycycle"]=mtrDC_dutycycleR;
myArray["left"]["enc"]=countL;
myArray["right"]["enc"]=countR;
String jsonString;
serializeJson(myArray, jsonString);
request->send(200, "application/json", jsonString);
});
server.on("/update", HTTP_PUT, [](AsyncWebServerRequest *request){
if (request->hasParam("delay")) {
delayControl = request->getParam("delay")->value().toInt();
request->send(200, "text/plain", "Berhasil update delay "+String(delayControl));
}
if (request->hasParam("stop")) { running=false;
request->send(200, "text/plain", "Berhasil stop ");
}
if (request->hasParam("start")) { running=true;
request->send(200, "text/plain", "Berhasil start ");
}
if (request->hasParam("run")) {
mode_run = request->getParam("mode")->value().toInt();
x_awal = x; y_awal=y;
x_final = x + 0.5; y_final = 0.0;
Tfinal = 3.0;
t = 0;
running=true;
request->send(200, "text/plain", "Mulai Jalan ");
}
if (request->hasParam("srvdeg")) {
mtrSRV_deg = request->getParam("srvdeg")->value().toInt();
SetServoPos(mtrSRV_deg);
request->send(200, "text/plain", "Berhasil update servo Deg "+String(mtrSRV_deg));
}
if (request->hasParam("dcfreq")) {
mtrDC_freq = request->getParam("dcfreq")->value().toInt();
ledcWriteTone(CHN_MTRDC, mtrDC_freq);
ledcWrite(CHN_MTRDC, mtrDC_dutycycle);
ledcWrite(CHN_MTRDCR, mtrDC_dutycycleR);
request->send(200, "text/plain", "Berhasil update dcfreq "+String(mtrDC_freq));
}
if (request->hasParam("dcdutycycle")) {
mtrDC_dutycycle = request->getParam("dcdutycycle")->value().toInt();
mtrDC_dutycycleR = mtrDC_dutycycle;
ledcWrite(CHN_MTRDC, mtrDC_dutycycle);
ledcWrite(CHN_MTRDCR, mtrDC_dutycycleR);
request->send(200, "text/plain", "Berhasil update dcdutycycle "+String(mtrDC_dutycycle));
}
if (request->hasParam("dcdutycycleR")) {
mtrDC_dutycycleR = request->getParam("dcdutycycleR")->value().toInt();
ledcWrite(CHN_MTRDCR, mtrDC_dutycycleR);
request->send(200, "text/plain", "Berhasil update dcdutycycleR "+String(mtrDC_dutycycleR));
}
request->send(200, "text/plain", "Tidak ada yang di update");
});
server.serveStatic("/", SPIFFS, "/");
server.onNotFound([](AsyncWebServerRequest *request){ request->send(404, "text/plain", "Not found");
});
// Start ElegantOTA
AsyncElegantOTA.begin(&server);
server.begin();
for(;;){
delay(1000);
} }
void IRAM_ATTR encoderleft() { // countL++;return;
if (digitalRead(MLENCB))
countL += digitalRead(MLENCA)? 1:-1 ; else
countL -= digitalRead(MLENCA)? 1:-1 ; }
void IRAM_ATTR encoderright() { countR++;return;
if (digitalRead(MRENCB))
countR += digitalRead(MRENCA)? 1:-1 ; else
countR -= digitalRead(MRENCA)? 1:-1 ; }
void inverse_jacobian(float &dqr, float &dql, float dx,float dy, float dshi, float shi) { dqr=1.0/R*(cos(shi)*dx + sin(shi)*dy +W/2.0*dshi);
dql=1.0/R*(cos(shi)*dx + sin(shi)*dy -W/2.0*dshi);
}
void jacobian(float &dx, float &dy, float &dshi, float dqr, float dql, float shi) { dx=R/2.0*cos(shi)*(dqr+dql);
dy=R/2.0*sin(shi)*(dqr+dql);
dshi=R/W*(dqr-dql);
}
void controlLoop( void * pvParameters ){
printf("controlLoop running on core %d\n",xPortGetCoreID()); // on Serial // setup PWM motor
// init PWM kontrol stir robot ackerman digitalWrite(SRVX, LOW);
ledcSetup(CHN_MTRSRV, mtrSRV_freq, mtrSRV_res);
ledcAttachPin(SRVX, CHN_MTRSRV);
ledcWrite(CHN_MTRSRV, mtrSRV_dutycycle);
// init PWM kontrol motor kiri digitalWrite(MLIN1, LOW);
ledcSetup(CHN_MTRDC, mtrDC_freq, mtrDC_res);
ledcAttachPin(MLIN2, CHN_MTRDC);
ledcWrite(CHN_MTRDC, mtrDC_dutycycle);
// init PWM kontrol motor kanan digitalWrite(MRIN1, LOW);
ledcSetup(CHN_MTRDCR, mtrDC_freq, mtrDC_res);
ledcAttachPin(MRIN2, CHN_MTRDCR);
ledcWrite(CHN_MTRDCR, mtrDC_dutycycleR);
for(;;){
if (err>1) {
mtrDC_dutycycle = 187.5;
mtrDC_dutycycleR = 50;
} else if (err<-1) { mtrDC_dutycycle = 62.5;
mtrDC_dutycycleR = 150;
} else {
mtrDC_dutycycle = 125;
mtrDC_dutycycleR = 100;
}
if (running) {
// ledcWrite(CHN_MTRDC, mtrDC_dutycycle);
// ledcWrite(CHN_MTRDCR, mtrDC_dutycycleR);
// if ((mode_run==1)&&(t<Tfinal)) {
// x_d=(x_final-x_awal)*t/Tfinal + x_awal;
// y_d=(y_final-y_awal)*t/Tfinal + y_awal;
// dx_d=(x_final-x_awal)/Tfinal; // kecepatan / gradient // dy_d=(y_final-y_awal)/Tfinal;
// shi_d = atan2(dy_d,dx_d);
// dshi_ref= Kp/2.0 * (shi_d - shi);
// dx_ref = Kp*(x_d - x) ; // dy_ref = Kp*(y_d - y) ;
// inverse_jacobian(dqr_ref, dql_ref, dx_ref, dy_ref, dshi_ref, shi);
// mtrDC_dutycycle = (dql_ref>255) ? 255:dql_ref;
// mtrDC_dutycycleR = (dqr_ref>255) ? 255:dqr_ref;
// jacobian(dx,dy, dshi, dqr, dql, shi);
// // hitung x,y
// v=dx*cos(shi)+dy*sin(shi);
// dx = dx_ref;
// dy = dy_ref;
// qr = countR * 0.5;
// ql = countL * 0.5;
// shi=R/W*(qr-ql)+shi_init;
// x=x+v*cos((shi+shi_old)/2.0)*dt;
// y=y+v*sin((shi+shi_old)/2.0)*dt;
// }
// t += dt;
ledcWrite(CHN_MTRDC, mtrDC_dutycycle);
ledcWrite(CHN_MTRDCR, mtrDC_dutycycleR);
} else {
ledcWrite(CHN_MTRDC, 0);
ledcWrite(CHN_MTRDCR, 0);
}
vTaskDelay(delayControl/ portTICK_PERIOD_MS);
} }
// Initialize SPIFFS void initSPIFFS() {
if (!SPIFFS.begin(true)) {
Serial.println("An error has occurred while mounting SPIFFS");
}
Serial.println("SPIFFS mounted successfully");
}
void setup() {
Serial.begin(115200);
display.begin(SH1106_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64)
initSPIFFS();
WiFi.mode(WIFI_MODE_AP);
uint64_t chipid = ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes).
uint16_t chip = (uint16_t)(chipid >> 32);
ssid = "ALAT-" + String(chip,HEX);
WiFi.mode(WIFI_MODE_AP);
WiFi.softAP(ssid.c_str(), password.c_str(), 3,0,11); //ssid, passwd, ch, hidden, maxconn WiFi.softAPConfig(IP_ap, IP_ap, NMask_ap);
// declare pin motor pinMode(MLIN1,OUTPUT);
pinMode(MLIN2,OUTPUT);
pinMode(MRIN1,OUTPUT);
pinMode(MRIN2,OUTPUT);
pinMode(SRVX,OUTPUT);
// declare pin encoder pinMode(MLENCA,INPUT);
pinMode(MLENCB,INPUT);
pinMode(MRENCA,INPUT);
pinMode(MRENCB,INPUT);
// baca LINE deklarasi mux pinMode(SSA,OUTPUT);
pinMode(SSB,OUTPUT);
pinMode(SSC,OUTPUT);
pinMode(SSD,OUTPUT);
pinMode(SZ,INPUT);
// attach encoder interrupt (odometry) attachInterrupt(MLENCA, encoderleft, CHANGE);
attachInterrupt(MRENCA, encoderright, CHANGE);
// send Torque Reference to CANbus xTaskCreatePinnedToCore(
handleWeb, /* Function to implement the task */
"handleWeb", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
0, /* Priority of the task */
NULL, /* Task handle. */
1); /* Core where the task should run */
delay(1000); // send Torque Reference to CANbus xTaskCreatePinnedToCore(
controlLoop, /* Function to implement the task */
"controlLoop", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
0, /* Priority of the task */
NULL, /* Task handle. */
0); /* Core where the task should run */
delay(1000);
}
void loop() {
// put your main code here, to run repeatedly:
display.clearDisplay();
display.setTextSize(1.1);
display.setTextColor(WHITE, BLACK);
display.setCursor(0,0);display.println("Status Counter");
display.setCursor(0,20);display.println(String(countL)+" "+String(countR));
display.display();
delay(1000);
}
Compile dengan PlatformIO
Untuk men-compile menggunakan PlatformIO, Anda dapat menggunakan “build” pada tab platformIO.
Jika Anda sudah selesai mencompile program, Akan terlihat pesan pada terminal sebagai berikut,
Upload compiled binary ke line follower melalui OTA
Setelah mem-build program, Anda akan meng-upload hasil compile binary dari PlatformIO melalui OTA.
Anda dapat mendapatkan file hasil compile dair PlatformIO pada folder Anda menyimpan projek Anda, Contoh: D:\% USER%\Downloads\%NAMAPROJECT%\.pio\build\esp32doit-devkit-v1
Pada folder tersebut, seharusnya terdapat firmware.bin. File .bin inilah yang nantinya akan diupload melalui OTA ke dalam line follower robot.
Agar Anda dapat mengupload program, Anda harus tersambung ke wifi yang diberikan oleh line follower robot. Lalu, Anda harus mencari IP address yang disediakan oleh wifi tersebut. Anda dapat mencari IP address dengan membuka properties dari wifi yang disediakan
Anda akan mendapatkan IP address pada kolom IPV4 address
Lalu, salinlah IPv4 address yang didapatkan pada browser Anda seperti berikut https://%IPADDRESS%/update
Contohnya: https://192.168.0.100/update (menggunakan IP address sebelumnya)
Setelah itu, seharusnya akan tertampilkan laman berikut
Pilihlah firmware dan upload firmware.bin yang telah Anda build pada PlatformIO sebelumnya.
Menjalankan line follower
Setelah mengupload firmware, Anda masih harus mengupdate parameter yang akan diberikan pada linefollower. Untuk mengubah parameter tersebut, Anda harus mengakses URL berikut,
Lalu, aturlah parameter sesuai dengan keinginan Anda!
Borang Praktikum Modul Line Follower Robot
1. Buatlah .bin file untuk line follower robot! Lalu, ubahlah parameter duty cycle pada motor kiri dan kanan ketika sesuai dengan nilai berikut
Percobaan 1 Belok kiri:
Duty cycle motor kiri 150
Duty cycle motor kanan 50
Belok kanan:
Duty cycle motor kiri 50
Duty cycle motor kanan 150
Lurus:
Duty cycle motor kiri 100
Duty cycle motor kanan 100
Percobaan 2 Belok kiri:
Duty cycle motor kiri 188
Duty cycle motor kanan 50
Belok kanan:
Duty cycle motor kiri 63
Duty cycle motor kanan 150
Lurus:
Duty cycle motor kiri 125
Duty cycle motor kanan 100
Tulislah main.cpp kalian, temukan perbedaannya dari kedua percobaan dan jelaskan mengapa hal ini bisa terjadi!
Kemudian, untuk percobaan berikutnya, cobalah jalankan line follower robot pada track yang sudah disediakan menggunakan nilai pada percobaan 2. Lalu, bandingkan dengan line follower yang dijalankan pada area yang tidak memiliki track!
2. Apabila sensor pada line follower seperti percobaan yang telah dilakukan menghasilkan output “000000000011100”, ke manakah line follower akan berjalan?
3. Apabila sensor pada line follower seperti percobaan yang telah dilakukan menghasilkan output “111111111111111”, ke manakah line follower akan berjalan?
4. Buatlah kesimpulan untuk praktikum ini!
Kumpulkan file dalam bentuk .pdf ke link drive berikut dengan format Nomor Induk_Nama.pdf https://drive.google.com/drive/folders/1Zi6UXU92Aeph1nLNlZANaXDouuyQO7Zq?usp=sharing Deadline: 10/06/2023 Pukul 15.00