ESP32, RC카

샤시 조립#

샤시 부품#

번호부품사진수량용도 및 참고사항
1전륜 회전축
2
2베어링(대)
2
3스티어링 컵2
4커플러 고정핀2
5베어링(중)2
6(전륜) 육각 커플러
2
74
8나이록 너트(M4)2
9십자형 육각 복스1
10볼 조인트 커넥팅 로드1홀 간격 80.5mm 유지
11볼 조인트 커넥팅 로드1홀 간격 51mm 유지
12서보모터
MG996R
1
13ㄴ자 서보모터 브라켓2
14휠 베이스 고정판2
15서보 혼1(볼트 포함)
16나이록 너트
(M2.5)
1
17샤시 하판1
18DC엔코더 모터 & 커넥터2
19모터 브라켓
2
20샤프트2
21써클립2
22샤프트 베어링4
23기어
(무두볼트(Set Screw) 포함)
2
24육각 샤프트 커플링 커넥터2
25범퍼1
26샤시 상판1
27락스위치1
28황동 육각 스터드
(M3×22mm)
6휠 베이스 고정용 4개, 상판 고정용 2개
29황동 육각 스터드
(M3×16mm)
2범퍼 고정용 2개
30볼트
(Pan head, M4×6mm)
6모터 브라켓과 샤시 하판 고정용 6개
31볼트
(Bind-Washer head, M3×6mm)
10서보모터와 서보모터 브라켓 고정용 4개, 서보모터 브라켓과 샤시하판 고정용 4개, 범퍼 고정용 2개
32볼트
(Bind head, M3×8mm)
`14육각 스터드 고정용 12개, 범퍼 고정용 2개
33볼트
(Pan head, M2.5×10mm)
7커넥팅 로드 연결용 3개, 휠 베이스 연결용 4개
34볼트
(Flat head, M2.5×5mm)
4모터와 모터브라켓 고정
35육각렌치1기어 무두볼트용
36저항
1K or 5.1K
2(선택사항) 인코더 풀업저항
37홀센서 칩
SH41F(SOT-23타입)
2(선택사항) 홀 센서를 이중채널로 사용할 필요가 있는 경우에, 각 모터마다 1개씩 추가함
36종

전륜 조향 휠 조립#

사진 오른쪽부터 차례대로 8개의 부품을 끼워 휠을 조립한다. (왼쪽휠/오른쪽휠 각각 1개씩, 총 2개 조립)

  1. 먼저 검은색의 스티어링 컵에 큰 베어링을 삽입한다. (상당히 뻑뻑하므로 힘을 강하게 주면서 스티어링컵 안쪽 끝까지 밀어넣어야 한다.)

  1. 베어링이 장착된 스티어링 컵에 회전축을 끼운다. 이때, 회전축 중앙에 있는 작은 구멍이 (고정쇠를 끼울 수 있도록) 보여야 하며, 만약 구멍이 보이지 않으면, 이전 과정에서 베어링을 더 밀어넣어 스티어링 컵에 완전히 밀착시켜야 한다.

  1. 회전축이 나온 스티어링컵 밑바닥에 중간 크기의 베어링을 끼운다. 역시 뻑뻑하므로 강한 힘을 주어 밀어넣어야 한다.

  1. 회전축 중앙의 작은 구멍에 커플러 고정핀를 끼워 넣는다.

  1. 커플러 고정핀 끼워 넣는 홈이 파인 육각 커플러를 준비한 뒤, 커플러 고정핀이 꼽혀있는 방향을 고려하여 스티어링컵에 끼운다.

  1. 스티어링컵에 끼운 육각 커플러를, 휠의 안쪽에 끼워 넣으면(이때, 육각 커플러가 휠의 육각 홈 안쪽까지 완전히 밀착되도록 강한 힘을 가하면서 끼운다.) 휠 바깥쪽면에 회전축의 나사산이 나온다.

  1. 바퀴 바깥쪽으로 빠져나온 회전축 나사를 나이록 너트(M4)를 끼워 고정한다. (십자형 육각 복스로 나이록 너트를 돌릴때 ‘딱딱’하는 소리가 몇번 날때까지 돌리면 고정이 된다.)

  1. 동일한 방법으로 하나를 더 만들어 2개의 휠을 완성한다.

  1. 짧은 커넥팅 로드 1개와 긴 커넥팅 로드 1개, M2.5*10mm 나사(Pan head) 3개를 준비한 뒤,

  1. 오른쪽 휠 컵 암(Arm)에 있는 구멍 2개 중, 끝부분의 구멍(빨간색으로 표시된 부분)에

  1. 준비한 볼트(M2.5*10mm (Pan head)) 1개를 이용하여 긴 커넥팅 로드를 고정한다. 이때, 볼트의 끝 부분을 구멍 아래쪽에서 윗쪽으로 넣듯이 하여 고정한다.

​ 또한, 볼트의 끝부분이 반대편 구멍 바깥쪽으로 튀어나오지 않도록 조임의 정도를 조절한다.

  1. 같은 방법으로 왼쪽 휠 컵의 구멍에 긴 커넥팅 로드의 반대편을 고정한다. (이때, 볼트의 끝부분이 반대편 구멍 바깥쪽으로 튀어나오지 않도록 조임의 정도를 조절한다. )

  2. 오른쪽 휠 컵의 구멍 2개 중, 파란색으로 표시된 부분에는 동일한 볼트를 사용하여 짧은 커넥팅 로드를 연결한다. (이때, 볼트의 끝부분이 반대편 구멍 바깥쪽으로 튀어나오지 않도록 조임의 정도를 조절한다. )

  1. 전륜 휠 베이스 완성된 모습


조향 서보모터 조립#

  1. MG996R 서보모터를 준비한 후,

  1. 아래 회로도와 스케치를 참고하여, 서보모터의 초기 위치를 90도 위치로 회전시켜 둔다.

    • schematic

      MG996R갈색빨간색주황색
      ESP32GND5VGPIO 15
    • sketch

      const int ledPin = 15;  // corresponds to GPIO 15
      
      // setting PWM properties
      const int ledChannel = 0;
      const int freq = 50;
      const int resolution = 16;
      
      void setup()
      {
        // PWM Setup
        ledcSetup(ledChannel, freq, resolution);  // PWM CH0, Frequncy 50 Hz, 16bit resolution
        ledcAttachPin(ledPin, ledChannel);        // PWM CH0을 GPIO 15번으로 출력
      }
      
      void loop()
      {
        ledcWrite(ledChannel, 4910);              // 1638 ~ 8192 (90degree = 4910)
        delay(500);  
        }
      }
  2. ㄴ자 서보모터 브라켓, 브라켓 고정용 볼트(Pan-Washer head, M3×6mm) 8개, 서보 혼(고정용 볼트 1개 포함)를 준비한다.

  1. 와셔볼트 4개를 사용하여 ㄴ자 브라켓을 고정하고, 서버 혼도 고정용 볼트를 사용하여 장착한다. (아래 사진을 잘 보면서 MG996R 스티커의 글씨 방향, ㄴ자 브라켓이 고정된 방향, 서보 혼 방향(현재 사진에 보이는 서보 날개의 위치가 90도가 됨)에 유의하여 장착한다.)

  1. 샤시 하판에 와셔볼트 4개를 사용하여 서보모터를 고정한다.


샤시 하판에 휠 베이스 고정하기#

  1. 준비물: 서보모터가 고정된 샤시 하판, 황동 육각 스터드 M3×22mm 4개, 볼트 M3*8mm 8개, M2.5×10mm 4개, 부채꼴 모양의 휠 베이스 고정판 2개

  1. (위 1번 사진의) 빨간색으로 표시된 부분에 육각 스터드 4개를 고정한다.

  1. (위 1번 사진의) 파란색으로 표시된 부분의 구멍에는, 다음 그림과 같이 긴 커넥팅 로드가 붙어있는 쪽의 휠 컵 구멍(아래 사진에서 파란색으로 표시된 부분)을 맞추어 볼트(M2.5×10mm)로 고정시킨다. (이때, 양쪽 휠 컵의 암(Arm) 부분이 차량 전면을 향하도록 한 상태에서 연결해야 한다.)

  1. 고정이 완료된 상태에서 밑면을 보면 다음과 같으며,

​ 위에서 바라본 모습은 다음과 같다.

  1. (파란색 원으로 표시된 부분) 부채꼴 모양의 휠 베이스 고정판을 양쪽 휠에 있는 육각 스터드와 스티어링 컵의 구멍에 고정한다. (볼트 M3×8mm 8개, M2.5×10mm 4개 사용)

    (빨간색 원으로 표시된 부분) M2.5*10mm 나사와 나이록 너트(M2.5)를 사용하여, 짧은 커넥팅 로드를 서보 혼에 고정한다. 이때, 십자형 육각 복스로 나이록 너트를 돌릴때 ‘딱딱’하는 소리가 몇번 날때까지 돌리면 고정이 된다.)

​ 아래 사진은 완성된 전륜 조향 휠 부분을 정면에서 바라본 모습이다.


후륜 모터 파트 조립#

  1. 25GA370 엔코더 모터에 모터 커넥터를 끼우고, 볼트 (Flat head, M2.5×5mm) 2개를 사용하여 모터 브라켓를 고정한다.

  2. 베어링 2개, 샤프트, 써클립, 기어, 육각 샤프트 커플링 커넥터, 써클립(Circlip)을 준비한다.

  1. 샤프트 끝부분에 있는 홈에 써클립을 끼운다.

  1. 모터 브라켓 양쪽 끝에 있는 구멍에 베어링을 끼우고, 써클립을 끼운 샤프트를 밀어넣는다.

​ 샤프트를 끼운 후, 모터를 앞쪽과 뒷쪽에서 보았을때의 모습

  1. 모터에 부착되어 있는 작은 황동 기어에 흰색 플라스틱으로 된 기어가 맞물려지도록 샤프트에 끼우고, 기어 측면에 있는 작은 무두볼트(Set Screw) 2개를 육각렌치를 사용하여 적절하게 분배하여 조여준다. 이때 한쪽 무두볼트만 조여서 고정하면 기어에서 무두볼트가 빠지거나 고정이 되지 않을 수 있다. 이를 고려하여 양쪽의 나사를 적절히 분배하여 조이도록 한다.

  1. 육각 샤프트 커플링 커넥터를 준비하여 샤프트에 끼운 후, 커넥터 측면의 볼트를 돌려 고정한다. 이때, 고정하는 볼트의 끝부분이 샤프트의 편평한 면에 닿도록 조여준다.

  1. 같은 방법으로 후면 왼쪽 모터도 조립한다.

  2. 샤시 하판 뒷편 양쪽에 있는 삼각형 형태로 위치한 구멍에, 모터 브라켓을 올려놓고 볼트(Pan head, M4×6mm)를 사용하여 고정한다.

​ 오른쪽 모터를 샤시 후면에 고정한 모습이다. 같은 방법으로 왼쪽 후면 모터도 고정한다.


범퍼 조립#

  1. 육각 스터드(M3×16mm) 2개와 볼트 (Bind-Washer head, M3×6mm) 4개 준비

  1. 준비된 육각 스터드를 샤시 하판의 맨 앞쪽에 고정한다.

  1. 범퍼를 육각기둥에 끼운 후, 와셔볼트를 사용하여 고정한다.


샤시 상판 고정#

  1. 육각 스터드(M3×22mm) 2개를 서보모터와 DC엔코더모터 사이에 있는 구멍에 볼트(M3.0×8mm)로 고정한다.

​ 육각 스터드를 고정한 모습이다.

  1. 샤시 상판의 보호필름을 떼어내고, 육각 스터드 홀에 맟춰 올린 뒤,

  1. 볼트(Bind head, M3.0×8mm) 2개를 사용하여 상판을 고정한다.

​ 상판의 오른쪽 하단에 락스위치를 꼽아 고정한 뒤에, 배터리의 (+)라인에 직렬로 배선할 수 있도록 준비한다.


후륜 휠 장착#

  1. 육각 휠 너트 앞쪽에 있던 나사를 풀러낸 뒤,

  1. 바퀴를 끼우고, 풀러냈던 나사로 다시 고정한다.


샤시 완성#



ESP32 Parts#

완성된 샤시의 상판과 하판을 이용하여 ESP32, 브레드보드, 모터드라이버, 배터리, 전원모듈 등을 장작한다.


for RC Car (ESP32 + nRF24L01 + TB6612FNG + 25GA370 + MG996R)#

sketch#

RC Car에 장착하는 ESP32에 업로드

#include <SPI.h>
#include <RF24.h>
RF24 radio(4, 5);

long long address = 0x1234ABCDEFLL;

struct Value{
  uint16_t value_x;   // servo motor pwm
  uint16_t value_y;   // DC motor pwm
};

Value data;

int angle_x = 90;     // servo initial position (0~180)
int velo_y = 0;       // DC motor inital velocity
int direction_y = 0;  // DC motor direction
int i = 0;            // joystick center position calculation counter
int center_x = 0;     // joystick initial center_x 
int center_y = 0;     // joystick initial center_y
int ref_xl, ref_xr, ref_ya, ref_yb;    // Servo & DC rotation reference value from center position value

//servo setting
const int PIN_SERVO = 15;             // servo pwm pin
const int CH_SERVO = 2;               // servo pwm channel
const int servoFrequency = 50;        // servo pwm frequency (Hz)
const int servoResolution = 16;       // servo pwm resolution (bit) (12bit: 0~4095)

// RecvTime
unsigned long lastRecvTime = 0;

void centerData() {
  while(i < 100) {
    if(radio.available()){
      radio.read(&data, sizeof(Value));
      center_x = center_x + data.value_x;
      center_y = center_y + data.value_y;
      i++;
    }
  }
  center_x = center_x / 100;         // x축 조이스틱 센터값 (평균)
  center_y = center_y / 100;         // y축 조이스틱 센터값 (평균)

  ref_xl = center_x / 2;             // 좌회전 동작 기준값
  ref_xr = (4095 + center_x) / 2;    // 우회전 동작 기준값
  ref_ya = (4095 + center_y) / 2;    // accelator 동작 기준값
  ref_yb = center_y / 2;             // brake 동작 기준값
}

void recvData(){
  while(radio.available()){
    radio.read(&data, sizeof(Value));
    lastRecvTime = millis();  
  }
}

/*
// TB6612FNG right side & ESP32-WROOM-32D DEVKIT_C V4 left side pin order
const int PIN_PWMA = 32;  
const int PIN_INA1 = 33;
const int PIN_INA2 = 25;
const int PIN_STBY = 26;
const int PIN_INB1 = 27;
const int PIN_INB2 = 14;
const int PIN_PWMB = 12;
*/

// TB6612FNG right side & ESP32-WROOM-32 left side pin order
const int PIN_PWMA = 33;
const int PIN_AIN2 = 25;
const int PIN_AIN1 = 26;
const int PIN_STBY = 27;
const int PIN_BIN1 = 14;
const int PIN_BIN2 = 12;
const int PIN_PWMB = 13;

// channel setting (GPIO pin, channel)
// if STBY(CH0) = LOW, Standby states.
// if STBY(CH0) = HIGH, and INA1&INA2 have different value
// and PWMA has some value, CW or CCW rotation is made.

// Channel      CW   CCW  Stanby Stop Brake1 Brake2 Brake3
// PIN_STBY (0) HIGH HIGH LOW    HIGH  HIGH  HIGH   HIGH
// PIN_INA1 (1) HIGH LOW  *      LOW   HIGH  LOW    HIGH
// PIN_INA2 (2) LOW  HIGH *      LOW   HIGH  HIGH   LOW
// CH_PWMA  (3) PWM  PWM  *      HIGH  *     LOW    LOW

// PWM Channel setup
const int CH_PWMA = 0;
const int CH_PWMB = 1;
// PWM frequency and bit resolution setup
const int pwmFrequency = 10000;  // Hz
const int bitResolution = 8;     // pwm value: 0~255

// led setup
const int PIN_LED = 2;
int ledstate;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  radio.begin();
  radio.openReadingPipe(1, address);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening();

  // Servo PWM Setup
  ledcSetup(CH_SERVO, servoFrequency, servoResolution);  // PWM CH2, Frequncy 50 Hz, 12bit resolution
  ledcAttachPin(PIN_SERVO, CH_SERVO);                    // PWM CH2을 GPIO 15번으로 출력

  // center value
  centerData();
  
  pinMode(PIN_LED, OUTPUT);

  // tb6612fng setting
  pinMode(PIN_STBY, OUTPUT);
  pinMode(PIN_AIN1, OUTPUT);
  pinMode(PIN_AIN2, OUTPUT);
  pinMode(PIN_PWMA, OUTPUT);
  pinMode(PIN_BIN1, OUTPUT);
  pinMode(PIN_BIN2, OUTPUT);
  pinMode(PIN_PWMB, OUTPUT);
  
  // motor output(PWM) setting (channel, frequency, bit)
  ledcSetup(CH_PWMA, pwmFrequency, bitResolution);
  ledcSetup(CH_PWMB, pwmFrequency, bitResolution);
  ledcAttachPin(PIN_PWMA, CH_PWMA);
  ledcAttachPin(PIN_PWMB, CH_PWMB);
}

void loop() {
  ledstate = 0;
  unsigned long now = millis();

  if( now - lastRecvTime > 5){    // 5ms 마다 서보출력
    recvData();
    rotate_xy();

    move(1, velo_y, direction_y);               // motor A(right wheels), velo_y speed, moving direction
    move(2, velo_y, direction_y);               // motor B(left wheels), velo_y speed, moving direction

    if(ledstate > 0) {
      digitalWrite(PIN_LED, HIGH);
    }
    else {
      digitalWrite(PIN_LED, LOW);
    }
  }
}


void move(int motor, int speed, int direction) {   //Move specific motor at speed and direction
//motor: A(Right) -> 1, B(Left) -> 2
//speed: 0 is off, and 255 is full speed
//direction: 0 clockwise, 1 counter-clockwise

  digitalWrite(PIN_STBY, HIGH);         // move

  boolean inPin1 = HIGH;                // Defalut(direction=0) - Clockwise
  boolean inPin2 = LOW;

  if(direction == 1) {                  // Count-clockwise
    inPin1 = LOW;
    inPin2 = HIGH;
  }

  if(motor == 1){                       // if motor == 1, right wheel
    digitalWrite(PIN_AIN1, inPin1);
    digitalWrite(PIN_AIN2, inPin2);
    ledcWrite(CH_PWMA, speed);
  }
  else {                                // if motor is not 1, left wheel
    digitalWrite(PIN_BIN1, inPin1);
    digitalWrite(PIN_BIN2, inPin2);
    ledcWrite(CH_PWMB, speed);
  }
}

void stop() {
  digitalWrite(PIN_STBY, LOW);         // stand-by = stop
}

void rotate_xy() {
  Serial.print("x: "); Serial.print(data.value_x);
  Serial.print(", y: "); Serial.println(data.value_y);

  //x축 서보 구동 조건 설정
  if(data.value_x < ref_xl) {     
    if(angle_x > 55) {                 // servo 55~125 degree
      angle_x--;
    }
    else {
      angle_x = 55;
    }
    ledstate++;
  }
  else if(data.value_x > ref_xr) {
    if(angle_x < 125) {
      angle_x++;
    }
    else {
      angle_x = 125;
    }
    ledstate++;
  }
  else {
    if(angle_x < 90) {
      angle_x++;
    }
    else if(angle_x > 90) {
      angle_x--;
    }
    else {
      angle_x = 90;
    }
  }
  //servo_x.write(angle_x);
  servoWrite(CH_SERVO, angle_x);
  delay(5);                    // delay를 더 줄이면 서보 모터 기어에 무리가 감

  //y축 DC 구동 조건 설정
  if(data.value_y > ref_ya) {
    if(direction_y == 0) {
      if(velo_y < 255) {
        velo_y++;
      }
      else {
        velo_y = 255;
      }
    }
    if(direction_y == 1) {
      if(velo_y > 0) {
        velo_y--;
      }
      else {
        direction_y = 0;
        velo_y = 0;
      }
    }
    ledstate++;
  }
  else if(data.value_y < ref_yb) {
    if(direction_y == 1) {
      if(velo_y < 255) {
        velo_y++;
      }
      else {
        velo_y = 255;
      }
    }
    if(direction_y == 0) {
      if(velo_y > 0) {
        velo_y--;
      }
      else {
        direction_y = 1;
        velo_y = 0;
      }
    }
    ledstate++;
  }
  else {
    if(velo_y > 0) {
      velo_y--;
    }
    else {
      velo_y = 0;
    }
  }
}

// deg는 0~180도 까지
void servoWrite(int ch, int deg)
{
    int servoPWM = map(deg, 0, 180, 2720, 8191);  // 16bit (PWM 4.15% ~ 12.5%)
    ledcWrite(ch, servoPWM);
}

schematic#


16P & 4P jumper block 사용#

RC카를 제작해보면 프레임 조립 후 여러가지 구동을 위한 부품을 넣어야 하는데, 큰 브레드보드를 장착하기에는 공간이 좁아 조립이 어려울 수 있다. 그러므로 브레드보드를 제거하고 16P & 4P의 점퍼 블록을 사용해 RC카에 장착해본다.


16P 점퍼블록과 4P 점퍼블록, 그리고 2개의 와이어핀을 준비한다.


2개의 와이어핀을 4P 점퍼블록의 중앙에 꼽아넣음으로써, 4개의 핀소켓이 모두 연결되어 있는 상태가 되도록 만든다. (1개의 와이어만 넣어도 연결은 되지만, 헐거워 고정이 안되기 때문에 2개를 사용하는 것임)


16P 점퍼블록을 사용하여 ESP32 DevkitC V4 보드와 TB6612FNG 모터드라이브를 다음과 같이 연결한다.

32 - PWMA
33 - AIN2
25 - AIN1
26 - NC
27 - BIN1
14 - BIN2
12 - PWMB
GND - GND

ESP32의 5V핀에는 4P 점퍼블록을 꼽아, 5V핀을 3개 사용할 수 있도록 만들어준다. 제작 후 위에서 바라본 모습은 다음과 같다.



for Remote Controller (ESP32 + nRF24L01 + Dual Joystick)#

sketch#
  • 리모트 컨트롤러로 사용할 TTGO T-energy(ESP32)에 업로드한다.
  • TTGO T-energy를 컴퓨터와 연결하여 사용할 경우에는 반드시 18650 배터리를 제거하거나 스위치를 OFF상태로 두어야 한다.
  • 그러므로 스케치 업로드 시에는 안전한 사용을 위해 18650 배터리를 제거한 뒤 아래의 스케치를 업로드하도록 한다.
#include <SPI.h>
#include <RF24.h>
RF24 radio(4, 5);

long long address = 0x1234ABCDEFLL;

// for no interference, two joysticks must be used with each other ADC!  
// one joystick use ADC1: GPIO 36,39,32,33,34,35
// the other must use ADC2: GPIO 4,0,2,15,13,12,14,27,25,26
const int Jstick_x_pin = 25;           // Left_Right GPIO25
const int Jstick_y_pin = 32;           // Forward_Back GPIO32

struct Value{
  uint16_t value_x;
  uint16_t value_y;
};

Value data;


void setup() {
  Serial.begin(115200);
  
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

void loop() {
  data.value_x = Jstick(Jstick_x_pin);
  delay(10);
  data.value_y = Jstick(Jstick_y_pin);
  delay(10);
  radio.write(&data, sizeof(Value));

  //Serial.print("x: "); Serial.print(data.value_x);
  //Serial.print(", y: "); Serial.println(data.value_y);
}

uint16_t Jstick(int Jstick_Pin){
  return analogRead(Jstick_Pin);
}

schematic#


  • TTGO T-energy 에는 5V 단자가 1개 있음.
  • 2개의 조이스틱에 5V가 각각 입력되어야 하므로, TTGO T-energy의 5V 단자를 2개로 분기할 수 있는 Y자형 케이블을 제작하여 사용하도록 한다.

​ 위 형태는 (오른쪽) 1개의 (F)단자를 (왼쪽) 3개의 (F)단자로 분기하는 형태의 케이블이다.

​ 이를 참고하여 1개의 단자를 2개의 단자로 나눠주는 케이블을 제작하면 된다.