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



2. 모바일 매니퓰레이터 블랙베리의 매니퓰레이터 구성
그리퍼와 4개의 스마트 액츄에이터로 구성된 4자유도의 매니퓰레이터를 가집니다.
매니퓰레이터에서의 자유도란 위치와 자세를 결정하는 것에 영향을 미치는 요인이 몇 개인가에 대한 값입니다.
각 모터의 각도가 변함에 따라 매니퓰레이터의 위치나 자세가 변하기 때문에 매니퓰레이터의 자유도는 관절의 개수와 같습니다.
블랙베리의 매니퓰레이터는 바닥과 가장 가까이에 위치한 스마트 액츄에이터는 수평으로 회전하고, 나머지 모터들은 수직으로 회전하는 수직 다관절형태를 가집니다.

3. 스마트 액츄에이터의 각도 제어 (1)
매니퓰레이터에 사용된 스마트 액츄에이터는 바퀴와 달리 회전 속도가 아닌 위치로 제어를 합니다.
블랙베리의 모터를 위치로 제어할 때 단위는 degree(도)를 사용 할 것입니다.
모터의 가동 범위는 아래 좌측 사진과 같이 -180~180도이며 0도인 위치가 센터가 됩니다.
추후 그리퍼의 좌표 값에 따른 각 모터의 각도 계산의 편의를 위해 매니퓰레이터의 기본 자세(모든 모터의 위치를 센터로 이동시킨 자세)는 아래 우측 사진과 같은 모습으로 설정할 것입니다. 기본적으로는 모터 가동 범위의 중간 부분이 센터가 되지만 모터의 센터 위치를 재설정 하면 우측 사진과 같은 자세를 기본 자세로 만들 수 있습니다.
이후 실습에서 매니퓰레이터의 각 모터를 각도 값으로 제어할 때 기준은 아래 우측 사진과 같이 정의하도록 하겠습니다.


4. 스마트 액츄에이터의 각도 제어 (2)
여기서는 XL430-W250 모터와 통신하여 매니퓰레이터의 모터를 각도로 제어하는 실습을 할 것입니다. 모터를 관절로서 사용하도록 설정하는 방법, 모터 각도를 제어하는 방법 순서로 알아보겠습니다.
모터를 관절로서 사용하도록 설정하기
모터를 관절로서 사용하도록 설정할 때는 컨트롤 테이블의 Operating Mode와 Drive Mode를 설정해 주어야 합니다.
Operating Mode는 모터의 동작 모드를 설정합니다. 기본 값은 3으로 위치제어 모드를 의미합니다. 위치 제어 모드에서는 모터가 360도 이상 회전하지 못하고 위치(각도)로 제어해야 하기 때문에 로봇의 관절로서 사용하기 적합합니다. 그러므로 매니퓰레이터에 있는 모터의 Operating Mode는 기본 값을 그대로 사용할 것입니다.
Drive Mode는 모터의 드라이브 모드를 설정합니다. 0번 비트의 값에 따라 방향 모드가 결정되고 기본 값은 0으로 Normal Mode를 의미합니다. 이전 페이지에서 정의했던 대로 매니퓰레이터의 모든 모터가 시계방향으로 회전할 때 더 큰 각도 값을 갖도록 하기 위해 모든 모터의 방향 모드는 Reverse Mode(양수가 CW, 음수가 CCW) 로 설정 할 것입니다.
Drive Mode에서 설정할 수 있는 것으로는 Profile Configuration도 있습니다. Profile Configuration은 Drive Mode의 2번 비트로 설정하는데 값이 0이면 모터 제어 시 가속도와 최대속도를 설정하는 Velocity-based Profile 모드이고, 1이면 모터 제어 시 가속 시간과 전체 동작 시간을 설정하는 Time-based Profile 모드입니다. 이번 장에서는 두 가지 모드 모두를 사용하여 실습을 해 볼 것입니다.
Operating Mode와 Drive Mode는 EEPROM 영역에 있기 때문에 모터의 토크를 끈 후에 값을 수정해야 합니다. 컨트롤 테이블의 Torque Enable의 값이 0이면 Torque off, 1이면 Torque on을 의미합니다.
5. 스마트 액츄에이터의 각도 제어 (3)
모터 각도 제어하기
한 개 모터의 위치를 제어하기 위해서는 Goal Position을 설정해주어야 합니다.
아래 표는 Goal Position의 주소는 116, 데이터의 크기는 4바이트이며 데이터의 값 범위는 Min Position Limit과 Max Position Limit에 의해 제한되고, 값의 단위는 1 pulse임을 나타냅니다. 모터의 주요 사양을 보면 해상도에 4096 [pulse/rev]라고 표기되어 있는데 이는 한 번 회전하기 위해 4096펄스가 필요하다, 360도를 4096단계로 나누어 제어할 수 있다는 의미로 해석할 수 있습니다. (1 pulse = 0.088 deg)
아래 표에 나온 것 처럼 Min Position Limit과 Max Position Limit이 기본 값일 때
Goal Position에 설정할 수 있는 값의 범위는 0~4095 입니다.

6. 스마트 액츄에이터의 각도 제어 (4)
모터 각도 제어하기
다이나믹셀 모터의 위치를 제어할 때, 모터는 Profile에 의해 생성된 목표 위치 궤적에 따라 제어됩니다.
Goal Position 값이 변경되면 목표 위치 궤적은 Profile Velocity와 Profile Acceleration에 따라 형태가 결정되고 생성됩니다.
Profile Velocity와 Profile Acceleration은 Drive Mode의 Profile Configuration 설정에 따라 다른 의미를 가지게 됩니다.
Profile Configuration이 Velocity-based Profile로 되어있으면 Profile Velocity는 모터 위치 제어 시 모터의 목표 속도를 의미하게 되고, Time-based Profile로 되어있으면 Profile Velocity는 모터가 목표 위치에 도달하기까지 소요될 총 시간을 의미하게 됩니다. (자세한 내용은 매뉴얼을 참고하세요.)


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

8. 스마트 액츄에이터의 각도 제어 실습 소개
매니퓰레이터를 구성하고 있는 4개의 모터 ID는 아래 사진과 같습니다.
여기서는 Velocity-based Profile을 사용하여 모터에 목표 속도를 지정하고, 5번 스마트 액츄에이터를 시계 방향 또는 반시계 방향으로 회전시키는 코딩 실습을 합니다.
[주의] 매니퓰레이터가 회전할 때 부딪히지 않도록 주변에 닿는 물건이 없도록 주의합니다. 가능하면 전원이 꺼진 상태에서 매니퓰레이터를 아래 사진과 같은 모양으로 미리 접어두면 좋습니다.

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


ARM_DXL_IDS 배열은 필요한 모터가 모두 존재하는지 확인할 때 사용 할 것입니다.
10. 스마트 액츄에이터의 각도 제어 코딩 실습 (2)

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

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


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


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

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

블랙베리에 프로그램을 업로드한 후 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);
}
}
'소프트웨어 > Arduino' 카테고리의 다른 글
| 4자유도 매니퓰레이터의 위치 제어 #2 (0) | 2026.03.25 |
|---|---|
| 4자유도 매니퓰레이터의 위치 제어 #1 (0) | 2026.03.25 |
| 메카넘휠을 사용한 모바일베이스의 주행 원리 (0) | 2026.03.18 |
| 여러 개의 모터를 동시에 제어하기 (0) | 2026.03.18 |
| 스마트 액츄에이터의 회전 방향과 속도 제어 코딩 실습 (0) | 2026.03.18 |