esp32 서보모터 PWM제어

SG90, MG90S, MG966R 서보모터

datasheet


사용법

  1. ESP32는 16개의 PWM채널이 있으므로, 최대 16개의 서보모터를 동시에 제어할 수 있다.

  2. 서보모터 데이터 시트의 스펙상으로는…

    • 대부분의 서보모터(SG90, MG90S, MG966R 등 포함)는 위의 그림처럼 20ms가 1주기가 되므로, PWM frequency를 50Hz로 설정한다.

    • 20ms가 1주기이고, HIGH 펄스가 2ms 지속될 때 180º 회전을 한다.

    • 그러므로, 전체 펄스 폭의 1/10만큼 HIGH 펄스가 주어지면 180º 회전을 한다.

    • Resolution이 16비트라면 65535의 1/10에 해당하는 6554의 값이 duty값으로 주어지면 서보모터는 180º 회전을 한다.

    • 데이터 시트에 나온 스펙에 따라서, 16비트 해상도에서 duty값에 따른 회전 각도

      • PWM 5% : 3277 → 0º

      • PWM 7.5% : 4915 → 90º

      • PWM 10% : 6554 → 180º

        라고 생각했으나, 실제로는 이와 달랐다.

  3. MG90S

    • 16비트 해상도(0~65535)에서 duty값에 따른 회전 각도는

      • PWM 2.5% : 1639 → 0º
      • PWM 7.5% : 4915 → 90º
      • PWM 12.5% : 8191 → 180º
    • 즉, 각도에 따른 PWM값은 다음과 같이 지정한다.
      $$
      PWM = 2^{16} \times 0.1 \times \frac{angle}{180} + 2^{16} \times 0.025 = 2^{16} \times 0.1 \times (\frac{angle}{180} + 0.25)
      $$

    • 그리고, 이 서보모터 스펙상 180도 회전을 하지만 PWM 값을 조절하면 최대 190도 정도까지는 회전한다.

    • 그러므로, map 함수를 사용하여 다음과 같이 각도를 조절한다.

      duty = map(deg, 0, 180, 1638, 8191);

    • 이에 따라, 12비트 해상도(0~4095)에서 duty값에 따른 회전 각도는

      • PWM 2.5% : 103 → 0º
      • PWM 7.5% : 307 → 90º
      • PWM 12.5% : 511 → 180º
  4. MG996R

    duty = map(deg, 0, 180, 2720, 8191);

    가 가장 적당한 값을 나타내었다. (4.15% ~ 12.5%) (이유는 잘 모르겠음)

  5. 이런 내용을 종합해 볼때, 기기 데이터시트의 스펙과는 관계없이, 테스트를 통해 경험적으로 값을 찾아야 하는 듯…

  6. delay( );

    • 서보 회전 속도를 조절하며, 숫자가 작을 수록 빠르게 회전한다.

    • 이동하는 각도만큼 돌아갈 수 있는 충분한 시간을 주어야한다.

    • (전류가 약하면) delay( ) 값이 너무 작을 경우 원하는 각도의 회전을 완성하지 못할 수도 있다.

    • 전류가 충분하면 delay값을 줄여도 됨.



Servo 라이브러리를 이용하지 않는 경우

일반적으로 아두이노에서 서보모터를 사용하는 경우 아두이노 IDE에 기본적으로 포함되어 있는 <Servo.h> 라이브러리를 사용하면 되지만 이 라이브러리는 ESP32에서 사용할 수 없다. 그러므로 ESP32에 맞는 별도의 Servo.h 라이브러리를 다운로드 받아 사용해야하는 불편함이 있다. 또한 ESP32servo와 같은 ESP32용 라이브러리를 사용할 경우, 라이브러리에 따라 ledcWrite를 사용할 수 없는 경우도 있다.

그러므로, 여기서는 라이브러리를 사용하지 않고 PWM을 사용하여 ESP32로 MG90 서보모터를 제어해보고자 한다.


schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const int ledPin = 15;  // corresponds to GPIO 15

// setting PWM properties
const int ledChannel = 0;
const int freq = 50;
const int resolution = 16;

int deg, duty;

void setup()
{
// PWM Setup
ledcSetup(ledChannel, freq, resolution); // PWM CH0, Frequncy 50 Hz, 16bit resolution
ledcAttachPin(ledPin, ledChannel); // PWM CH0을 GPIO 15번으로 출력
}

void loop()
{
for (deg = 0;deg <= 180; deg++) {
servoWrite(ledChannel, deg);
}
for (deg = 180;deg >= 0; deg--) {
servoWrite(ledChannel, deg);
}
}

// deg는 0~180도 까지
void servoWrite(int ch, int deg)
{
duty = map(deg, 0, 180, 1638, 8192);
ledcWrite(ch, duty);
delay(15); // delay를 줄이면 180도가 완전히 돌지 않음
}

sketch: 시리얼 모니터에 입력한 문자로 서보모터 회전시키기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const int ledPin = 15;  // corresponds to GPIO 15

// setting PWM properties
const int ledChannel = 0;
const int freq = 50;
const int resolution = 16;

int deg = 90;
int duty;

void setup() {
Serial.begin(9600);

// PWM Setup
ledcSetup(ledChannel, freq, resolution); // PWM CH0, Frequncy 50 Hz, 16bit resolution
ledcAttachPin(ledPin, ledChannel); // PWM CH0을 GPIO 15번으로 출력

servoWrite(ledChannel, deg);
}

void loop()
{
while(Serial.available() > 0)
{
char flag=Serial.read();
delay(2);

if(flag=='l') {
deg = deg - 5;
if(deg < 0) {
deg = 0;
}
}

if(flag=='r') {
deg = deg + 5;
if(deg > 180) {
deg = 180;
}
}
servoWrite(ledChannel, deg);
}

// deg는 0~180도 까지
void servoWrite(int ch, int deg)
{
duty = map(deg, 0, 180, 1638, 8192);
ledcWrite(ch, duty);
delay(15); // delay를 줄이면 180도가 완전히 돌지 않음
}


2축 서보모터 거치대 조립 및 테스트

거치대 조립

첫번째 단계로 서보모터 거치대를 조립하고 서보모터 2개를 장착한다.

SG90 서보 2개를 준비하고

니퍼로 아래 빨간 동그라미로 표시한 부분의 크기에 맞추어, 서보모터 날개를 잘라낸 뒤

아래 형태대로 조립한다.

날개를 나사로 조인 부분에 순간접착제를 살짝 뿌려, 완전히 고정시킨다. (단, 서보모터의 회전축 부분에는 접착제가 뭍지 않도록 주의한다.)


schematic


sketch: 두개의 서보모터를 180도 회전시키기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// for horizontal Servo PWM properties
const int ledPin_hori = 15; // corresponds to GPIO 15
const int ledChannel_hori = 1;
const int freq_hori = 50;
const int resolution_hori = 16;

// for vertical Servo PWM properties
const int ledPin_vert = 2; // corresponds to GPIO 2
const int ledChannel_vert = 0;
const int freq_vert = 50;
const int resolution_vert = 16;

int deg_hori, deg_vert, duty;

void setup()
{
// PWM Setup
ledcSetup(ledChannel_hori, freq_hori, resolution_hori); // PWM CH1, Frequncy 50 Hz, 16bit resolution
ledcAttachPin(ledPin_hori, ledChannel_hori); // PWM CH1을 GPIO 19번으로 출력
ledcSetup(ledChannel_vert, freq_vert, resolution_vert); // PWM CH0, Frequncy 50 Hz, 16bit resolution
ledcAttachPin(ledPin_vert, ledChannel_vert); // PWM CH0을 GPIO 18번으로 출력
}

void loop()
{
for (deg_hori = 0;deg_hori <= 180; deg_hori++) {
servoWrite(ledChannel_hori, deg_hori);
}
for (deg_vert = 0;deg_vert <= 180; deg_vert++) {
servoWrite(ledChannel_vert, deg_vert);
}
for (deg_hori = 180;deg_hori >= 0; deg_hori--) {
servoWrite(ledChannel_hori, deg_hori);
}
for (deg_vert = 180;deg_vert >= 0; deg_vert--) {
servoWrite(ledChannel_vert, deg_vert);
}
}

// deg는 0~180도 까지
void servoWrite(int ch, int deg)
{
duty = map(deg, 0, 180, 1638, 8192);
ledcWrite(ch, duty);
delay(15); // delay를 줄이면 180도가 완전히 돌지 않음
}


Joystick 1개를 사용하여 2개의 서보모터 제어하기

schematic

sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// for horizontal Servo PWM properties
const int ledPin_hori = 15; // corresponds to GPIO 15
const int ledChannel_hori = 1;
const int freq_hori = 50;
const int resolution_hori = 16;

// for vertical Servo PWM properties
const int ledPin_vert = 2; // corresponds to GPIO 2
const int ledChannel_vert = 0;
const int freq_vert = 50;
const int resolution_vert = 16;

const int Jstick_LR_pin = 25; // Left_Right GPIO25
const int Jstick_FB_pin = 32; // Forward_Backward GPIO32

int Jstick_LR, Jstick_FB, deg_hori, deg_vert, duty;

void setup() {
Serial.begin(115200);

// Servo PWM Setup
ledcSetup(ledChannel_hori, freq_hori, resolution_hori); // PWM CH1, Frequncy 50 Hz, 16bit resolution
ledcAttachPin(ledPin_hori, ledChannel_hori); // PWM CH1을 GPIO 19번으로 출력
ledcSetup(ledChannel_vert, freq_vert, resolution_vert); // PWM CH0, Frequncy 50 Hz, 16bit resolution
ledcAttachPin(ledPin_vert, ledChannel_vert); // PWM CH0을 GPIO 18번으로 출력
}


void loop(){
PrintValue();

Jstick_LR = analogRead(Jstick_LR_pin);
deg_hori = map(Jstick_LR, 0, 4095, 0, 180);
servoWrite(ledChannel_hori, deg_hori);

Jstick_UD = analogRead(Jstick_UD_pin);
deg_vert = map(Jstick_FB, 0, 4095, 0, 180);
servoWrite(ledChannel_vert, deg_vert);

delay(15);
}

// deg는 0~180도 까지
void servoWrite(int ch, int deg)
{
duty = map(deg, 0, 180, 1638, 8192);
ledcWrite(ch, duty);
delay(15); // delay를 줄이면 180도가 완전히 돌지 않음 (최소값 15)
}

void PrintValue(){
Serial.print(analogRead(Jstick_LR));
Serial.print (" - ");
Serial.print(analogRead(Jstick_FB));
Serial.println (" Joystick Value");
}


ESP32Servo 라이브러리 사용시

library for ESP32

스케치> 라이브러리 포함하기> 라이브러리 관리 에서 “esp32servo“ (by Kevin Harrington) 검색 후 설치


schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <ESP32Servo.h>

Servo servo_x; // create servo object to control a servo
Servo servo_y; // 16 servo objects can be created on the ESP32

int pos = 0; // variable to store the servo position

// Recommended PWM GPIO pins on the ESP32 include 2,4,12-19,21-23,25-27,32-33
int servo_xPin = 15;
int servo_yPin = 2;

void setup() {
// Allow allocation of all timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
servo_x.setPeriodHertz(50); // standard 50 hz servo
servo_y.setPeriodHertz(50);
servo_x.attach(servo_xPin, 500, 2500); // attaches the servo on pin 25 & 32 to the servo object
servo_y.attach(servo_yPin, 500, 2500);
// using default min/max of 1000us and 2000us
// different servos may require different min/max settings
// for an accurate 0 to 180 sweep
}

void loop() {
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
servo_x.write(pos); // tell servo to go to position in variable 'pos'
servo_y.write(180-pos);
delay(5); // waits 5ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
servo_x.write(pos); // tell servo to go to position in variable 'pos'
servo_y.write(180-pos);
delay(5); // waits 5ms for the servo to reach the position
}
}
Author

chemidot

Posted on

2020-10-19

Updated on

2021-11-17

Licensed under

댓글