본문으로 바로가기

스마트 액츄에이터의 각도 제어

category 소프트웨어/Arduino 2026. 3. 25. 22:08

1. 매니퓰레이터란?

인간의 팔과 같이 사물을 조작하고 다루는 역할을 하는 기계적인 장치입니다. 매니퓰레이터는 관절 구조에 따라 여러 종류로 나뉠 수 있습니다. 각각의 장점과 단점이 있어 상황에 따라 적절한 형태의 로봇을 사용하게 됩니다. 또한 매니퓰레이터는 팔 끝에 장착된 말단 장치에 따라 물체를 이동시키거나, 용접, 도장 등의 다양한 임무를 수행할 수 있습니다.

 

 

 

 

 

2. 모바일 매니퓰레이터 블랙베리의 매니퓰레이터 구성

 

그리퍼와 4개의 스마트 액츄에이터로 구성된 4자유도의 매니퓰레이터를 가집니다.

매니퓰레이터에서의 자유도란 위치와 자세를 결정하는 것에 영향을 미치는 요인이 몇 개인가에 대한 값입니다.
각 모터의 각도가 변함에 따라 매니퓰레이터의 위치나 자세가 변하기 때문에 매니퓰레이터의 자유도는 관절의 개수와 같습니다.

블랙베리의 매니퓰레이터는 바닥과 가장 가까이에 위치한 스마트 액츄에이터는 수평으로 회전하고, 나머지 모터들은 수직으로 회전하는 수직 다관절형태를 가집니다.

 

 

 

 

3. 스마트 액츄에이터의 각도 제어 (1)

 

매니퓰레이터에 사용된 스마트 액츄에이터는 바퀴와 달리 회전 속도가 아닌 위치로 제어를 합니다.

블랙베리의 모터를 위치로 제어할 때 단위는 degree()를 사용 할 것입니다.

모터의 가동 범위는 아래 좌측 사진과 같이 -180~180도이며 0도인 위치가 센터가 됩니다.

추후 그리퍼의 좌표 값에 따른 각 모터의 각도 계산의 편의를 위해 매니퓰레이터의 기본 자세(모든 모터의 위치를 센터로 이동시킨 자세)는 아래 우측 사진과 같은 모습으로 설정할 것입니다. 기본적으로는 모터 가동 범위의 중간 부분이 센터가 되지만 모터의 센터 위치를 재설정 하면 우측 사진과 같은 자세를 기본 자세로 만들 수 있습니다.

이후 실습에서 매니퓰레이터의 각 모터를 각도 값으로 제어할 때 기준은 아래 우측 사진과 같이 정의하도록 하겠습니다.

 

 

 

 

4. 스마트 액츄에이터의 각도 제어 (2)

 

여기서는 XL430-W250 모터와 통신하여 매니퓰레이터의 모터를 각도로 제어하는 실습을 할 것입니다. 모터를 관절로서 사용하도록 설정하는 방법, 모터 각도를 제어하는 방법 순서로 알아보겠습니다.

모터를 관절로서 사용하도록 설정하기

모터를 관절로서 사용하도록 설정할 때는 컨트롤 테이블의 Operating ModeDrive Mode를 설정해 주어야 합니다.

Operating Mode는 모터의 동작 모드를 설정합니다. 기본 값은 3으로 위치제어 모드를 의미합니다. 위치 제어 모드에서는 모터가 360도 이상 회전하지 못하고 위치(각도)로 제어해야 하기 때문에 로봇의 관절로서 사용하기 적합합니다. 그러므로 매니퓰레이터에 있는 모터의 Operating Mode는 기본 값을 그대로 사용할 것입니다.

Drive Mode는 모터의 드라이브 모드를 설정합니다. 0번 비트의 값에 따라 방향 모드가 결정되고 기본 값은 0으로 Normal Mode를 의미합니다. 이전 페이지에서 정의했던 대로 매니퓰레이터의 모든 모터가 시계방향으로 회전할 때 더 큰 각도 값을 갖도록 하기 위해 모든 모터의 방향 모드는 Reverse Mode(양수가 CW, 음수가 CCW) 로 설정 할 것입니다.

Drive Mode에서 설정할 수 있는 것으로는 Profile Configuration도 있습니다. Profile ConfigurationDrive Mode2번 비트로 설정하는데 값이 0이면 모터 제어 시 가속도와 최대속도를 설정하는 Velocity-based Profile 모드이고, 1이면 모터 제어 시 가속 시간과 전체 동작 시간을 설정하는 Time-based Profile 모드입니다. 이번 장에서는 두 가지 모드 모두를 사용하여 실습을 해 볼 것입니다.

Operating ModeDrive ModeEEPROM 영역에 있기 때문에 모터의 토크를 끈 후에 값을 수정해야 합니다. 컨트롤 테이블의 Torque Enable의 값이 0이면 Torque off, 1이면 Torque on을 의미합니다.

 

 

 

5. 스마트 액츄에이터의 각도 제어 (3)

 

모터 각도 제어하기

한 개 모터의 위치를 제어하기 위해서는 Goal Position을 설정해주어야 합니다.

아래 표는 Goal Position의 주소는 116, 데이터의 크기는 4바이트이며 데이터의 값 범위는 Min Position LimitMax Position Limit에 의해 제한되고, 값의 단위는 1 pulse임을 나타냅니다. 모터의 주요 사양을 보면 해상도에 4096 [pulse/rev]라고 표기되어 있는데 이는 한 번 회전하기 위해 4096펄스가 필요하다, 360도를 4096단계로 나누어 제어할 수 있다는 의미로 해석할 수 있습니다. (1 pulse = 0.088 deg)

아래 표에 나온 것 처럼 Min Position LimitMax Position Limit이 기본 값일 때

Goal Position에 설정할 수 있는 값의 범위는 0~4095 입니다.

 

 

 

 

 

6. 스마트 액츄에이터의 각도 제어 (4)

 

모터 각도 제어하기

다이나믹셀 모터의 위치를 제어할 때, 모터는 Profile에 의해 생성된 목표 위치 궤적에 따라 제어됩니다.

Goal Position 값이 변경되면 목표 위치 궤적은 Profile VelocityProfile Acceleration에 따라 형태가 결정되고 생성됩니다.

Profile VelocityProfile AccelerationDrive ModeProfile Configuration 설정에 따라 다른 의미를 가지게 됩니다.

Profile ConfigurationVelocity-based Profile로 되어있으면 Profile Velocity는 모터 위치 제어 시 모터의 목표 속도를 의미하게 되고, Time-based Profile로 되어있으면 Profile Velocity는 모터가 목표 위치에 도달하기까지 소요될 총 시간을 의미하게 됩니다. (자세한 내용은 매뉴얼을 참고하세요.)

 

 

 

 

 

7. 스마트 액츄에이터의 각도 제어 (5)

 

모터 각도 제어하기

모터의 위치를 제어할 때 부가적으로 사용할 수 있는 컨트롤테이블의 데이터로는 Min Position Limit, Max Position LimitHoming Offset 이 있습니다. Min Position Limit, Max Position Limit은 모터 구동 범위를 제한하기 위해 사용하고 Homing Offset은 모터의 센터 값의 Offset을 설정해 센터 값을 변경하기 위해 사용합니다.

 

 

 

 

8. 스마트 액츄에이터의 각도 제어 실습 소개

 

매니퓰레이터를 구성하고 있는 4개의 모터 ID는 아래 사진과 같습니다.

여기서는 Velocity-based Profile을 사용하여 모터에 목표 속도를 지정하고, 5번 스마트 액츄에이터를 시계 방향 또는 반시계 방향으로 회전시키는 코딩 실습을 합니다.

[주의]  매니퓰레이터가 회전할 때 부딪히지 않도록 주변에 닿는 물건이 없도록 주의합니다. 가능하면 전원이 꺼진 상태에서 매니퓰레이터를 아래 사진과 같은 모양으로 미리 접어두면 좋습니다.

 

 

 

9. 스마트 액츄에이터의 각도 제어 코딩 실습 (1)

 

 

 

 

1.모터 사용을 위해 Dynamixel2Arduino.h 헤더파일을 코드에 포함시키고, 관련 값들을 정의 및 선언합니다.
2.모터에 설정하기 위한 ID와 방향 모드, 모터 위치 제한 값과 Homing Offset 값들을 정의합니다.
3.매니퓰레이터에 있는 모터들의 아이디를 선언합니다.
ARM_DXL_IDS
배열은 필요한 모터가 모두 존재하는지 확인할 때 사용 할 것입니다.
 

 

10. 스마트 액츄에이터의 각도 제어 코딩 실습 (2)

 

 

 
4.setup 에서는 통신 설정 후에 필요한 모터가 모두 있는지 확인하고 모터에 drive modeoperating mode를 설정합니다.

 

1) 시리얼모니터, 모터와의 통신을 설정합니다.
2)  FindManipulatorServos 함수를 사용해 ARM_DXL_IDS 배열에 있는 아이디의 모터들이 모두 통신 가능한지 확인합니다. 모든 모터를 찾으면 true, 아니면 false를 반환하는 함수입니다.
3) 모터의 토크를 끈 후에 DRIVE_MODE1(Reverse Mode, Velocity-based Profile)으로, OPERATING_MODE3(위치 제어 모드)으로 설정합니다. 각 모터에 맞는 Position Limit 값과 Homing Offset 값을 설정 하고 다시 모터 토크를 켠 후 Profile Velocity 값을 44(10 rpm)로 설정합니다.

 

 

11. 스마트 액츄에이터의 각도 제어 코딩 실습 (3)

 

 

 

4.setup 에서는 통신 설정 후에 필요한 모터가 모두 있는지 확인하고 모터에 drive modeoperating mode를 설정합니다.
 
3) 모터의 토크를 끈 후에 DRIVE_MODE1(Reverse Mode, Velocity-based Profile)으로, OPERATING_MODE3(위치 제어 모드)으로 설정합니다. 각 모터에 맞는 Position Limit 값과 Homing Offset 값을 설정 하고 다시 모터 토크를 켠 후 Profile Velocity 값을 44(10 rpm)로 설정합니다.
 
 

12. 스마트 액츄에이터의 각도 제어 코딩 실습 (4)

 

 

 

FindManipulatorServos 함수는 앞서 선언했던 상수 배열 ARM_DXL_IDS 에 있는 아이디의 모터들이 모두 통신 가능한지 확인합니다. 모든 모터를 찾으면 true, 아니면 false를 반환하는 함수입니다. 앞서 모바일베이스를 제어하는 실습에서 사용했던 FindMobileBaseServos 함수와 같은 원리로 동작합니다.

 
 

13. 스마트 액츄에이터의 각도 제어 코딩 실습 (5)

 

 

 

6.moveArmServoWithAngle 함수는 아래와 같이 정의됩니다.
 
1) angle이 숫자일 때만 실행 하겠다는 코드입니다. nannot a number를 의미하며, isnan() 함수는 인자로 nan이 넘겨지면 참을 반환합니다.
2) 입력 받은 각도 값을 대응하는 모터 위치 값으로 매핑하는 코드입니다. constrain 함수는 첫 번째 인자로 입력 받은 값을 두 번째 인자 값 이상, 세 번째 인자 값 이하로 제한하여 값을 반환하는 함수입니다. map 함수는 첫 번째 인자로 입력 받은 값을 어떤 범위에서 다른 범위의 대응하는 값으로 변환시켜주는 함수입니다. 그래서 이 코드는 constraint 함수와 map 함수를 사용해 모터의 목표 각도 angle 값을 -180~180 범위로 제한 한 후 0~4095 범위의 모터 위치 값으로 변환하겠다는 의미입니다.
3) 변환된 위치 값으로 모터를 제어합니다.

 

 

 

 

14. 스마트 액츄에이터의 각도 제어 코딩 실습 (6)

 

 

 

loop에서는 아래 7번 부터 10번 과정을 반복합니다.

7.5번 모터를 -90˚(CCW)로 회전시킨 후 2초간 대기합니다.
 
8.5번 모터를 0˚(center)로 회전시킨 후 2초간 대기합니다.
 
9.5번 모터를 90˚(CW)로 회전시킨 후 2초간 대기합니다.
 
10.5번 모터를 0˚(center)로 회전시킨 5초간 대기합니다.

 

 

 

 

블랙베리에 프로그램을 업로드한 후 5번 모터가 함수에서 호출한 각도에 따라 좌측 사진과 같이 움직이는지 확인합니다.

 

 

//////////////  모터 구동 관련 선언
#include <Dynamixel2Arduino.h>

#define DXL_SERIAL   Serial1
const int DXL_DIR_PIN = 2; // DIR PIN
const float DXL_PROTOCOL_VERSION = 2.0;

Dynamixel2Arduino dxl(DXL_SERIAL, DXL_DIR_PIN);

// 컨트롤 테이블 아이템의 이름을 사용하기 위해 이 네임스페이스가 필요함
using namespace ControlTableItem;


//////////////  모터 구동용 변수/상수
#define ARM_DXL_ID_1            0x05 // 매니퓰레이터 1번 모터 아이디 (가장 아래)
#define ARM_DXL_ID_2            0x06 // 매니퓰레이터 2번 모터 아이디
#define ARM_DXL_ID_3            0x07 // 매니퓰레이터 3번 모터 아이디
#define ARM_DXL_ID_4            0x08 // 매니퓰레이터 4번 모터 아이디 (가장 위)

#define NORMAL_MODE                 0x00 // drive mode의 방향 모드
#define REVERSE_MODE                0x01

#define ARM_DXL_1_POSITION_MIN  0
#define ARM_DXL_1_POSITION_MAX  4095
#define ARM_DXL_2_POSITION_MIN  1024
#define ARM_DXL_2_POSITION_MAX  3474
#define ARM_DXL_3_POSITION_MIN  211
#define ARM_DXL_3_POSITION_MAX  2640
#define ARM_DXL_4_POSITION_MIN  722
#define ARM_DXL_4_POSITION_MAX  3161

#define ARM_DXL_1_OFFSET        0
#define ARM_DXL_2_OFFSET        238
#define ARM_DXL_3_OFFSET        799
#define ARM_DXL_4_OFFSET        0

// FinManipulatorServos 함수에서 찾을 모터 아이디들, 값이 중복 없이 정렬되어있어야 함
const uint8_t ARM_DXL_ID_CNT = 4;
const uint8_t ARM_DXL_IDS[ARM_DXL_ID_CNT] = {ARM_DXL_ID_1,
                                             ARM_DXL_ID_2,
                                             ARM_DXL_ID_3,
                                             ARM_DXL_ID_4
                                            };


//////////////  메인 프로그램

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

  dxl.begin(57600);
  dxl.setPortProtocolVersion(DXL_PROTOCOL_VERSION);

  // 모터가 모두 있는지 확인될 때 까지 진행하지 않음
  while (!FindManipulatorServos()) {}

  // 각 모터에 설정
  for (int i = 0 ; i < sizeof(ARM_DXL_IDS) / sizeof(uint8_t) ; i++) {
    // 토크 끄기
    dxl.writeControlTableItem(TORQUE_ENABLE, ARM_DXL_IDS[i], 0);

    // 드라이브 모드 설정
    // 방향은 Reverse Mode, Profile Configuration은 Velocity-based Profile
    dxl.writeControlTableItem(DRIVE_MODE, ARM_DXL_IDS[i], REVERSE_MODE);
    // 오퍼레이팅 모드를 위치 제어 모드로 설정
    dxl.writeControlTableItem(OPERATING_MODE, ARM_DXL_IDS[i], 3);

    switch (ARM_DXL_IDS[i]) {
      case ARM_DXL_ID_1:
        dxl.writeControlTableItem(MIN_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_1_POSITION_MIN);
        dxl.writeControlTableItem(MAX_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_1_POSITION_MAX);
        dxl.writeControlTableItem(HOMING_OFFSET,
                                  ARM_DXL_IDS[i], ARM_DXL_1_OFFSET);
        break;
      case ARM_DXL_ID_2:
        dxl.writeControlTableItem(MIN_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_2_POSITION_MIN);
        dxl.writeControlTableItem(MAX_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_2_POSITION_MAX);
        dxl.writeControlTableItem(HOMING_OFFSET,
                                  ARM_DXL_IDS[i], ARM_DXL_2_OFFSET);
        break;
      case ARM_DXL_ID_3:
        dxl.writeControlTableItem(MIN_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_3_POSITION_MIN);
        dxl.writeControlTableItem(MAX_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_3_POSITION_MAX);
        dxl.writeControlTableItem(HOMING_OFFSET,
                                  ARM_DXL_IDS[i], ARM_DXL_3_OFFSET);
        break;
      case ARM_DXL_ID_4:
        dxl.writeControlTableItem(MIN_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_4_POSITION_MIN);
        dxl.writeControlTableItem(MAX_POSITION_LIMIT,
                                  ARM_DXL_IDS[i], ARM_DXL_4_POSITION_MAX);
        dxl.writeControlTableItem(HOMING_OFFSET,
                                  ARM_DXL_IDS[i], ARM_DXL_4_OFFSET);
        break;
    }

    // 토크 켜기
    dxl.writeControlTableItem(TORQUE_ENABLE, ARM_DXL_IDS[i], 1);

    // 위치 제어 모드에서는 Goal velocity가 아닌 profile velocity를 사용
    // 약 10 rpm으로 목표속도 설정
    dxl.writeControlTableItem(PROFILE_VELOCITY, ARM_DXL_IDS[i], 44);
  }
}

void loop()
{
  MoveArmServoWithAngle(ARM_DXL_ID_1, -90);
  delay(2000);

  MoveArmServoWithAngle(ARM_DXL_ID_1, 0);
  delay(2000);

  MoveArmServoWithAngle(ARM_DXL_ID_1, 90);
  delay(2000);

  MoveArmServoWithAngle(ARM_DXL_ID_1, 0);
  delay(5000);
}

/*
   ARM_DXL_IDS 배열에 있는 아이디의 모터들이 모두 통신 가능한지
   확인하는 함수. 모두 통신 가능하면 true, 아니면 false를 반환
*/
bool FindManipulatorServos() {
  uint8_t ids_pinged[10] = {0,};
  bool is_each_motor_found = true;
  if (uint8_t count_pinged = dxl.ping(DXL_BROADCAST_ID, ids_pinged,
                                      sizeof(ids_pinged) / sizeof(ids_pinged[0]), 100)) {
    if (count_pinged >= sizeof(ARM_DXL_IDS) / sizeof(uint8_t)) {
      uint8_t arm_dxl_ids_idx = 0;
      uint8_t ids_pinged_idx = 0;
      while (1) {
        if (ARM_DXL_IDS[arm_dxl_ids_idx]
            == ids_pinged[ids_pinged_idx++]) {
          arm_dxl_ids_idx ++;

          if (arm_dxl_ids_idx
              == sizeof(ARM_DXL_IDS) / sizeof(uint8_t)) {
            // 찾으려는 모터를 모두 찾은 경우
            break;
          }
        } else {
          if (ids_pinged_idx == count_pinged) {
            // 통신가능한 모터가 더이상 없는 경우
            is_each_motor_found = false;
            break;
          }
        }
      }

      if (!is_each_motor_found) {
        Serial.print("Motor IDs does not match : ");
        Serial.println(dxl.getLastLibErrCode());
      }
    } else {
      Serial.print("Motor count does not match : ");
      Serial.println(dxl.getLastLibErrCode());
      is_each_motor_found = false;
    }
  } else {
    Serial.print("Broadcast returned no items : ");
    Serial.println(dxl.getLastLibErrCode());
    is_each_motor_found = false;
  }
  return is_each_motor_found;
}

/*
   모터의 각도를 제어하는 함수
   params :
      motorID : 제어할 모터의 아이디
      angle : 제어할 모터의 목표 각도
*/
void MoveArmServoWithAngle(uint8_t motorID, float angle) {
  if (!isnan(angle)) {
    // 입력받은 angle 값을 -180~180 범위 내의 값으로 제한하고
    // 0~4095 범위로 매핑
    int32_t motorPosition = map(constrain((int32_t)angle, -180, 180),
                                -180, 180, 0, 4095);
    dxl.writeControlTableItem(GOAL_POSITION, motorID, motorPosition);
  }
}

 

728x90
728x90