F/T 센서
Neuromeka Indy7 v3 의 툴 플랜지와 그리퍼 사이에 Aidin AFT200-D80 힘/토크(F/T) 센서를 달 수 있습니다. 값은 툴 보드가 EtherCAT 으로 중계하므로 따로 배선하거나 드라이버를 띄울 필요가 없습니다.
한눈에 보기
| 단계 | 어디서 | 결과 |
|---|---|---|
| 1. 설정 | 로봇 프로필에서 센서 모델 선택 | 다음 스택 기동부터 펜던트·TUI·Lab 에 값이 보입니다 |
| 2. 영점 | 펜던트 F/T 카드의 영점 | 센서가 지금 값을 0 으로 센서 안에 저장합니다(전원을 꺼도 유지) |
| 3. 읽기 | 플러그인 measured.wrench() | 영점이 이미 빠진 값이 옵니다. 코드에서 영점을 뺄 필요가 없습니다 |
| 4. 공구 무게 빼기(선택) | 펜던트 무게 측정 → 플러그인 ToolLoad | 자세가 바뀌어도 공구 무게를 뺀 외력만 남습니다 |
값은 센서 좌표 그대로이고 중력 보상을 하지 않습니다. 그리퍼 무게는 영점을 잡은 자세에서만 0 으로 보이고, 자세가 바뀌면 다시 힘으로 나타납니다. 이것까지 빼려면 4단계를 씁니다.
센서 축은 툴 좌표축과 방향이 같고, 센서에 가해진 힘의 방향이 + 입니다. 툴을 +Z 방향으로 밀면 Fz 가 커집니다(실기 누르기 시험으로 확인).
1. 설정
로봇 프로필에서 F/T 센서 모델을 고릅니다. 아래 중 아무 곳에서나 하면 됩니다.
- TUI 설정(
,)의 로봇 편집 - Teaching Pendant 설정의 로봇 편집
plem profile set-ft-sensor <로봇> aft200_d80(해제는none)
저장한 값은 다음 스택 기동부터 적용됩니다. 그리퍼·카메라는 센서와 상관없이 따로 고릅니다.
- 지원하지 않는 로봇이면 저장을 거절합니다. AFT200-D80 은 Neuromeka Indy7 v3 만 지원합니다.
- 툴 보드 펌웨어에 F/T 중계가 없으면 저장은 되고, 기동 뒤 「툴 보드가 F/T 신호를 내지 않습니다」로 알려 줍니다.
- 프로필을 손으로 고쳐 센서를 적었는데 로봇 설명(URDF)에 센서가 없으면, 로봇은 기동하되 로그에 경고를 남기고 F/T 를 켜지 않습니다. 펜던트에는 F/T 칩이 나오지 않고, TUI 에는 「no signal」이 계속 보입니다. 센서가 없는 로봇이면 프로필에서 해제하세요.
센서를 끼우거나 빼면 툴 끝 높이가 바뀌어 카메라 위치도 달라집니다. 저장·기동은 막지 않고 경고만 한 번 띄웁니다. 카메라를 계속 쓰려면 캘리브레이션을 다시 하세요.
2. 펜던트에서 영점 잡기
① 3D 화면 왼쪽 아래의 F/T 칩을 누릅니다. 정상이면 F/T ✓ 옆에 힘과 토크의 크기가 보입니다. 영점 전이나 자세를 바꾼 뒤에는 그리퍼 무게 때문에 힘이 보이는 것이 정상입니다.

② 카드에서 영점을 누릅니다. 공구에 아무것도 닿지 않게 하고, 값을 볼 자세로 로봇을 둔 뒤에 누릅니다.

③ 확인 창에서 영점 잡기를 누릅니다.

④ 막대가 0 근처로 돌아오면 끝입니다. 로봇이 영점 직후 값을 다시 읽어 축마다 힘 1 N·토크 0.05 N·m 이내일 때만 성공을 알립니다. 실패하면 그때 값과 이유가 나오니, 툴을 움직이지 말고 다시 누르세요.

영점을 잡을 수 없는 때:
- 펜던트가 버튼을 잠급니다: 연결 끊김, 수신 없음, 센서 오류, Job 실행 중, 조그 중.
- 로봇이 사유와 함께 거부합니다: 센서 기동 절차 중, 센서 미인식, 정격 초과, 로봇 이동 중, 이전 요청 처리 중. 이 거부는 펜던트든 ROS 2 서비스든 같습니다. Job·조그 중 잠금은 펜던트만 하므로, 프로그램에서 영점을 잡을 때는 호출하는 쪽이 시점을 고르세요.
OUT_OF_RANGE) 상태의 영점이 상태에서는 영점을 막지 않습니다. 걸린 힘을 모두 푼 뒤 영점을 잡는 것이 이 상태를 푸는 방법이기 때문입니다. 힘이 걸린 채로 잡으면 틀린 영점이 센서에 저장되니, 반드시 하중을 뺀 뒤 잡으세요.
3. 토크 제어기 플러그인에서 읽기
커스텀 토크 컨트롤러(plem init my_controller 로 만든 플러그인)의 include/my_controller/my_controller.hpp·src/my_controller.cpp 에 있는 compute() 에서 매 제어 주기 읽습니다. 펜던트에서 잡은 영점은 센서가 이미 적용했으므로, 받은 값을 그대로 쓰면 됩니다. 아래 예제는 보기 쉽게 한 파일에 모았습니다.
#include <plem/control/plugin/torque_plugin_sdk.hpp>
namespace my_controller {
using namespace plem::control::plugin;
class MyController : public TunableController {
public:
using TunableController::TunableController;
void compute(const Measured& measured, const Target& target,
double* output_torque, bool /*free_drive*/) const override {
for (std::size_t j = 0; j < measured.dof(); ++j) {
output_torque[j] = 0.0; // ← 여기에 힘 없이도 도는 제어 법칙
}
const auto w = measured.wrench(); // 영점이 빠진 값
if (!w) {
return; // 센서 없음·이번 주기 무효 → 힘 항 없이
}
const double fz = w->force.z; // [N]
const double tx = w->torque.x; // [N·m]
// fz·tx 로 힘 항을 output_torque 에 더한다
}
void reset() noexcept override {}
};
} // namespace my_controller
PLEM_TORQUE_PLUGIN(my_controller::MyController);
| 접근자 | 돌려주는 것 |
|---|---|
measured.wrench() | force.{x,y,z} [N]·torque.{x,y,z} [N·m]·frame_id(센서 좌표계). 값이 없으면 std::nullopt |
measured.wrench_state() | 값이 없는 이유. 값이 있으면 OK (아래 표) |
measured.wrench_zero_seq() | 영점 명령이 들어갈 때마다 바뀌는 번호(펜던트가 실패라고 알려도 바뀔 수 있음) |
NaN 이 나가면 로봇이 모든 관절 토크를 0 으로 하고 보호 정지합니다. wrench() 가 비어 있는 주기에는 힘 항을 빼고 제어하세요.
- 센서가 없는 로봇에서도 플러그인은 그대로 올라가고,
wrench()는 늘 비어 있습니다. frame_id는 센서의 측정 원점입니다. AFT200-D80 은 벤더 CAD 기준 F/T 스택 꼭대기보다 14.8 mm 아래입니다.- F/T 읽기가 없던 때 빌드한 플러그인은 다시 빌드하지 않아도 전과 똑같이 동작합니다.
영점이 바뀐 순간 알아채기
힘 기준값을 플러그인이 따로 들고 있을 때만 필요합니다. 영점이 도는 동안은 ZEROING(값 없음)이고, 끝나는 순간 wrench_zero_seq() 가 바뀝니다. 그 첫 주기의 값 변화는 접촉이 아니라 영점이므로 기준을 다시 잡습니다.
// compute() 안, w 가 있을 때
if (measured.wrench_zero_seq() != last_zero_seq_) {
last_zero_seq_ = measured.wrench_zero_seq();
baseline_fz_ = w->force.z;
}
// 멤버 — compute() 가 const 라 mutable
mutable std::uint32_t last_zero_seq_ = 0;
mutable double baseline_fz_ = 0.0;
4. 공구 무게 빼기 (선택)
자세와 상관없이 외력만 쓰고 싶을 때 씁니다. 재지 않아도 경고하거나 막지 않습니다.
첫 측정 자세까지 팔 전체가 움직일 수 있습니다. 시작하기 전에 주변을 비우고, 공구에 아무것도 닿지 않게 하세요.
① 로봇을 TRAJ 모드로 둡니다. 보호정지 중이 아니어야 하고, BRAKED 등 다른 모드에서는 측정 자세로 움직이지 않습니다.
② F/T 카드의 무게 측정 → 측정 시작을 누릅니다. 로봇이 정해진 측정 자세 4곳(관절각 고정)을 돌고 원래 자세로 돌아옵니다. 30 초 안팎 걸리고, 카드의 중지 로 언제든 멈춥니다.

③ 결과의 복사를 누릅니다. 질량 [kg] 과 센서 좌표 기준 무게중심 [m] 4개 값이 복사됩니다. 「잔차가 큽니다」가 나오면 공구·케이블이 아무 데도 닿지 않게 하고 다시 잽니다.

④ 복사한 값으로 ToolLoad 를 만들고, wrench() 대신 compensate() 를 씁니다. tool_load.hpp 는 plem init 이 늘 함께 생성합니다. §3 예제의 using TunableController::TunableController; 줄을 아래 생성자로 바꿉니다. plem 은 이 값을 저장하지 않으니 본인 프로젝트(코드·파일·plugin_config_json)에 두세요.
#include "my_controller/tool_load.hpp"
namespace my_controller {
using namespace plem::control::plugin;
class MyController : public TunableController {
public:
explicit MyController(const PlemPluginContext* ctx)
: TunableController(ctx),
tool_load_(ctx, ToolLoad::Params{1.753, {0.005, -0.001, 0.103}}) {} // ③ 에서 복사한 값
void compute(const Measured& measured, const Target& target,
double* output_torque, bool /*free_drive*/) const override {
// ... output_torque 를 채우는 부분은 §3 과 같음
const auto f = tool_load_.compensate(measured); // 영점·공구 무게가 빠진 외력
if (!f) {
return;
}
const double fz = f->force.z; // [N]
}
void reset() noexcept override {}
private:
ToolLoad tool_load_;
};
} // namespace my_controller
- 기준은 자동으로 잡힙니다. 기동 뒤 로봇이 처음 150 ms 정지했을 때(보통 첫 브레이크 해제 직후)와, 영점을 다시 잡은 뒤 로봇이 150 ms 정지해 있을 때마다 그 순간 값을 기준으로 삼습니다. 그 전에는
std::nullopt입니다. 매 기동마다 영점을 잡을 필요는 없습니다. - 기준을 잡는 순간 공구에 아무것도 닿아 있지 않아야 합니다. 어긋났다면 아무것도 닿지 않은 상태에서 영점을 한 번 잡으면 바로잡힙니다.
- F/T 센서가 없는 로봇에서는
ToolLoad생성이 실패해 플러그인이 올라가지 않습니다. - 그리퍼·카메라·장착을 바꾸면 다시 잽니다. 측정 자세는 지금 Indy7 에만 있습니다.
- 기준 자세에서 멀어질수록 오차가 커집니다. 공구 무게가 센서에 걸리는 방향이 바뀐 만큼의 5
7% 정도가 남습니다(Indy7 + RG6 실기에서 보통 0.40.7 N, 손목을 60° 넘게 돌리면 약 1.2 N). 접촉 판정 기준은 이보다 크게 잡으세요. - 정지·저속을 전제로 합니다(관성력 항 없음). 기준 이후의 센서 표류는 남습니다.
- 설치 기울기, 케이블 장력, 그리퍼 손가락 위치, 파지물 무게는 반영되지 않습니다. 손가락은 평소 상태로 두고 잽니다.
화면에서 보기
| 위치 | 보이는 것 |
|---|---|
| 펜던트 F/T 칩 | 정상이면 F/T ✓ 와 힘·토크 크기, 아니면 상태 문장. 1 초 동안 값이 없으면 「수신 없음」 |
| 펜던트 F/T 카드 | 상태(정상·경고·센서 오류·수신 없음), 정격 대비 부하(%), 축별 힘(N)·토크(N·m) 막대(가운데 0, + 는 오른쪽), 3D 힘 화살표 켜기·끄기, 영점, 무게 측정 |
| 펜던트 3D 화면 | 힘은 센서를 가리키는 화살표, 토크는 원호. 힘 1 N·토크 0.05 N·m 이상이면 나타나고 그 70% 아래로 내려가야 사라집니다. 값이 무효가 되면 바로 사라집니다 |
| 펜던트 하단 로봇 제어 바 | 경고·오류일 때만 「F/T 경고」·「F/T 센서 오류」 |
| TUI 로봇 상세 | F/T aft200_d80 ● ok |F|=… |T|=… load=…%, 이상하면 상태 문장 |
펜던트는 첫 번째 로봇의 센서만 보여 주고, TUI 는 로봇마다 보여 줍니다. 워크플로와 스킬에서는 힘 값을 조건이나 입력으로 쓸 수 없습니다. Plem Lab 에서는 그래프로 보고 파일로 내보낼 수 있습니다.
정격과 상태
정격은 힘 200 N·토크 15 N·m 입니다. 복합 부하(|Fxy|/200 + |Tz|/15 와 |Fz|/200 + |Txy|/15 중 큰 값)가 105% 를 넘으면 값을 무효로 하고 경고하며, 100% 아래로 내려오면 풀립니다.
OK 가 아니면 값은 무효(NaN)이고, 플러그인의 wrench() 는 비어 있습니다. 화면에는 상태 문장이, ROS 2 상태 토픽과 플러그인 wrench_state() 에는 같은 이름의 코드가 갑니다.
state | 상태 문장(앞부분) | 뜻 | 조치 |
|---|---|---|---|
OK | OK | 정상 | — |
ZEROING | Zeroing the F/T sensor | 영점 진행 중(보통 1 초 이내, 길면 몇 초). 끝나면 zero_seq 가 바뀌고 값이 돌아옵니다 | 기다립니다 |
STARTING | The F/T sensor is starting up | 스택 기동 직후 센서 준비 중. 로봇 버스를 먼저 기다려 수십 초 걸릴 수 있습니다 | 기다립니다 |
OVER_RATED | Rated load exceeded | 정격 초과 | 센서에 걸린 힘을 줄입니다 |
OUT_OF_RANGE | Force or torque exceeded the measurement range | 한 축이라도 측정 범위(토크 약 16.4 N·m, 힘 약 655 N)를 넘어 값이 뒤집혔을 수 있음. 아주 짧고 큰 충격이나 기동 때 이미 넘어 있던 하중은 못 잡을 수 있습니다 | 걸린 힘을 모두 풀고 영점을 다시 잡습니다 |
NOT_RESPONDING | The F/T sensor is not responding | EtherCAT 통신 끊김, 케이블 분리·접촉 불량, 툴 보드 자동 인식 실패, 센서 고장 중 하나 | 통신 상태와 케이블을 확인하고 로봇 전원을 껐다 켠 뒤 로봇 스택을 재시작합니다. 센서 모드 설정은 스택 기동 때만 하므로 재시작이 확실한 복구입니다 |
FROZEN | The F/T sensor values are not updating | 6축 값이 20 ms 넘게 전혀 바뀌지 않음. 케이블 분리·접촉 불량, 센서 전원, 툴 보드 이상일 수 있습니다(케이블이 빠져도 툴 보드는 마지막 값을 계속 보냅니다) | 케이블과 연결을 확인합니다. 값이 다시 바뀌면 저절로 풀립니다. 다시 연결한 직후에는 값이 한동안(실기에서 수십 초) 흘러갈 수 있으니 기다린 뒤 영점을 다시 잡습니다 |
NO_RELAY | The tool board provides no F/T signal | 툴 보드 펌웨어가 F/T 중계를 지원하지 않거나 이 로봇에 중계가 없음 | 로봇 기동 로그의 F/T 경고를 확인합니다 |
STALE | (플러그인만) | 새 값이 5 ms 넘게 오지 않음. 계속될 수 있습니다 | 힘 항 없이 제어합니다 |
NO_SENSOR | (플러그인만) | 센서가 없는 로봇 | — |
STARTING·ZEROING·OVER_RATED·OUT_OF_RANGE 는 경고(WARN), 나머지 이상은 오류(ERROR) 수준입니다. 센서 문제로 로봇이 멈추지는 않고, 그동안 F/T 값만 무효가 됩니다.
ROS 2 로 쓰기
로봇 이름이 robot_1 이면 이렇게 확인합니다.
ros2 topic echo /robot_1/ft_sensor/wrench # 힘·토크 (100 Hz)
ros2 topic echo /robot_1/ft_sensor/status # 상태 — values 의 state·zero_seq
ros2 service call /robot_1/ft_sensor/zero std_srvs/srv/Trigger # 영점
토픽·서비스의 전체 규약(NaN, 좌표 변환, 웹 클라이언트 주의점)은 ROS 2 통합에 있습니다.