이 중공축 모터를 아두이노로 정밀 위치제어하려면 어떻게 하나요?
RTG42 엔코더 시리즈(모델명 E)는 1000CPR 엔코더가 내장된 BLDC 모터라
BLC-400R4E 드라이버와 조합하면 RS485 통신으로 0.01도 단위 위치 지령과 위치·속도·전류 피드백이 가능합니다. 백래쉬 1arcmin 이하 감속기의 정밀도를 그대로 쓰려면 통신 위치제어가 정석입니다. 아래 예제에는 인덱스 테이블·θ축 응용을 위한
분할 이동(인덱싱) 명령이 포함되어 있습니다 — i6을 입력하면 출력축이 정확히 60도씩 회전합니다. 모터 상선(U/V/W)·홀센서·엔코더 결선은 드라이버 상세페이지 결선도를 기준으로 하세요.
| RS485 모듈 |
연결 |
| RO (수신 출력) |
아두이노 D10 |
| DI (송신 입력) |
아두이노 D11 |
| DE + RE (묶어서) |
아두이노 D3 |
| A / B (통신선) |
BLC-400R4E의 485 단자 (통신이 안 되면 A/B 교차) |
| VCC / GND |
5V / GND (드라이버와 GND 공통) |
주의: 모터 전원은 구매하신 모델의 정격 전압(DC12V 또는 DC24V)으로 드라이버 전원 단자에 별도 공급하고, 모든 GND는 공통으로 묶으세요. 모터 상선(U/V/W)·홀센서·엔코더 결선은 드라이버 상세페이지의 결선도를 따르세요 — 엔코더 6선 중 EA−·EB−는 연결하지 않고 절연 처리합니다. 분해능·감속비 등록(c, d 명령)은 반드시 모터 정지·제어 Off 상태에서 실행하세요 — 제어 중 설정 변경은 오작동·과전류의 원인이 됩니다.
//==============================================================
// RTG42 로터리 중공축 엔코더 시리즈 + BLC-400R4E 아두이노 RS485 제어 예제 V1.0
// 적용: RTG42-BL4239E / 4259E / 4279E / 4299E (12V·24V 공통, 1000CPR)
// (MC-RS485 프로토콜 V1.0.2 문서 전수 대조본 기반)
// 보드: Arduino Uno + RS485 컨버터 모듈(MAX485 등)
//
// [통신 설정] 9600bps(제품 초기값), 8 Data, No Parity, 1 Stop
// [프레임 구조] FF FE | ID | Data크기 | Checksum | Mode | Data...
// Checksum = ~(ID + Data크기 + Mode + Data바이트 합)의 하위 1바이트
//
// [결선 - MAX485 모듈 기준] ※ 반드시 BLC-400R4E 상세페이지 결선도 기준
// 모듈 RO -> D10 / DI -> D11 / DE+RE(묶음) -> D3
// 모듈 A/B -> 드라이버 485 A/B (통신 안 되면 A/B 교차)
// 모듈 VCC -> 5V, GND -> 아두이노·드라이버 공통
//
// [이 모터 전용 초기 설정] 반드시 제어 Off(E) 상태에서, 명령 간 0.3초 이상 간격:
// c : 분해능 등록 - 1000CPR 모터는 CPR/4 규칙에 따라 250이 입력됩니다
// d5 / d10 : 감속비 등록 (★구매 옵션: 1/5 -> d5, 1/10 -> d10. 0.1비 단위 자동 환산)
// m / M : 상대 / 절대 위치제어 모드 전환 (인덱싱·무한회전은 상대 모드 권장)
// 설정은 EEPROM에 기록되므로 최초 1회만 하면 되고, 등록 후에는
// 위치(도)·속도(RPM)가 감속기 출력축 기준으로 동작합니다.
//
// [시리얼 모니터 사용법] (9600bps, "새 줄" 설정)
// e / E : 제어 On / Off (Off 상태에서는 제어 명령이 무시됩니다)
// f1000 : CW 100.0RPM (0.1RPM 단위, 도달시간 1초 가감속)
// r500 : CCW 50.0RPM
// s : 감속 정지
// g9000 : CW 90.00도 위치 이동 (0.01도 단위, 절대위치 0~655.33도)
// G9000 : CCW 90.00도 위치 이동
// i6 : 인덱싱 - 출력축을 360/6 = 60.00도씩 CW 분할 이동 (상대 모드에서 사용)
// z : 현재 위치를 0으로 초기화 (제어 Off 상태에서 권장)
// p / v : 위치 / 속도 피드백 요청
// [중공축 주의] 축 내부로 통과시키는 케이블은 모터 결선과 별개입니다 - 회전부와
// 간섭 없는지, 케이블 직경이 중공 구경에 맞는지 조립 전에 확인하세요.
//==============================================================
#include <SoftwareSerial.h>
const int RS485_RX = 10, RS485_TX = 11, RS485_EN = 3;
const byte MOTOR_ID = 0x00; // 제품 초기 ID 0x00 (변경했다면 수정)
const unsigned int ENC_PPR = 250; // 1000CPR / 4 (프로토콜 규칙: CPR은 4로 나눠 입력)
SoftwareSerial rs485(RS485_RX, RS485_TX);
// 프레임 송신: mode + data -> 헤더/크기/체크섬 자동 구성
void sendFrame(byte mode, const byte *data, byte dlen) {
byte size = dlen + 2; // Mode + Data + Checksum
int sum = MOTOR_ID + size + mode;
for (byte i = 0; i < dlen; i++) sum += data[i];
byte chk = (~sum) & 0xFF;
byte f[16]; byte n = 0;
f[n++] = 0xFF; f[n++] = 0xFE;
f[n++] = MOTOR_ID; f[n++] = size; f[n++] = chk; f[n++] = mode;
for (byte i = 0; i < dlen; i++) f[n++] = data[i];
while (rs485.available()) rs485.read(); // 수신버퍼 비우기
digitalWrite(RS485_EN, HIGH);
rs485.write(f, n);
rs485.flush();
digitalWrite(RS485_EN, LOW);
Serial.print(F("TX:"));
for (byte i = 0; i < n; i++) { Serial.print(' '); if (f[i]<16) Serial.print('0'); Serial.print(f[i], HEX); }
Serial.println();
}
// 수신 프레임 읽기 + 위치/속도 피드백(0xD1/0xD2) 해석
void readFeedback() {
byte buf[24]; byte n = 0;
unsigned long t0 = millis();
while (millis() - t0 < 300 && n < 24) {
if (rs485.available()) buf[n++] = rs485.read();
}
if (n == 0) { Serial.println(F("RX: (응답 없음 - 결선/ID/전원 확인)")); return; }
Serial.print(F("RX:"));
for (byte i = 0; i < n; i++) { Serial.print(' '); if (buf[i]<16) Serial.print('0'); Serial.print(buf[i], HEX); }
Serial.println();
for (int i = n - 4; i >= 0; i--) {
if (buf[i] == 0xFF && buf[i+1] == 0xFE) {
byte mode = buf[i+5];
if ((mode == 0xD1 || mode == 0xD2) && i + 11 < (int)n + 1) {
byte dir = buf[i+6];
unsigned int a = ((unsigned int)buf[i+7] << 8) | buf[i+8];
unsigned int b = ((unsigned int)buf[i+9] << 8) | buf[i+10];
byte cur = buf[i+11];
if (mode == 0xD1) { // 위치 피드백: 위치[0.01도] 속도[0.1RPM] 전류[100mA]
Serial.print(F("위치: ")); Serial.print(dir ? F("CW ") : F("CCW "));
Serial.print(a / 100.0); Serial.print(F("도, 속도 "));
Serial.print(b / 10.0); Serial.print(F("RPM, 전류 "));
Serial.print(cur * 0.1); Serial.println(F("A"));
} else { // 속도 피드백: 속도[0.1RPM] 위치[0.1도] 전류[100mA]
Serial.print(F("속도: ")); Serial.print(dir ? F("CW ") : F("CCW "));
Serial.print(a / 10.0); Serial.print(F("RPM, 위치 "));
Serial.print(b / 10.0); Serial.print(F("도, 전류 "));
Serial.print(cur * 0.1); Serial.println(F("A"));
}
}
break;
}
}
}
void speedMove(byte dir, unsigned int spd01rpm, byte time01s) { // 가감속 속도제어 0x03
byte d[4] = { dir, (byte)(spd01rpm >> 8), (byte)(spd01rpm & 0xFF), time01s };
sendFrame(0x03, d, 4);
}
void posMove(byte dir, unsigned int pos001deg, byte time01s) { // 가감속 위치제어 0x02
byte d[4] = { dir, (byte)(pos001deg >> 8), (byte)(pos001deg & 0xFF), time01s };
sendFrame(0x02, d, 4);
}
void setU16(byte mode, unsigned int v) { // 2바이트 설정값 송신
byte d[2] = { (byte)(v >> 8), (byte)(v & 0xFF) };
sendFrame(mode, d, 2);
}
void ctrlOnOff(bool on) { // 제어 On/Off 0x0C
byte d[1] = { on ? (byte)0x00 : (byte)0x01 };
sendFrame(0x0C, d, 1);
}
void setup() {
Serial.begin(9600);
rs485.begin(9600); // 제품 초기값 9600bps
pinMode(RS485_EN, OUTPUT);
digitalWrite(RS485_EN, LOW);
Serial.println(F("RTG42 encoder series + BLC-400R4E ready. (V1.0)"));
Serial.println(F("초기 1회(제어 Off 상태에서): c d5|d10 m | e/E f1000 r500 s g9000 G9000 i6 z p v"));
}
void loop() {
if (!Serial.available()) return;
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd.length() == 0) return;
char c = cmd.charAt(0);
long v = cmd.substring(1).toInt();
switch (c) {
case 'e': ctrlOnOff(true); Serial.println(F("제어 On")); break;
case 'E': ctrlOnOff(false); Serial.println(F("제어 Off")); break;
case 'f': speedMove(0x01, constrain(v, 1, 65533), 10); break; // CW, 도달 1.0s
case 'r': speedMove(0x00, constrain(v, 1, 65533), 10); break; // CCW
case 's': speedMove(0x01, 1, 5); delay(600); speedMove(0x01, 1, 1);
Serial.println(F("감속 정지")); break;
case 'g': posMove(0x01, constrain(v, 0, 65533), 10); break; // CW 위치
case 'G': posMove(0x00, constrain(v, 0, 65533), 10); break; // CCW 위치
case 'c': setU16(0x0A, ENC_PPR); // 분해능 설정(10번) - EEPROM 기록, 제어 Off에서
Serial.println(F("분해능 등록: 1000CPR -> 250 입력 (CPR/4 규칙)")); break;
case 'd': if (v < 1) { Serial.println(F("감속비를 입력하세요 (예 d14)")); return; }
setU16(0x0B, (unsigned int)(v * 10)); // 감속비 설정(11번) - 0.1비 단위
Serial.print(F("감속비 1/")); Serial.print(v);
Serial.print(F(" 등록 (전송값 ")); Serial.print(v * 10); Serial.println(F(")")); break;
case 'm': { byte d[1]={0x01}; sendFrame(0x0D, d, 1);
Serial.println(F("상대 위치제어 모드 (인덱싱용)")); break; }
case 'M': { byte d[1]={0x00}; sendFrame(0x0D, d, 1);
Serial.println(F("절대 위치제어 모드")); break; }
case 'i': { // 인덱싱: 360/n 도씩 CW 상대 이동 (예 i6 = 60도씩, i12 = 30도씩)
if (v < 2 || v > 360) { Serial.println(F("분할 수 2~360 (예 i6)")); return; }
unsigned int step001deg = (unsigned int)(36000UL / v); // 0.01도 단위
posMove(0x01, step001deg, 10);
Serial.print(F("인덱스 이동: ")); Serial.print(step001deg / 100.0);
Serial.println(F("도 (상대 모드 확인)"));
break;
}
case 'z': sendFrame(0x0F, nullptr, 0); Serial.println(F("위치 0 초기화 (15번 명령)")); break;
case 'p': sendFrame(0xA1, nullptr, 0); readFeedback(); return;
case 'v': sendFrame(0xA2, nullptr, 0); readFeedback(); return;
default: Serial.println(F("? e/E c d5 m/M f1000 r500 s g9000 G9000 i6 z p v")); return;
}
readFeedback(); // 명령 후 응답/에코 로그 표시
}
사용 순서: 최초 1회 제어 Off(E) 상태에서 c(분해능, 1000CPR→250 자동 적용) → d5 또는 d10(구매 감속비) → m(상대 위치제어 모드)을 각 0.3초 이상 간격으로 등록하면 준비 끝입니다. 이후 i6(60도씩)·i12(30도씩)처럼 분할 수만 바꿔 인덱싱하고, p로 실제 위치를 읽어 1arcmin급 반복 정밀도를 직접 확인할 수 있습니다. 속도는 0.1RPM 단위, 위치는 0.01도 단위이며 감속비 등록 후 출력축 기준으로 동작합니다. 중공축 안으로 통과시키는 케이블은 모터 결선과 별개이니 조립 전 구경·간섭을 확인하세요. 송수신 HEX 로그가 출력되므로 문제 발생 시 로그를 상품문의에 붙여 주세요.
필요한 구성 — 본품(감속비 1/5 또는 1/10 선택) +
BLC-400R4E 드라이버 → + 아두이노 UNO + RS485 컨버터 모듈(별도 준비) + 모델 정격에 맞는 전원(DC12V/24V)
동작이 안 되면 상품문의 게시판에 ①구매하신 감속비 옵션 ②결선 사진 ③시리얼 모니터의 TX/RX 로그를 남겨 주세요. 기술팀이 확인해 드립니다.
작성: MotorBank 기술팀 · 최종 업데이트: 2026-08-16