지금까지 Raspberry Pi - ESP32 통신 실험이 간단하여 쉽게 마무리가 될거라고 예상하였지만 지난 개발 일지에서도 해결이 되지 않아 생각보다 많은 시간이 소요되었다.
하지만 이번 개발 일지에서는 통신 실험에 성공하여 어떠한 방법으로 실험을 구성하였고, 지금까지 실패한 원인을 분석하였다.
또한 통신 구성 이외에도 여러 제어 실험들이 생각한대로 잘 이루어져, 이번 개발 일지에는 좋은 성과들이 많이 기록될 것 같다.
다른 일정 때문에 2주 정도 개발 일지를 미루다 이제 작성하였다.. 🙈
AI Turret System을 정상적으로 구동하기 위해선 5V~6V의 입력전압과 MAX 7.2A의 입 력전류가 필요하다.
결과적으로 컨버터를 이용하여 전원을 공급하기엔 해결해야 할 문제들과 떠안아야 할 부담이 많을 것으로 예상된다. 따라서 Sol 2)를 사용하여 전원 문제 해결을 결정했다.
신청한 물품을 수령 후 학교 실험실의 DC Supply를 사용하여 초기 실험을 진행할 계 획이다.
물품 수령 날짜는 7월 중으로 예상된다. AI Turret System을 구성하여 실험을 진행할 계획이다. 학교 실험실 DC Supply를 사용하여 전원을 공급한다. 예상처럼 정상적으로 구동한다면 최종적으로 Sol 2)의 DC Supply 방식을 사용하여 전원 공급한다.
이전 실험에서의 ESP32 환경에서 MPU6050의 Pitch 축을 기반으로 Stabilizer 기능을 구현하여 실험을 진행하였으며, 동일한 코드를 Roll 축에도 적용시켜 실험을 진행하였다.
위 사진처럼 Roll, Pitch 축을 기반으로 한 Stabilizer를 구성하여 실험을 진행하였으며, 프로그램 된 코드 상으로는 문제를 찾을 수는 없었지만 코드를 기반으로 동작을 시켜보면 Stabilizer가 안정화되지 못하고 MPU6050의 이동 방향과 동일하게 움직이는 것을 확인 할 수 있었다.
이러한 문제가 발생하는 이유로 Roll 축에서 받아오는 Data(angle_X)의 범위가 -90 ~ 90이 아닌 -180 ~ 180인 것을 모르고 Pitch와 동일하게 제어를 하였기 때문이었다.
이외에도, 실험을 위해 구성한 간의 Stabilizer Frame에서 MPU6050의 위치가 잘못 부착되었다는 사실도 알 수 있었다.
“MPU6050_light.h” 함수에서의 Roll의 측정범위와 Pitch의 측정범위가 다름
또한 Pitch는 Servo Motor와 동작 범위가 1:1 Scale을 가지지만 Roll은 Servo와 2:1 Scale을 가지게 되어 Servo Motor와 동작이 완전히 동기화되지 않는 것을 알 수 있었다.
위의 문제점을 해결하기 위해 아래의 사진의 “constrain” 함수를 사용하였으며 이 는 Min - Max 값을 입력하여 사용하고자 하는 값을 제한할 수 있는 함수이다. 이를 사용하여 Pitch 축과 동일하게 -90 ~ 90 범위만 사용할 수 있도록 "-180 ~ -90 범위"와 "90 ~ 180 범위"를 제한하였으며 아래와 동일하게 코드를 구성하였다.
지금까지 발생한 여러 문제들을 파악하여 이를 보완하고자 실험에 사용된 간의 Stabilizer 구성과 구동 코드를 수정하여 실험을 진행하였다. 또한 Stabilizer의 동작 속 도 및 정확도를 파악하기 위해 항상 수평면과 수평을 이루어져야 하는 Servo Motor 면 에 또 다른 MPU6050을 부착하여 총 2개의 ESP32를 사용하여 실험을 진행하였다.
#include <MPU6050_light.h>
#include <ESP32_Servo.h>
#include "Wire.h"
MPU6050 mpu(Wire);
Servo servo1;
Servo servo2;
const int servo_x = 4;
const int servo_Y = 5;
int gryo_X, gryo_Y;
long angle_X, angle_Y;
String gyro_data;
String servo_data;
void setup() {
Serial.begin(115200);
Wire.begin();
byte status = mpu.begin();
mpu.calcOffsets();
servo1.attach(servo_x);
servo2.attach(servo_Y);
}
void loop() {
mpu.update();
angle_X = mpu.getAngleX(); // -180 ~ 180
angle_Y = mpu.getAngleY(); // -90 ~ 90
int gryo_con = constrain(angle_X, -90, 90);
gryo_X = map(gryo_con, -90, 90, 0, 180);
gryo_Y = map(angle_Y, -90, 90, 180, 0);
gyro_data = "Roll : " + (String)angle_X + " " + "Pitch : " + (String)angle_Y;
servo_data = "Servo_X : " + (String)gryo_X + " " + "Servo_Y : " + (String)gryo_Y;
servo1.write(gryo_X);
servo2.write(gryo_Y);
Serial.println(gyro_data);
Serial.println(servo_data);
delay(10);
}
#include <MPU6050_light.h>
#include "Wire.h"
MPU6050 mpu(Wire);
int gryo_X, gryo_Y;
long angle_X, angle_Y;
String gyro_data;
void setup() {
Serial.begin(115200);
Wire.begin();
byte status = mpu.begin();
mpu.calcOffsets();
}
void loop() {
mpu.update();
angle_X = mpu.getAngleX(); // -180 ~ 180
angle_Y = mpu.getAngleY(); // -90 ~ 90
int gryo_con = constrain(angle_X, -90, 90);
gryo_X = map(gryo_con, -90, 90, 0, 180);
gryo_Y = map(angle_Y, -90, 90, 180, 0);
gyro_data = "Roll : " + (String)angle_X + " " + "Pitch : " + (String)angle_Y;
Serial.println(gyro_data);
delay(10);
}
"Stabilizer 측 ESP32"에 연결된 MPU6050의 Data를 활용하여 Stabilizer를 동작하게끔 설계하였으며 정상적인 동작을 하는지를 확인하기 위한 "Stabilizer Gyro Data 수집용 ESP32"에 MPU6050를 연결하여 실험값을 측정하였다.
이후 "Stabilizer 측 MPU6050"을 기울여 Roll, Pitch 축의 Servo Motor를 동작하게 만들 었으며 이때, "Gyro Data 수집용 MPU6050"의 출력값을 수집하여 각각의 입-출력값을 비교하여 보았다.
Roll과 Pitch 축의 Data에서 상호보안 되는 방향으로 90°의 차이가 발생하는 것을 보아 전반적으로 Servo Motor의 기능을 잘 수행하는 것을 확인할 수 있었다.
하지만 Gyro Data 수집용 ESP32 출력값을 살펴보면 Roll과 Pitch 축이 일정한 각도를 유지하고 있는 것이 아닌 6° ~ 7°의 오차를 보이고 있는 것을 확인할 수 있다.
이는 충분한 전원이 인가되지 않아 DataSheet에 기록된 성능대로 동작하지 않아 정상적인 동작을 하지 못한 것으로 판단된다.
실험에 사용된 HS-311 Servo Motor의 DataSheet를 살펴보면 정격전압이 4.8V ~ 6V인 것을 확인할 수 있으며 실험에서 HS-311에 인가한 전압은 ESP32의 System 전압인 정격전압에 한참 못 미치는 2.3V ~ 3.6V이며 심지어 2개의 Servo Motor를 사용하였기에 입력 전류도 병렬로 분할되어 충분한 전원이 인가되지 않았다.
이번 실험이 인가된 전원이 부족하여 Servo Motor가 느린 것은 해결 가능하다고 판단해도 Pitch의 정확도와 안정화에 비해 Roll에서 동일한 성능으로 출력되지 않는 것으로 보아, 또 다른 문제가 있는 것으로 판단된다.
이를 확인하기 위해 실제 System의 구성 환경과 동일한 환경에서 실험을 진행한 후, 판단을 해 보는 것이 이상적이라고 생각이 되며 추가적으로 Roll 축에 PID 제어를 사용하는 것에 대해 필요성을 느낄 수 있었다.
카메라를 통해 객체를 감지하고 감지된 객체를 추적하는 과정에서 Raspberry Pi는 객체 탐지 및 추적데이터 수집의 역할을 수행하며, ESP32는 수집된 데이터를 기반으로 Servo Motor를 이용한 Detecting을 수행한다.
Raspberry Pi에서는 총 6개의 UART가 있고 실험에서는 UART3를 추가 할당하여 진행하였다. 맵핑된 디바이스는 “/dev/ttyAMA2”를 사용한다.
ESP32는 SoftwareSerial이 없으므로 HardwareSerial을 통해 통신을 하고 업로드 포트에 해당하는 Serial0와 블루투스에 해당하는 Serial1을 제외하고 남아있는 Serial2를 사용한다.
import serial
ser = serial.Serial('/dev/ttyAMA2',115200)
While True:
print("servo : ")
servo = input() ser.write(servo.encode())
#include <HardwareSerial.h>
#include <ESP32_Servo.h>
HardwareSerial mySerial(2); //3개의 시리얼 중 2번 채널을 사용
Servo servo1;
const int servoPin = 4;
void setup() {
Serial.begin(115200); //기존의 기본 시리얼
mySerial.begin(115200, SERIAL_8N1, 16, 17); //추가로 사용할 시리얼. RX:12 / TX:13번 핀 사용
servo1.attach(servoPin);
pinMode(servoPin, OUTPUT);
}
void loop() {
String command = "";
if (mySerial.available() > 0) {
command = mySerial.readStringUntil('\n'); //시리얼2의 값을 수신하여 String으로 저장 int data = command.toInt();
Serial.println(command); //시리얼1에 시리얼2 내용을 출력
Serial.println(data); //string데이터를 int데이터로 변경 후 출력
servo1.write(data);
}
}
Raspberry Pi로부터 데이터 값을 String의 형태로 받아 저장하고 Serial Monitor로 확인, 서보모터에서 사용되는 데이터 자료형이 숫자이므로 int로 변환하여 Serial Monitor에 확인한다. String 데이터와 int 데이터 모두 정상적으로 시리얼 통신 완료하였다.
ESP32에서 Servo Motor를 구현하기 위한 과정이다. 우선 Arduino와 다르게 ESP32는 자체적인 Servo library가 없기에 "ESP32_Servo.h"라는 외부의 Library를 설치하여 실험을 진행하였다.
#include <ESP32_Servo.h>
Servo servo1;
const int val = 4;
const int servo_x = 5;
void setup() {
Serial.begin(115200);
servo1.attach(servo_x);
pinMode(val, INPUT);
}
void loop() {
int data = analogRead(val);
int servo_val = map(data, 0, 4095, 0, 180);
servo1.write(servo_val);
}
여기서 Servo Motor와 가변저항의 입·출력에 대한 Max-Min 값을 설정하기 위해 "map“ 함수를 사용하였으며 ESP32에 내장되어있는 ADC의 분해능이 0부터 4095이기 에 map 함수 내부의 입력값에 대한 Max-Min 설정을 아래와 같이 설정하였다.
Raspberry Pi와 ESP32의 Serial 통신과 ESP32의 Servo Motor 제어 코드를 결합하여 최종형태인 Raspberry Pi 데이터를 ESP32로 수신 받아 Servo Motor로 출력하는 실험이다.
추가로 Detecting에 사용되는 Servo Motor는 2개이므로 본 실험에서는 Raspberry Pi의 파이썬 코드를 2개의 데이터가 송신되는 코드로 변경하여 실험을 진행하였다.
import serial
ser = serial.Serial('/dev/ttyAMA2',115200)
While True:
print("servo1 : ")
servo1 = input()
print("servo2 : ")
servo2 = input()
data = servo1 + “,” + servo2 ser.write(data.encode())
#include <HardwareSerial.h>
#include <ESP32_Servo.h>
HardwareSerial mySerial(2); //3개의 시리얼 중 2번 채널을 사용
Servo servo1;
Servo servo2;
const int servoPin1 = 4;
const int servoPin2 = 5;
void setup() {
Serial.begin(115200);
mySerial.begin(115200, SERIAL_8N1, 16, 17); //추가로 사용할 시리얼. RX:12 / TX:13번 핀 사용
servo1.attach(servoPin1);
servo2.attach(servoPin2);
}
void loop() {
String command = "";
if (mySerial.available() > 0) {
command = mySerial.readStringUntil('\n'); //시리얼2의 값을 수신하여 String으로 저장 int first = command.indexOf(","); //“,”위치 index
int length = command.length(); //데이터의 길이
String str1 = command.substring(0,first); //첫번째 값
String str2 = command.substring(first+1,length); //두번째 값 int data1 = str1.toInt();
//기존의 기본 시리얼
int data2 = str2.toInt();
Serial.println(command); //시리얼1에 시리얼2 내용을 출력 Serial.println(data1);
Serial.println(data2);
servo1.write(data1);
servo2.write(data2);
}
}
Raspberry Pi의 입력 데이터는 2개의 input문을 통해 들어가고 2개의 데이터 사이를 ” , “를 사용하여 구분하였다.
ESP32에서는 Raspberry Pi에서 송신한 2개의 데이터를 구분하기 위해 indexOf 함수를 이용해 ” , “의 위치를 찾고, 0부터 ” , “까지를 첫 번째 데이터로 저장하였다.
그리고 ” , “다음부터 끝 지점인 length까지를 두 번째 데이터로 지정하여 해석한다. 이렇게 분석된 데이터를 Servo Motor가 받아들일 수 있는 숫자 데이터로 변환하고 Servo Motor의 출력을 만들어 주었다.
위 실험 결과에서 볼 수 잇듯이, Raspberry Pi로 입력한 데이터는 정확하게 Servo Motor의 출력으로 나타났으며, 이로써 Detecting을 위한 통신 메커니즘 구현의 기반을 완성했다.
기술적으로 매우 어려운 작업이 아니었음에도 불과하고 Raspberry pi - ESP32 간의 Serial 통신 실험을 성공하기까지 많은 시간이 소요되었다. 이는 ESP32에서 Serial 통신 을 구성할 때, Arduino와 동일한 구조를 가질 것이라 판단하고 "시리얼 통신 방식을 SoftwareSerial을 기반으로 실험 코드를 작성한 것"이 이유였다.
ESP32에서는 Arduino와 달리 3개의 UART(HardwareSerial) 통신 포트를 제공하며 이는 "Hardware Serial"을 통하여 SoftwareSerial을 설정하거나 접근하는 방식이었다.
이러한 동작 방식에 대해 전혀 알지 못하였기에 3 ~ 4주의 시간 동안 AI Turret System을 구성하는 중요한 부분인 보드 간의 통신을 할 수 없었던 것이다.
현재 1000장의 Object 사진 촬영을 완료했다. 풀숲, 흙밭, 놀이터, 맨땅 등등 여러 가지 환경에서 Object를 두고 다방면의 각도에서 데이터를 수집했다.
지금까지 1000장의 사진 중 800장을 Labeling 작업을 완료했다. 200장의 사진은 Labeling을 진행하지 않았는데, 이는 Labeling을 한 학습 Image를 사용하여 YOLO 모델을 커스텀하여 정확도 및 신뢰도를 판단하기 위함이다.
지금까지 수집한 Object에 대한 학습 Image를 사용하여 YOLO 모델을 커스텀할 계획이며, 이때 학습을 위한 학습 환경을 기존의 Raspberry Pi나 그보다 성능이 더욱 좋은 PC 환경이 아닌 Google Colab을 활용하여 Yolo 학습을 진행할 계획이다.
Colab에서 YOLO를 학습하여 출력된 모델 파일, Object의 이름이 담겨있는 파일, 학습 정보가 담겨있는 cfg 파일 등을 수집하여 Object를 탐지하기 위한 Raspberry Pi에 내장하여 목표를 Detecting 및 Tracking을 할 수 있도록 설계할 예정이다.
AI Turret System에서 Object를 탐지한 이후, Airsoft gun으로 대상을 조준하고 사격하기에 Airsoft gun을 제어하는 것은 이번 프로젝트에서 중요하게 여겨진다. System에 사용될 Airsoft gun은 이전에 소개된 기성품을 분해한 Gear Box를 사용하며, 이를 프로젝트를 구성하기 위해서는 3D Modeling Tool을 사용하여 모델링을 하여야 한다.
사격 기능에 필요한 부품으로는 기성품을 분해한 Gear Box와 탄창에서 공급된 탄알을 격발하기 전 잡아주기 위한 T자 Frame 및 탄알을 수급하기 위한 탄창으로 구성된다. 여기서 탄창은 부피가 너무 커 동작 구조를 파악하여 별도의 장치로 탄알을 수급하여 한다.
부품 설계에 사용된 3D Modeling Tool은 “Google SketchUP”이라는 프로그램이며, 3D Print를 사용하기 위한 Tool로는 “CURA”를 사용하였다.
T자 Frame
Gear Box
탄알 공급 및 고정 Frame
위 3개의 Parts로 사격 System은 구성될 것이며 1차로 “탄알 공급 및 고정 Frame” 을 3D Printer를 사용하여 출력한 후, Parts의 모델링 치수와 결합 방식을 고려하여 실 사용할 고정 Frame을 설계할 예정이다.
사격에 필요한 모든 부품을 결합하여 조립하면 위의 사진과 동일할 것으로 예상된다.