굽힘센서로 서보모터를 움직이는 연습

프로그래밍과 신체감각최종 업데이트:

굽힘센서를 굽힘으로서 서보모터를 움직이는 연습용 프로젝트입니다. 사용하는 재료는 이하와 같습니다.

PCA9685 (모터 드라이버)
DS3225(25kg 서보모터)
RDS5160(60kg 서보모터)
ESP32 Flex Sensor (굽힘 센서)
10kgΩ 저항

1. 연습의 개요

개요

동영상으로 움직임을 확인하기

이번에는 굽힘센서를 굽혔을때의 정도에 따라 서보모터를 회전시키는 연습을 해보겠습니다.

위의 영상이 이번 교재에서 최종적으로 얻을 수 있는 결과입니다.

굽힘센서는 필름으로 되어있기에 부드러운 소재에 붙히거나 어느정도 굽혀질 수 있는 소재에 붙힘으로서 사용할 수 있습니다.

그렇게 손으로 만지는 동작을, 모터를 회전시킨다는 물리적 현상으로 연결시킨다는 것이 연습의 포인트가 되겠습니다.

GitHub 페이지

 이 링크로부터 GitHub상의 코드를 다운로드 할수 있습니다.

녹색 Code버튼으로 압축파일을 다운로드 됩니다.

2. 환경의 준비

하드웨어(PCA9685 모터드라이버)

⚫︎ PCA9685 모터드라이버

서보모터는, 폭을 가지고 있는 펄스신호를 회전 할 각도로서 인식하여 그 각도까지 회전하는, 그러한 모터입니다. 회전할 때는 형식에 맞는 신호를 만들어 모터와 신호를 주고받을 필요가 있지요. 모터드라이버는 MCU(ESP32등의 마이크로컨트롤러) 와 모터 사이에서, 바로 그러한 이유로 존재합니다.

모터 드라이버의 사진

모터 드라이버의 사진

하드웨어 (서보모터)

⚫︎ DS3225(25kg Servo) とRDS5160(60kg Servo)

서보모터는 다른 모터와 다르게, 회전을 하기 위한 모터가 아닙니다. 물론 회전이 가능한 종류도 있습니다만, 기본적으로는 펄스신호를 받으면 정해진 하나의 각도까지 회전한다, 라는 원리로 움직입니다.

이번에는 회전력(토르크)이 어느 정도 큰 모터를 사용하겠습니다.

서보모터 (DS3225 25kg) 의 사진

서보모터 (DS3225 25kg) 의 사진

서보모터 (RDS5160 60kg) 의 사진

서보모터 (RDS5160 60kg) 의 사진

하드웨어 (ESP32 Devkit)

⚫︎ ESP32開発ボード

ESP32 는 Espressif 사가 제조하는 마이크로 컨트롤러입니다. 마이크로 컨트롤러중에서는 Arduino가 널리 알려져 있는데요, ESP32는 기본적으로 와이파이, 블루투스 통신 기능을 가지고 있고 탑재된 메모리양도 많은등 장점을 가지고 있습니다.

그렇다면 전부 이런 스펙을 가진 칩을 사용하면 되지 않나? 라고 생각할 수도 있겠습니다만, 일반적인 기능을 목적으로 하는 데스크탑 컴퓨터와 다르게, 임베디드 시스템이라는 영역에서는 높은 사양은 곧 높은 전기사용량을 의미합니다.

그렇기에 마이크로 컨트롤러를 개발하는 회사는 여러가지 타입의 라인업을 가지고 개발합니다. 그러한 다양한 측면을 고려하기에 어떻게 보면 복잡해 보이기도 하는데, 또한 단일하지 않은 폭넓음이 매력적이기도 합니다.

ESP32개발보드의 사진

ESP32개발보드의 사진

배선작업의 이미지

배선작업의 이미지

배선작업에 관하여

서보모터는 서보모터 드라이버 모듈에 연결됩니다. 이번에 사용한 드라이버에는 1개의 모터에 대해 3개의 선 (전원 +와- , PWM) 을 연결합니다.

또한, 모터드라이버에 모터를 위한 전원을 연결함으로써 PWM신호를 만들어내는 ESP32가 전원 공급을 하지 않아도 되게 됩니다. 물론 애초에 ESP32가 모터가 필요로하는 전원을 공급하기에는 약하다는 점도 관계가 있지요.

그리고 이번에는 굽힘센서 역시 연결해야 합니다. ESP32의 3.3V +전기를 10kΩ의 저항에 연결시켜 센서와 연결시킵니다. 이 저항은 풀업저항으로서 연결시키고 있기 때문에, 센서가 오프일때 랜덤한 의미없는 노이즈를 만들지 않도록 하기 위해 필요합니다.

3. 코드의 설명

코드의 설명 1.

// 서보모터를 굽힘센서로 움직이는 연습
// by Gounbeee
// coaramause.com
// 2023


// 이번에 사용하는 모터 드라이버, PCA9685를 움직이기 위한 드라이버
//https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library
#include <Adafruit_PWMServoDriver.h>  


// ESP32가 2개의 코어를 사용하여
// Core 0 -> 서보 모터를 움직이고,
// Core 1 -> 굽힘센서의 입력을 받는,
// 구성으로 움직이게 하고자 합니다.
// 
// 그를 위해서 먼저 태스크를 2개 작성합니다.
TaskHandle_t tsk1;
TaskHandle_t tsk2;

해설 1.

이제 코드설명을 하겠습니다. 우선 #include로 필요한 도구를 준비합니다. #include 라고 적고나서 파일 장소와 이름을 적으면 다른 사람이 기술한 코드를 도입할수가 있습니다. 다른 프로그래밍 언어에서는 import 등의 단어를 사용합니다. 이번 연습에서는 Adafruit사의 PWM서보모터 드라이버를 사용하겠습니다.

TaskHandle을 두개 만든것은, 굽힘센서의 입력을 받아들이는 태스크와 서보모터를 움직이는 태스크의 두개의 처리를 하고 있기 때문입니다.

핸들, 이라고 표현하는 것은 C언어의 경우 메모리의 주소를 넣어둔후 나중에 액세스함으로써 필요한 정보를 얻을수 있는 포인터라는 구조를 가지고 있기에, 말로 표현하자면 “메모리의 이 부분부터” 정보를 가져올수 있다, 정도로 생각할수가 있겠습니다.

코드 설명 2.

// --------------------------------------------------------


// 위에서 도입한 라이브러리를 사용하여, 서보 모터드라이버 제어용 오브젝트를 만듭니다.
Adafruit_PWMServoDriver servoDrv = Adafruit_PWMServoDriver(0x40);

설명 2.

자 그럼, Adafruit 사의 서보모터드라이버를 오브젝트로서 작성합니다. 서보모터의 드라이버와 ESP32는 I2C (아이 스퀘어드 시라고 읽음) 이라는 통신방식으로 정보를 교환합니다.

그렇기에 서보모터드라이버를 오브젝트로서 만들어 두고, I2C방식의 커뮤니케이션을 위해 0x40이라고 메모리 주소를 지정합니다.

0x40라는 표기의 ”0x”는, 이 다음 이어지는 40이라는 숫자가 “16진수”임을 의미합니다.

코드설명 3.

// --------------------------------------------------------
// 이곳은, Task2용의 센서를 위한 글로벌한 정보입니다.
// 기본적으로는, 별개의 태스크로 설정되어 있는 이상, 같은 메모리의 장소를 참조할 때는
// 주의가 필요합니다. 공유하고 있는 정보가 갱신되어 지는 동안은, 아직 그 정보가 확정되어 있지 않기 때문입니다.
// 그렇기에, 태스크간의 통신을 제대로 하여 "기다리는" 등의 처리가 필요로 되어질 때도 있습니다만
// 여기서는 생략하도록 하겠습니다.

// ADC(Analog-to-Digital Converter)기능을 갖는 핀입니다.
int sensorPin = 4;

// 센서로 부터 받은 정보입니다.
// 센서로 부터 받은 정보를 바탕으로, 서보 모터가 움직여야 할 지점을 계산합니다.
int sensorValue = 0;

설명 3.

여기서는 센서를 위한 핀 설정을 하겠습니다. 4번을 이용하도록 하겠습니다. sensorValue변수는 센서로부터 받은 정보를 저장할 장소로서 설정했습니다. 이 정보를 이후에 서보모터가 회전해야 할 각도로서 사용할 예정입니다.

코드설명 4.

// 이 스케치를 실행 했을때, 한번 실행하면서 필요한 정보의 준비를 하는 함수
void setup() {

  // Serial통신(여기서는USB)를 통하여,
  // Arduino IDE가 가지는"시리얼 모니터"를 사용해
  // 값을 출력하기 위한 셋업
  Serial.begin(115200); 


  // --------------------------

  // 센서를 위한 PIN설정
  pinMode(sensorPin, INPUT);

  // --------------------------

  // 서보모터를 위한 셋업
  servoDrv.begin();

  // 이번에 사용할 서보모터를 60Hz의 속도로 움직이도록 설정
  servoDrv.setPWMFreq(60); 


  delay(10);


  // TASK를 설정
  // 서보모터 용
  // 밑에 정의 한 함수를 참조
  //
  // Task1Job :  태스크의 이름. 이 정보를 가지고 태스크 간의 정보교환을 해야 할 때도 있습니다.
  // "tsk1"   : 태스크의 이름. 이 정보를 가지고 태스크 간의 정보교환을 해야 할 때도 있습니다.
  // 10000    :  이 태스크가 사용할 메모리의 크기
  // NULL.    :  이 태스크에 입력할 정보
  // 1        :  태스크처리의 우선순위
  // &tsk1    :  태스크 오브젝트를 참조하는 포인터. 이 태스크의 정보를 보기 위한 입구와도 같은 것입니다.
  // 0.       :  이 태스크를 담당하는 코어의 번호
  xTaskCreatePinnedToCore(
                    Task1Job,   
                    "tsk1",
                    10000,
                    NULL,
                    1,        
                    &tsk1,     
                    0);                  
  delay(500); 



  xTaskCreatePinnedToCore(
                    Task2Job,   
                    "tsk2",    
                    10000,       
                    NULL,        
                    1,          
                    &tsk2,
                    1);       
    delay(500); 



}

설명 4.

setup함수의 설명을 하겠습니다. 지금 저희는 ESP32를 사용중입니다만, 소프트웨어로서는 Arduino라이브러리를 사용중이기에 기본적인 설정(setup함수, loop함수) 을 따라주어야 합니다. 이처럼, 단순히 보면 규칙으로만 보이는 구조는 사용하는 소프트웨어적인 틀에 영향을 받는 경우가 많습니다. 그렇다는 말은, 다른 틀을 사용할때는 당연히 다른 이름의 함수를 설정해야 하거나 합니다. ESP32의 경우에는, “ESP-IDF” 라는 C언어(Arduino는C++)로 기술하여 사용하는 도구 역시 선택 할수가 있습니다. Arduino 라이브러리보다 더 긴 코드를 기술해야 하지만, 마이크로 컨트롤러를 레지스트리 레벨에서 설정하거나, 보다 세부적인 설정이 가능합니다. 물론, Arduino라이브러리를 사용하면서도 세부설정을 할수도 있습니다.

코드 설명은 코드의 코멘트에 적혀 있는데, 내용은 USB를 경유한 출력을 확인하기 위한 Serial초기화, 또 센서를 위한 핀의 초기화등입니다. 더나아가, 이미 전역변수로서 생성해 놓은 모터 드라이버의 오브젝트를 사용하여, PWM처리에 있어서의 주기를 설정하고 있습니다. 이번에 사용한 서보 모터는 더 빠른 주기설정도 가능한 모터이지만, 우선 여기서는 60Hz로 설정해 보겠습니다.

다음 단계로서, xTaskCreatePinnedToCore함수를 사용하여, 2개의 태스크를 작성합니다. 여기까지 진행하는 동안, 핸들을 변수로 설정해 두었는데, 이제 여기서 태스크의 상세내용을 정하면서 만들어 두었던 핸들 역시 등록하게 됩니다. 우선, 이 함수는 ESP32가 지원하는 FreeRTOS가 가지는 기능입니다. FreeRTOS란, 임베디드 시스템에서 사용하는 운영체제의 한가지로, 이번 연습에서 2가지의 태스크를 작성하기 위해 2번 불러오고 있습니다.

태스크란, 기본적으로는 독립된 움직임을 하는 프로세스를 뜻합니다. 그리고 각각의 태스크는 각기 다른 실행 루프를 가지고 있지요. 간단한 이미지로서는, Arduino라이브러리를 사용할 때의 setup함수와 loop함수가 각기 다른 태스크에서 움직이고 있다고 생각하면 되겠습니다. 루프자체는 for루프로 설정되고 있고, 루프를 위한 조건이 설정되어 있지 않기 때문에, 절대 빠져나오지 않는 무한 루프가 되어 있습니다.

xTaskCreatePinnedToCore함수의 설정에서는, 이 태스크가 필요로 하는 메모리의 양을 정할 필요가 있습니다. 당연한 이야기지만 처리의 양이 많을 수록, 많은 메모리가 필요합니다. 그리고 ESP32는 코어가 2개 준비되어 있기 때문에, 각각의 태스크를 담당하는 코어의 번호 역시, 서로 다른 수치를 설정해 줄 필요가 있는 것이지요.

마지막으로, 첫번째 인수에 이제부터 선언하는 함수의 이름이 등록되어 있는 점을 유의해 주시길 바랍니다. 이제, 이 함수의 내부에서 실제 행하고자 하는 처리 (센서정보의 입력과 모터 회전)를 기술하도록 하겠습니다.

코드설명 5.

void loop() {
  // 통상적인 루프 함수를 사용하지 않습니다.
}

해설 5.

loop함수는, 위에서 기술한 것 처럼, FreeRTOS의 기능이 아닌, Arduino가 제공하는 기능입니다. 그렇기 때문에 이번에는 사용하지 않겠습니다.

코드설명 6.

// 태스크 1
// 서보 모터를 움직이는 처리
void Task1Job( void * pvParameters ) {

  // TASK는 독립적인 루프를 가질 필요가 있습니다.
  // 밑의 For루프는 그를 위한 것입니다.
  // For문의 처리는 적어도 delay(1)를 적용시키며 움직이는게 좋습니다.

  
  for(;;) {

    // 서보 드라이버 오브젝트를 사용하여,
    // 처음부터 5개 까지의 모터에 대해 센서에서 받은 정보를 반영하여,
    // 회전 시키는 부분.
    servoDrv.writeMicroseconds(0, sensorValue );
    servoDrv.writeMicroseconds(1, sensorValue );
    servoDrv.writeMicroseconds(2, sensorValue );
    servoDrv.writeMicroseconds(3, sensorValue );
    servoDrv.writeMicroseconds(4, sensorValue );
    

    delay(10);

  }

}

설명 6.

첫번째 태스크는 “서보 모터를 회전시키기” 위한 것입니다. Task1Job이란 이름으로 등록하려는데, 이 이름은 setup함수에서 태스크설정을 할 때 사용되어지니 주의합시다. 이와 같이, 태스크는 핸들을 마련하고, 함수의 이름을 등록하는 등의 과정을 거쳐 설정되어 집니다.

또 태스크는 내부에 독립된 루프를 가져야 한다고 했습니다. 그러니 for루프의 내부에서, 서보모터를 회전시키기 위한 함수를 기술하고자 합니다.

서보모터는, 시간적인 폭을 가지고 있는 파형을 받았을 때, 그것을 회전해야 할 “각도”로 해석합니다. 그렇기에 회전시키기 위해 사용중인 함수, writeMicroseconds에서는, 2번째 인수로서 “시간”을 넣고 있는 것이지요. 설정하고 있는 정보는 시간이지만, 이것이 결과적으로는 펄스 파형의 형태가 되는 것입니다.

더욱이, writeMicroseconds함수의 1번째 인수는, 드라이버의 모터를 연결중인 슬롯의 번호입니다. 즉, 여기서는 1번에서 5번까지의 모터를 시간적인 차이가 없이 동시에 움직이고 있습니다.

코드설명 7.

// 태스크 2
// 센서의 입력을 받는 처리
void Task2Job( void * pvParameters ) {

  for(;;) {

    // 처음에 설정한 핀 4번으로부터 센서의 정보를 받음
    sensorValue = analogRead(sensorPin); 
    //Serial.println(sensorValue);
    //Serial.println("  |  ");

    // 굽힘센서의 입력치는 0에서 4095(4096까지의 폭)
    // 입니다만, 필름을 어느정도 굽히기 전까진 계속 4095가 얻어지며,
    // 거기서부터 갑자기 1300 정도까지 내려가 변동되었습니다.
    // 그렇기에, 일단 4095(전혀 굽혀지지 않은 상태)는, 의도적으로 수치를 줄였습니다.
    if (sensorValue == 4095) sensorValue = 2000;

    // 그러고 나서, map함수를 사용하여, 값을 스케일 합니다.
    sensorValue = map(sensorValue, 0, 2000, 500, 2000);

    // 또, 이번에 사용하는 서보모터는 500 에서 2500까지 펄스폭을 입력 받을 수 있어,
    // 그 값의 범위가, 0도에서 270도까지의 회전을 의미하고 있기에,
    // 그 값의 범위를 벗어나는 값이 나오지 않도록 constrain함수로 값을 잘라내었습니다.
    sensorValue = constrain(sensorValue, 500, 2500);

    Serial.println(sensorValue);

    delay(20);
  }

}

설명 7.

다음으로, 태스크2의 설정입니다. 태스크2는 센서에서 오는 입력을 받기 위한 처리가 들어 있습니다.

이번 연습에서는, Arduino 라이브러리의 기능을 사용하여 아날로그 입력으로서 설정한 핀4번에서부터 analogRead함수를 사용하여 정보를 받고 있습니다.

코드의 다음부분은, 굽힘센서를 실제로 움직여 보면서 세부를 조정한 코드입니다. 모터가 움직이는 속도, 각도의 한계치등을, 실제로 움직이면서 수치를 조정하는 작업입니다. 더욱이, 이와 종류의 개발에서는 실제 움직여보면서 조정하는 과정도 필요하지만, 사용하고 있는 도구에 관련한 도큐멘트를 참조하며 작업하는 것도 매우 중요하다는 것을 잊지 마시길 바랍니다.

그 다음 부분은, map함수와 constrain함수를 사용하여, 입력된 값의 범위를 조정하거나 불필요한 부분을 잘라내고 있습니다. 모터가 받아들일 수 있는 각도의 값이 정해져 있기 때문에, 이것을 벗어난 값이 입력되지 않도록 해주고 있습니다.

마지막은 어느정도의 delay를 넣어 준후 마무리하고 있는데, 여기서도 모터의 처리속도를 60Hz로 설정하고 있는 것을 고려하여, 그것에 가까운 속도의 루프가 되도록 설정하고 있습니다.

4. 연습을 끝내면서

이번 연습의 응용

이번 연습은 굽힘 센서를 굽힌 정도에 따라 서보모터를 정해진 각도로 회전시키는 예제였습니다.

이번 연습의 내용을 응용하자면, 우선 굽힘 센서를 어디에 설치할 것인가에 대해 생각해 볼수 있겠습니다. 어느 정도 굽혀지는 재료에 설치하거나 천과 같은 재료에 붙일 수도 있겠지요.

어느 경우가 되더라도, 굽힘 센서가 굽혀지는 수단이 무엇인가에 따라서 신체적인 감각에 영향을 주며, 바람과 같은 환경에 맞는 인터랙션이 일어날 수도 있겠지요.

← 코스 목록으로 맨 위로 ↑