Category (Click)
개발보드 덕질하기

GIM6010-8 모터 돌려보기 (feat. ODrive)

 차완기 - @5/31/2025, 1:40:00 AM
지난 포스팅에서 언급했듯이 관절에는 SteadyWin의 GIM6010-8 모터를 사용합니다. 유성기어가 포함된 모터 단품, 케이스에 조립된 모터, 컨트롤러가 포함된 모터 등등 모터 자체는 여러 옵션이 있었습니다. 모터 고수라면 저렴한 모터 단품, 그리고 모터에 맞는 드라이버를 체리피킹해 사용할텐데요, 초짜인 제가 어설프게 따라했다가는 이중지출에 무한 삽질 당첨이 뻔해보여 모터드라이버와 엔코더가 포함된 모터를 구매했습니다.
사실 이런 고민을 하던 중에 결정적으로 기본 드라이버도 괜찮다!! 라는 글을 보게 되어 결정하게 된 것이긴 합니다.
그런데, 판매 페이지를 살펴보면 조금 이상한 문구가 보입니다. secondary encoder..? 엔코더도 failsafe가 있나..? 하고 조금 검색해보니 기어를 거친 모터이기 때문에 FOC를 위한 엔코더 하나, 기어비를 거친 출력축의 절대 위치를 알기 위한 엔코더. 이렇게 2개가 필요한 것이었습니다.
즉, secondary encoder라고 햇갈리게 적어서 그렇지 그냥 샤프트 엔코더인 것이죠. 번외로 기어가 없어 모터의 출력을 곧바로 써먹는 모터를 “direct drive” 모터, 지금 제가 가지고 있는 GIM6010-8과 같은 모터를 “indirect drive” 모터라 부른다고 합니다.
시간이 흘러 모터가 도착했습니다.
실물을 만져보니 생각보다 크고 묵직하더라고요. 무언가 산업산업 하게 생겨서 내가 이걸 돌린다고?? 하고 있습니다 ㅋㅋ..
처음 보는 신기한게 눈앞에 있다? 바로 뜯어봐야죠.
모터드라이버를 감싸고 있는 커버를 분리했습니다. 아랫쪽에 달려있는 검은색 커넥터가 신기했는데요, XT30(2+2)라는 커넥터였습니다. 전원 뿐만 아니라 CAN 통신 신호를 위한 2개의 핀이 더 뽑혀있는 일체형 커넥터였습니다. 나름 좋은 아이디어 인것 같기도 하고.. 한편으로는 도대체 이런 혼종을 누가 만든건가 했는데 XT60, XT30 등 RC 커넥터로 유명한 Amass에서 만든 커넥터였습니다. 이럴수가..
조금 더 뜯어봤습니다.
모터드라이버 정중앙(uC 왼쪽)에 있는 정사각형이 BLDC 모터의 회전을 측정하기 위한 마그네틱 엔코더인 것 같습니다.
뭔가 초록 기판이 하나 더 있어, 이것도 분해해봤는데요, 아무래도 이게 secondary encoder 라고 하는 녀석인 것 같습니다. 사진상에서는 잘 보이지 않지만 모터의 * 모양 기둥 아래에 검은색 자석이 있는데, 비슷한 위치에 다리 3개 달린 검은색 IC가 4개 있습니다. 아무래도 이게 홀 센서인것 같고, 귀퉁이에 붙어있는 IC가 홀 센서 데이터를 serialize하는 녀석인 것 같습니다.
나사가 몇개 더 보여 풀어봤는데요, 더 이상은 모터드라이버에 납땜된 케이블을 분리하지 않고서는 분해가 불가능했습니다. 유성기어도 구경하고 싶었지만 돌이킬 수 없어지기 전에 그만두기로 했습니다 ㅋㅋ

그래서 모터 어떻게 돌림?

사실 설명서라도 넣어줄줄 알았는데 아무것도 없이 모터만 덜렁 있었습니다.
SteadyWin - Document Download [링크]
다행이라 해야할지 구글에서 모터 제조사인 SteadyWin를 검색한 후 조금 뒤져보니 GIM6010-8 모터의 메뉴얼이 있었습니다. 그으런데..
… 중국어네요..
구글 번역기와 눈치를 삭삭 보니 ODrive라는 녀석을 사용해 모터를 제어하고 있었습니다.
ODrive - Shop [링크]
처음 모터의 구매 버튼을 만지작거리고 있을 때 다들 사용하고 있길레 기웃거렸던 그 모터드라이버인데요, 아두이노가 하드웨어와 소프트웨어를 싸잡아서 부르는것처럼 ODrive도 같은 이름의 하드웨어, 소프트웨어, 펌웨어가 있었습니다.
이걸 갈아서 저기 집어넣은건 당연히 아닐거고 ㅋㅋ 조금 찾아보니 지금은 ODrive가 Closed Source이지만, 과거 오픈소스 시절일 때 공개되어 있던 ODrive v3.6 HW와 0.5.6를 커스터마이징해 사용한 모터드라이버가 바로 GIM6010-8에 들어간 GDS68 드라이버인 것이었습니다.
ODrive forum - Customisation of New ODrive Generation [링크]
과거에는 오픈소스..? 최신버전은 NDA..? 어... 일단 모터 돌리러가볼게요,,

모터 돌려보기

전원을 연결한 후 USB를 통해 PC와 연결했습니다. CAN통신을 위한 통신선은 연결하지 않은 상태입니다.
ODrive의 공식 가이드를 따라해 PC측 ODrive 제어 프로그램인 odrivetool을 설치했습니다. 파이썬 환경을 아작내고 싶지는 않았기 때문에 venv를 하나 생성해서 진행했습니다.
odrivetool을 실행했더니 USB로 연결된 모터가 자동으로 인식되었습니다. ODrive의 Python 패키지를 실행하는 Python 인터프리터 프롬프트이기 때문에 Python 명령을 그대로 먹는다고 합니다.
속성별 기능은 ODrive API Reference를 참고했습니다.
모터가 인식되면 객체가 생성되는 것 같습니다. 방금 인식된 odrv0을 입력했더니 여러 속성들이 주르륵 나오네요. 여기 데이터를 집어넣거나 읽으면서 모터를 제어합니다.

모터 켈리브레이션

설명서에 따르면 모터를 처음 구매하면 위의 켈리브레이션 과정을 거쳐야 한다고 합니다.
odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION odrv0.axis0.motor.config.pre_calibrated = 1 odrv0.axis0.encoder.config.pre_calibrated = 1 odrv0.save_configuration()
Python
복사
AXIS_STATE_MOTOR_CALIBRATION: 모터의 전기적 속성 측정
AXIS_STATE_ENCODER_OFFSET_CALIBRATION: 자세히는 모르겠지만 모터에 공급되는 신호의 위상 변화(→실제 모터의 움직임)에 따른 엔코더의 데이터 편차를 비교하는 과정이라 이해했습니다.
켈리브레이션 하나를 실행하고 dump_errors(odrv0) 명령을 입력해 모터의 상태를 확인하라 하는데, 다 하고 보게 되었습니다.
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
Python
복사
이후 위 명령을 입력하면 피드백 제어가 시작됩니다. 엔코더의 실제 신호를 읽어 작동하는지 모터 축을 만져보니 단단하게 고정되어 있습니다.
그런데 모터가 조금씩 부들부들거리네요. 뭔가 PID값이 맞지 않나봅니다.
odrv0.axis0.controller.config.pos_gain=20.0 odrv0.axis0.controller.config.vel_gain=0.16 odrv0.axis0.controller.config.vel_integrator_gain=0.32
Python
복사
설명서의 예시 PID값을 그대로 사용했을 때 당장은 큰 문제가 없는것 같아서 당분간 이렇게 사용합니다. 혹시 튜닝이 필요하다면 나중에 다뤄보려 합니다.

속도 제어 모드

모터의 제어 모드는 토크, 속도, 위치 3가지가 있고, 단계가 변할 때 마다 이전 단계 제어를 깔고 간다고 합니다. 즉, 위치 제어 모드에서는 토크, 속도 두 제어가 함께 되는것이죠.
우선 속도 제어 모드로 모터를 돌려봤습니다. 입력값의 단위는 초당 회전수입니다. (turn/s)
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL odrv0.axis0.controller.config.input_mode = INPUT_MODE_VEL_RAMP odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL odrv0.axis0.controller.input_vel = 15
Python
복사
오.. 슈슈슉 하면서 돌아가네요. 굉음을 내면서 돌아가는 전동보드 모터와는 또 다른 느낌입니다.
영상에서는 입력하지 않았지만 - 값을 입력하면 모터가 반대 방향으로 돌아갑니다.

위치 제어 모드

다음으로 위치 제어 모드입니다. 단위는 회전수인데, GIM6010의 경우 1:8 엔코더가 달려있기 때문에 실제 회전수는 8을 나눈 값이 됩니다.
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL odrv0.axis0.controller.config.input_mode = INPUT_MODE_POS_FILTER odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL odrv0.axis0.controller.input_pos = 0.0
Python
복사
부드럽게 잘 움직이네요. 예전에 아무것도 모르는 상태에서 모터 각도를 PID로 제어하느라 해딩했던 기억이 납니다 ㅋㅋ..
이것 역시 - 값을 입력하면 반대 방향인 반시계 방향으로 움직이게 됩니다.

모니터(Liveplotter)

데이터 시각화 중요하죠. ODrive에서도 데이터 시각화를 위한 그래프를 제공합니다.
start_liveplotter(properties=[ odrv0._ibus_property, odrv0.axis0.encoder._pos_estimate_property, odrv0.axis0.controller._input_pos_property, ], )
Python
복사
전류, 엔코더 위치, 입력 위치를 출력하도록 해봤습니다.
이야..정말 편하네요 ㅋㅋㅋ

secondary encoder 활용

모터 작동은 잘 알겠고, 돈을 더 쓰게 만든 secondary encoder도 봐야겠습니다.
start_liveplotter(properties=[ odrv0.axis0.encoder._pos_estimate_property, odrv0.axis0.controller._input_pos_property, ], )
Python
복사
pos_estimate와 그래프의 스케일을 위해 0으로 둘 input_pos를 띄운 후 손으로 모터를 1회전 이상 시켰습니다. 만약 샤프트 엔코더가 없다면 껐다 켰을 때 1회전 이상을 보존할 수 없겠죠. 재부팅 후 같은 회전수를 유지하는걸 보아 샤프트 엔코더가 잘 작동하는 것 같습니다.
그럼 한 가지 문제가 생기죠. 로봇을 조립할 때 모터의 0점을 정확히 맞추지 않으면 모터마다 pos가 틀어져 로봇 해먹기 딱 좋은건데요, 이때는 index_offset을 조절해 해결한다고 합니다.
odrv0.axis0.encoder.config.index_offset=odrv0.axis0.encoder.pos_estimate odrv0.save_configuration()
Python
복사
save_configuration() 명령을 날려 저장까지 해줘야 실제로 반영되네요.

마무리

처음 사용하는 ODrive이다 보니 도대체 얼마나 삽질을 하려나, 그리고 혹시라도 작동이 안되면 어쩌지 하는 걱정이 있었는데요, 삽질을 충분히 하고 돌아가는 모터를 보니 마음이 후련합니다.
다음 포스팅에서는 MCU에서 CAN 통신을 통해 모터를 제어해보려 합니다. CAN통신도 처음이라 ㅋㅋ.. 갈길이 아주 머네요 🫠