============================== 누리로봇 제어 프로토콜 메뉴얼 ============================== .. toctree:: :maxdepth: 6 온라인 메뉴얼 URL: https://manuals.nurirobot.info/latest/rs485/index.html **적용대상**: 누리로봇 RS485 기반 제어기 / 액추에이터 전 제품군 1. 문서개요 ------------ 1.1 목적 ++++++++ RS485 기반 누리로봇 제품 제어를 위한 통신 프로토콜을 명확히 이해하고 적용할 수 있도록 구조, 명령 체계, 피드백 구조를 정의한다. 1.2 적용 범위 +++++++++++++ 본 문서는 아래 제품에 공통적으로 적용된다. * BLDC 모터 컨트롤러(RS485 통신) * BCA3508 * BCA3516 * BCA3516E * BCA5008 * BCA5016 * BCA5016E * DC 모터 컨트롤러(RS485 통신) * DCA3508 * DCA5008 * 스마트모터(RS485 통신) * SA 모터군 * SA59 * SA70 * SA84 * SA112 * SB 모터군 * SB60 * SC 모터군 * SC70(SM70) -------- 2. 시스템 구성(System Architecture) -------------------------------------- 2.1 통신 개요 +++++++++++++ * RS485 Half-Duplex * Data bit: 8bit / Stop bit: 1bit / Parity: None * Baudrate: 1,200 ~ 1,000,000bps (출고 기본값: 9600bps) * 통신 구조: 1:N Master–Slave 2.2 네트워크 구성 예시 ++++++++++++++++++++++ **구성 예시** .. image:: 모터컨트롤러_20251112-배선도.png :width: 6in :align: center **구성 권장** .. image:: 모터컨트롤러_20251112-배선도_권장.png :width: 6in :align: center * Daisy-chain 방식 권장 * 종단저항 반드시 양 끝단 120Ω 적용 * Shielded Cable 권장 * 모든 모듈의 GND 공통 연결 **※ 제품(모터제어기, 스마트모터) 구성 상 전원부와 통신부가 2개씩으로 구성되어 있으나 두개 모두 사용하는 것을 권장하지 않음.** .. raw:: pdf PageBreak 테스트 2.3 Broadcast 사용 주의사항 +++++++++++++++++++++++++++ * ID 0xFF는 Broadcast 전용 * Broadcast 사용 시 피드백 요청 금지 * 단, 응답시간 차등 설정 시 동시 Broadcast 피드백 가능 * 예) 통신속도가 115200bps 일 때, ID 1번 응답시간(us) 1(100us) 설정, ID 2번 응답시간(us) 21 (2100us = 2.1ms), ID 3번 응답시간(us) 42 (4200us = 4.2ms), ID 4번 응답시간 63(6300us = 6.3ms)으로 설정할 경우 위치피드백이나 속도피드백을 Broadcast 방식으로 1회 호출하여, 4대의 장비의 피드백 수신이 가능하다. **일반적인 다중 피드백** .. image:: Pasted_image_20251113095312.png :width: 6in :align: center **Broadcast 피드백** .. image:: Pasted_image_20251113100951.png :width: 6in :align: center .. raw:: pdf PageBreak 2.4 DC 전원 배선 ++++++++++++++++ * 연결방식 * 병렬 * 버스형 .. image:: 모터컨트롤러_20251112-DC_전원_분기.png :width: 6in :align: center .. image:: 모터컨트롤러_20251112-DC_전원_병렬.png :width: 6in :align: center * 반드시 모든 노드의 전원 GND를 공통으로 연결 * 전원 제어를 **LowSide Switch 제어**\를 할 경우 **통신선·신호선을 통해 역전류 유입** 통신 IC 손상. * 전원 제어는 HighSide Switch 제어로 구성하여야 한다. .. image:: Pasted_image_20251113151335.png :width: 6in :align: center * Low Side Switch는 모든 그라운드 공유된 제품이 동시에 전원이 꺼질 경우, 외부의 별도 전원을 쓰는 장치와 연결이 되지 않을 경우에만 안전하다. * Low Side Switch가 비용적 이득이 있음. * Low Side Switch를 릴레이 스위치 제어용으로 사용하고 릴레이 스위치를 전원 제어로 사용하면 전류 이슈 등에서 자유로울 수 있다. .. raw:: pdf PageBreak 3. 데이터 프레임 구조 (Packet Structure) ----------------------------------------- .. list-table:: :header-rows: 1 :widths: 18 12 12 58 * - 필드명 - 크기(Byte) - Index - 설명 * - Header - 2 - [0-1] - Header (0xFFFE) * - ID - 1 - [2] - ID (0x00~0xFE, 0xFF=Broadcast) * - Data Size - 1 - [3] - Data Size (N) * - Check Sum - 1 - [4] - Checksum = ~((ID + Size + Mode + Data...) & 0xFF) * - Mode - 1 - [5] - 명령 구분 코드 * - Values - n - [6..] - 명령에 따른 데이터 영역 .. image:: 모터컨트롤러_20251112-페이지-5.png :width: 5in :align: center 3.1 Checksum 계산식 ++++++++++++++++++++ .. code-block:: c CheckSum = ~(([ID + DataSize + Mode + Data...]) & 0xFF) 3.2 송수신 예시 ++++++++++++++++++ .. code-block:: c TX : 0xFF 0xFE 0x00 0x02 0x5C 0xA1 RX : 0xFF 0xFE 0x00 0x08 0x26 0xD1 0x00 0x00 0x00 0x00 0x00 0x00 .. raw:: pdf PageBreak 4. 명령 체계 (Command Set Overview) -------------------------------------- 4.1 명령 분류 +++++++++++++ .. list-table:: :header-rows: 1 :widths: 16 24 60 * - 구분 - Mode 범위 - 설명 * - 제어 명령 - 0x01~0x03 - 위치, 속도, 가감속 제어 * - 설정 명령 - 0x04~0x0F - 제어기 설정, 통신 설정, ID, 감속비 등 * - 특수 명령 - 0x10~0x11 - 공장 초기화, Open-loop 제어 * - 요청 명령 - 0xA0~0xAA, 0xCD - 피드백 요청 * - 응답 명령 - 0xD0~0xFD - 장치 피드백 및 상태 보고 PC(MCU) → 컨트롤러 ****************** .. list-table:: :header-rows: 1 :widths: 6 13 27 28 26 * - 구분 - Mode(hex) - BCA, DCA - SA, SB, SC - SM(구형 SC) * - 1 - 0x01 - 위치.속도 제어 - 위치.속도 제어 - 지원안함 * - 2 - 0x02 - 가감속 위치 제어 - 가감속 위치 제어 - 지원안함 * - 3 - 0x03 - 가감속 속도 제어 - 가감속 속도 제어 - 가감속 속도 제어 * - 4 - 0x04 - 위치제어기 설정 - 위치제어기 설정 - 지원안함 * - 5 - 0x05 - 속도제어기 설정 - 속도제어기 설정 - 속도제어기 설정 * - 6 - 0x06 - ID 설정 - ID 설정 - 외부 감속비 설정 * - 7 - 0x07 - 통신속도 설정 - 통신속도 설정 - 제어 On/Off설정 * - 8 - 0x08 - 통신 응답시간 설정 - 통신 응답시간 설정 - 위치 초기화 * - 9 - 0x09 - 모터 정격속도 설정 - 외부 감속비 설정 - 공장 초기화 * - 10 - 0x0A - 분해능 설정 - 제어 On/Off설정 - 지원안함 * - 11 - 0x0B - 감속비 설정 - 위치제어 모드 설정 - 지원안함 * - 12 - 0x0C - 제어 On/Off 설정 - 위치초기화 - 지원안함 * - 13 - 0x0D - 위치제어 모드 설정 - 공장 초기화 - 지원안함 * - 14 - 0x0E - 제어방향 설정 - Echo 모드 설정 - 지원안함 * - 15 - 0x0F - 위치 초기화 - 지원안함 - 지원안함 * - 16 - 0x10 - 공장 초기화 - 지원안함 - 지원안함 * - 17 - 0x11 - Open-Loop 구동 - Open-Loop 구동 - 지원안함 .. raw:: pdf Spacer 0 16 .. list-table:: :header-rows: 1 :widths: 6 13 27 28 26 * - 구분 - Mode(hex) - BCA, DCA - SA, SB, SC - SM(구형 SC) * - 18 - 0xA0 - Ping 요청 - Ping 요청 - Ping 요청 * - 19 - 0xA1 - 위치 피드백 요청 - 위치 피드백 요청 - 속도 피드백 요청 * - 20 - 0xA2 - 속도 피드백 요청 - 속도 피드백 요청 - 속도제어기 피드백 * - 21 - 0xA3 - 위치제어기 피드백 요청 - 위치제어기 피드백 요청 - 통신 응답시간 피드백 * - 22 - 0xA4 - 속도제어기 피드백 요청 - 속도제어기 피드백 요청 - 외부 감속비 피드백 * - 23 - 0xA5 - 통신 응답시간 피드백 요청 - 통신 응답시간 피드백 요청 - 제어 On/Off 피드백 * - 24 - 0xA6 - 모터 정격속도 피드백 - 외부 감속비 피드백 - 펌웨어 버전 피드백 * - 25 - 0xA7 - 분해능 피드백 - 제어 On/Off 피드백 - 지원안함 * - 26 - 0xA8 - 감속비 피드백 - 위치제어 모드 피드백 - 지원안함 * - 27 - 0xA9 - 제어 On/Off 피드백 - 절대엔코더 피드백 - 지원안함 * - 28 - 0xAA - 위치제어 모드 피드백 - Echo 모드 피드백 - 지원안함 * - 29 - 0xAB - 제어방향 피드백 - 지원안함 - 지원안함 * - 30 - 0xCD - 펌웨어 버전 - 펌웨어 버전 - 펌웨어 버전 컨트롤러 → PC(MCU) ******************** .. list-table:: :header-rows: 1 :widths: 6 13 27 28 26 * - 구분 - Mode(hex) - BCA, DCA - SA, SB, SC - SM(구형 SC) * - 31 - 0xD0 - Ping 응답 - Ping 응답 - Ping 응답 * - 32 - 0xD1 - 위치 피드백 응답 - 위치 피드백 응답 - 속도 피드백 응답 * - 33 - 0xD2 - 속도 피드백 응답 - 속도 피드백 응답 - 속도제어기 피드백 응답 * - 34 - 0xD3 - 위치제어기 피드백 응답 - 위치제어기 피드백 응답 - 통신 응답시간 피드백 응답 * - 35 - 0xD4 - 속도제어기 피드백 응답 - 속도제어기 피드백 응답 - 외부 감속비 피드백 응답 * - 36 - 0xD5 - 통신 응답시간 피드백 응답 - 통신 응답시간 피드백 응답 - 제어 On/Off 피드백 응답 * - 37 - 0xD6 - 모터 정격속도 피드백 응답 - 외부 감속비 피드백 응답 - 펌웨어 버전 피드백 응답 * - 38 - 0xD7 - 분해능 피드백 응답 - 제어 On/Off 피드백 응답 - 지원안함 * - 39 - 0xD8 - 감속비 피드백 응답 - 위치제어 모드 피드백 응답 - 지원안함 * - 40 - 0xD9 - 제어 On/Off 피드백 응답 - 절대엔코더 피드백 응답 - 지원안함 * - 41 - 0xDA - 위치제어 모드 피드백 응답 - Echo 모드 피드백 응답 - 지원안함 * - 42 - 0xDB - 제어방향 피드백 응답 - 지원안함 - 지원안함 * - 43 - 0xFD - 펌웨어 버전 응답 - 펌웨어 버전 응답 - 펌웨어 버전 응답 ※ **설정 명령은 기기별로 모드 구분자가 다르다.** ※ BCA와 DCA는 동일하지만, SA, SB, SC는 설정 명령이 다름. 4.2 제품별 지원 명령 요약 +++++++++++++++++++++++++++ .. list-table:: :header-rows: 1 :widths: 16 24 60 * - 제품 - 지원 범위 - 특이 사항 * - BCA, DCA - 0x01~0x11 - 분해능, 감속비, 제어방향 포함, Open-loop PWM 제어 포함 * - SA, SB, SC - 0x01~0x0E, 0x11 - Open-loop PWM 제어 포함, *Echo 모드 설정* ※ **펌웨어 버전에 따라 일부 지원되지 않는 기능이 존재한다.** .. raw:: pdf PageBreak 5. 제어 명령 (Control Commands) -------------------------------------- .. image:: Pasted_image_20251112172611.png :width: 5in :align: left .. image:: Pasted_image_20251112172651.png :width: 5in :align: left .. image:: Pasted_image_20251112172754.png :width: 5in :align: left .. image:: Pasted_image_20251112172837.png :width: 4.17in :align: left .. rst-class:: clear-both .. raw:: html
.. list-table:: :header-rows: 1 :widths: 8 19 23 23 27 * - Mode - 명령명 - 데이터 구조 - 단위 - 설명 * - 0x01 - 위치·속도제어 - [방향, 위치, 속도] - deg(0.01), RPM(0.1) - 위치 및 속도를 동시에 지정 * - 0x02 - 가감속 위치제어 - [방향, 위치, 도달시간] - sec(0.1) - 도달 시간 기반 위치 이동 * - 0x03 - 가감속 속도제어 - [방향, 속도, 도달시간] - sec(0.1) - 도달 시간 기반 속도 변경 * - 0x11 - Open-loop 구동 - [방향, 듀티비] - 0.01% - PWM 방식의 개방 루프 제어 방향: 1 bytes, 위치: 2 bytes, 속도: 2 bytes, 듀티비: 2 bytes 5.1 PID 게인 튜닝 +++++++++++++++++++ * PID 게인 튜닝은 목표 도달 방식에 따라 결정되어야 한다. * 한번에 목표에 도달하는 것이 목적일 경우 PID 순으로 튜닝하는 것이 유리하다. * 위치 제어 * 목표에 빠르게 도달하는 것이 목적일 경우 PDI 순으로 튜닝하는 것이 유리하다. * 속도 제어 * Kp, Ki, Kd: 1byte 정규화 값(0~255) * 실제 제어식은 제품 내부 알고리즘에 의해 스케일링됨 * PID 튜닝 그래프는 개념 참고용이며 절대 scale은 아님 5.1.1 P I D 순차 튜닝 *********************** * 목표에 부드럽게 도달할 수 있다. P 게인 튜닝 ~~~~~~~~~~~~ .. image:: 모터컨트롤러_20251112-P_게인튜닝_1.png :width: 6in :align: left .. rst-class:: clear-both P+I 게인 튜닝 ~~~~~~~~~~~~~~ .. image:: 모터컨트롤러_20251112-I게인튜닝.png :width: 6in :align: left .. rst-class:: clear-both P+I+D 게인 튜닝 ~~~~~~~~~~~~~~~~ .. image:: 모터컨트롤러_20251112-D게인튜닝_1.png :width: 6in :align: left .. rst-class:: clear-both 5.1.2 P D I 순차 튜닝 ********************** * P 게인이 크면 목표 도달 시간이 짧다. .. image:: Pasted_image_20251113175709.png :width: 6in :align: left .. rst-class:: clear-both 5.2 위치 제어 +++++++++++++++++++ * 위치 제어는 절대 위치제어와 상대 위치제어로 구분된다. * 절대 위치제어 * 0~65533까지 제어가 가능(0도 ~ 655.33 도) * 기준점(0도)을 기준으로 목표 위치로 이동 * 예: 목표값 100도 → 항상 절대 100도 위치로 이동 * 허용 범위보다 크게 이동하기 위해서는 감속비를 설정하여서 우회할 수 있다. * 상대 위치제어 * 현재 위치를 기준으로 지정한 크기만큼 이동 * 예: 현재 300도에서 +80도 이동이면 최종 위치 = 380도 → 360도 Overflow → 20도 위치 * 위치제어 모드는 위치제어 모드 설정을 통해서 변경된다. * 위치 제어는 위치 이동 후 지정 위치를 유지하기 위해서 계속 제어 동작을 한다. * 외력에 의해서 위치가 이동될 경우 지정 위치로 이동한다. * 위치 제어는 위치제어기 설정을 통해서 PID 게인을 설정한다. * PID는 개별 혹은 조합하여 사용이 가능하다. * 위치제어 PID 게인 튜닝은 기구적인 외부 요인의 영향을 받으므로 항상 성능 좋은 게인 값을 찾기는 어렵다. 5.2.1 위치 속도 제어 ********************** .. image:: 모터컨트롤러_20251112-위치속도_그래프.png :width: 3in :align: left .. rst-class:: clear-both * 현재 위치에서 목표 위치까지 지정한 속도로 이동한다. * 방향은 0점으로 부터의 회전 방향이다. * 절대 위치제어 * 고정된 0점을 기준으로 방향(CW/CCW)으로 위치값으로 이동한다. * 상대 위치제어 * 현재 위치에서 지정한 방향(CW/CCW)으로 위치값만큼 이동한다. 5.2.2 가감속 위치제어 ********************** .. image:: 모터컨트롤러_20251112-가감속위치제어.png :width: 3in :align: left .. rst-class:: clear-both * 현재 위치에서 목표 위치까지 지정된 시간동안 이동한다. * 지정된 시간동안 속도는 리니어하게 가속과 감속한다. * 도달 시간을 2초로 설정하면, 가속 1초, 감속 1초로 동작한다. 5.3 속도 제어 ++++++++++++++ * **속도 제어**\는 지정한 속도로 회전하도록 하는 명령이다. * 누리로봇의 제어기는 가감속 속도 제어(accelerated speed control)만 제공한다. * **가감속 속도제어**\는 **모터가 목표 속도에 부드럽게 도달하도록 가속과 감속을 자동으로 제어하는 기능**\이다. 5.3.1 가감속 속도제어기 ************************* .. image:: 모터컨트롤러_20251112-속도_제어_1.png :width: 3in :align: left .. rst-class:: clear-both * 지정된 속도로 지정된 시간 동안 가속한다. * 현재 속도가 지정된 속도보다 크면 지정된 시간동안 감속한다. 5.4 Open-loop 구동 ++++++++++++++++++++ * Closed-Loop의 다른 제어 방식과 달리 Open-Loop 구동은 피드백을 통한 보정이 되지 않는다. * 위치 유지 불가 * 지정된 PWM 듀티 출력을 유지한다. * 부하 증가 시 Stall 위험 * 과전류 발생 가능 * 피드백에 의한 보정이 없기 때문에 상태 유지를 위한 동작이 없다. * 각 모터별 동작하기 위한 최소 입력 전압이 다르다. * 같은 듀티라도 모터별로 동작 유무가 다를 수 있다. .. raw:: pdf PageBreak 6. 설정 명령 (Configuration Commands) -------------------------------------- .. list-table:: :header-rows: 1 :widths: 10 15 25 25 25 * - 구분 - Mode(hex) - BCA, DCA - SA, SB, SC - SM(구형 SC) * - 4 - 0x04 - 위치제어기 설정 - 위치제어기 설정 - 지원안함 * - 5 - 0x05 - 속도제어기 설정 - 속도제어기 설정 - 속도제어기 설정 * - 6 - 0x06 - ID 설정 - ID 설정 - 외부 감속비 설정 * - 7 - 0x07 - 통신속도 설정 - 통신속도 설정 - 제어 On/Off설정 * - 8 - 0x08 - 통신 응답시간 설정 - 통신 응답시간 설정 - 위치 초기화 * - 9 - 0x09 - 모터 정격속도 설정 - 외부 감속비 설정 - 공장 초기화 * - 10 - 0x0A - 분해능 설정 - 제어 On/Off설정 - *지원안함* * - 11 - 0x0B - 감속비 설정 - 위치제어 모드 설정 - *지원안함* * - 12 - 0x0C - 제어 On/Off 설정 - 위치초기화 - *지원안함* * - 13 - 0x0D - 위치제어 모드 설정 - 공장 초기화 - *지원안함* * - 14 - 0x0E - 제어방향 설정 - Echo 모드 설정 - *지원안함* * - 15 - 0x0F - 위치 초기화 - *지원안함* - *지원안함* * - 16 - 0x10 - 공장 초기화 - *지원안함* - *지원안함* .. list-table:: :header-rows: 1 :widths: 40 18 18 24 * - 명령명 - 데이터 구조 - 기본값 - 비고 * - 위치제어기 설정 - [Kp, Ki, Kd, 전류] - 제품별 상이 - 내부 제어기 게인 설정 * - 속도제어기 설정 - [Kp, Ki, Kd, 전류] - 제품별 상이 - * - ID 설정 - [New ID] - 0x00 - 중복 금지 * - 통신속도 설정 - [Baudrate 코드] - 0x06 (9,600bps) - 부록 참고 * - 통신 응답시간 설정 - [시간 코드] - 0x01 - 100µs 단위 * - 감속비 / 외부 감속비 설정 - [ratio(2B)] - 10:1 - 제품별 차이 * - 제어 On/Off, 위치제어 모드, 초기화 - [1B] - 0x00(On) - * - 공장 초기화 - - - 모든 설정 초기화 6.1 설정 명령 공통 유의사항 ++++++++++++++++++++++++++++ .. list-table:: :header-rows: 1 :widths: 20 80 * - 항목 - 설명 * - 설정 명령 처리 시간 - 내부 EEPROM에 기록되므로 **약 50~300ms** 지연이 발생한다. 연속 명령 전송 시 최소 300ms 이상 간격이 필요하다. * - 피드백 확인 필요 - 설정 후 반드시 **피드백 요청**\으로 변경값을 검증해야 한다. * - 전원/통신 안정 상태에서만 수행 - 설정 도중 통신 끊김 또는 전원 차단 시 데이터 손상(EEPROM Error)이 발생할 수 있다. * - 제어 중 설정 변경 금지 - 제어 루프가 활성화된 상태에서 제어기, 분해능, 감속비 등의 설정을 변경하면 오작동 또는 과전류가 발생할 수 있다. 6.2 설정 명령 설명 +++++++++++++++++++ .. image:: Pasted_image_20251113181203.png :width: 6in :align: center .. image:: Pasted_image_20251113181229.png :width: 6in :align: center .. image:: Pasted_image_20251113181257.png :width: 3.8in :align: center .. image:: Pasted_image_20251113181348.png :width: 3.8in :align: center .. image:: Pasted_image_20251113181423.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192148.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192215.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192234.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192304.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192326.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192600.png :width: 3.8in :align: center .. image:: Pasted_image_20251113192616.png :width: 2.9in :align: center .. image:: Pasted_image_20251113192704.png :width: 2.9in :align: center .. image:: Pasted_image_20260507200411.png :width: 3.8in :align: center .. rst-class:: clear-both 6.2.1 위치제어기 설정 ********************** * 기능설명 * 위치 제어기의 PID 게인(Kp, Ki, Kd) 및 정격전류(Max Current)를 설정한다. * 제어기의 응답성, 위치 정밀도, 오버슈트 등에 직접적인 영향을 미친다. * 유의사항 * 잘못된 게인 설정 시 진동 및 발진 발생 가능 * 전류 값은 모터 정격전류 이하로 설정 * 정격전류 이상 설정 시 발열 또는 과전류 보호 작동 * 설정 후 반드시 피드백으로 값 확인 필요 6.2.2 속도제어기 설정 *********************** * 기능설명 * 속도 제어기의 PID 게인과 속도 제어용 정격전류를 설정한다. * 모터의 회전 속도 안정성 및 응답 특성을 조정할 때 사용된다. * 유의사항 * 잘못된 게인 설정 시 진동 및 발진 발생 가능 * 전류 값은 모터 정격전류 이하로 설정 * 정격전류 이상 설정 시 발열 또는 과전류 보호 작동 * 설정 후 반드시 피드백으로 값 확인 필요 6.2.3 ID 설정 ************** * 기능설명 * 모듈의 고유 ID를 변경한다. * 각 RS485 네트워크에서 장치를 구분하는 식별 번호로 사용된다. * 유의사항 * ID는 0x00~0xFE 범위 내에서 중복 금지 * Broadcast ID(0xFF)는 제어용이 아닌 공통 명령용 * Broadcast로 명령 전달 시 동일 ID로 설정된다. * 시스템에서 예외처리하지 않는다. 6.2.4 통신속도 설정 ******************** * 기능설명 * 통신 Baudrate를 설정한다. * 유의사항 * 제어기와 슬레이브의 Baudrate 반드시 일치해야 함 * 설정 직후 재통신 시도 필요 * 잘못된 설정 시 통신 불가 상태 발생 가능 6.2.5 통신 응답시간 설정 ************************* * 기능설명 * 피드백 응답 지연시간(100µs 단위)을 설정한다. * 여러 모듈이 동시에 응답하는 환경에서 충돌을 방지하기 위한 지연을 부여한다. * 유의사항 * 응답시간이 짧으면 다중모듈 환경에서 충돌 가능 * 응답시간이 너무 길면 통신 지연 발생 * ID별로 적절한 간격 설정 필요 * [[모터제어기/누리로봇 제어 프로토콜 매뉴얼#2.3 Broadcast 사용 주의사항|누리로봇 제어 프로토콜 매뉴얼]] 참고 6.2.6 모터 정격속도 설정 ************************** * 기능설명 * 모터의 정격속도(Rated Speed, 단위: RPM)를 설정한다. * 내부 제어기의 기준 속도 한계를 지정한다. * 유의사항 * 실제 모터 사양에 맞게 설정 필요 * 과도한 정격속도 설정 시 보호 동작 또는 손상 위험 * 변경 후 속도 피드백으로 확인 권장 6.2.7 분해능 설정 ****************** * 기능설명 * 엔코더 또는 홀센서의 분해능(해상도)을 설정한다. * Encoder 지원 제품은 엔코더 펄스를 입력한다.(PPR) * CPR(Counts per Revolution)과 PPR(Pulses per Revolution) 차이를 구분하라. * CPR : CPR / 4 입력 * PPR: PPR 입력 * 기본 제품은 홀센서 극수를 입력한다.(Number of Poles) * Pols과 Pole Pair 구분하라. * Pole : 그대로 입력 * Pole Pair : Pole Pair × 2 * 유의사항 * 현재 하드웨어 사양과 일치해야 정확한 제어 가능 * 잘못된 설정 시 위치 제어 오차 또는 속도 이상 발생 * 모터 모델 변경 시 반드시 재설정 6.2.8 감속비 설정 ****************** * 기능설명 * 내부 또는 외부 감속기(Gear Ratio)를 설정한다. * 모터 회전량과 출력축의 각도/속도를 변환하는 비율을 지정한다. * 유의사항 * 감속비 계산식: 출력속도 = 모터속도 ÷ 감속비 * 실제 감속비와 다르면 위치 오차 누적 발생 * 외부감속비 설정은 RSA, SA 제품에서 주로 사용 6.2.9 제어 On/Off 설정 ************************ * 기능설명 * 모터 드라이버의 제어 상태를 켜거나 끈다. (0x00=On, 0x01=Off) * 유의사항 * 제어 Off 시 PWM 출력 및 제어 루프 정지 * On 상태에서 제어기 파라미터 변경 금지 6.2.10 위치제어 모드 설정 ************************** * 기능설명 * 절대 위치제어(0x00) 또는 상대 위치제어(0x01) 모드를 선택한다. * 유의사항 * 절대모드: 지정된 절대 좌표로 이동 * 상대모드: 현재 위치를 기준으로 이동 * 잘못 설정 시 위치 이동 방향 오류 발생 가능 6.2.11 제어방향 설정 ********************** * 기능설명 * 모터의 회전 방향 기준을 설정한다. * 유의사항 * 실제 배선 방향과 불일치 시 동작 반대 발생 * 초기 구동 시 반드시 확인 필요 * 잘못된 방향 설정은 모터 발산의 원인 6.2.12 위치 초기화 ******************* * 기능설명 * 현재 위치를 0도로 초기화한다. * 홈 포지션 지정 시 사용된다. * 유의사항 * 제어 Off 상태에서 수행 권장 * 초기화 후 피드백 기준점 변경 * 홈센서 등 외부 기준 신호와 병행 시 주의 필요 6.2.13 공장 초기화 ******************* * 기능설명 * 모든 설정값을 출하 초기값으로 복원한다. * EEPROM에 저장된 설정이 모두 재설정된다. * 유의사항 * **모든 사용자 설정값 삭제됨** * 실행 전 반드시 백업 필요 (캡쳐 등) * Broadcast(0xFF) ID로 전송 시 모든 모듈 초기화됨 * 실행 후 재부팅 필수 6.2.14 Echo 모드 설정 *********************** * 기능설명 * 송신 명령에 대해서 에코(반송) 설정을 하면 전송된 메시지를 확인해서 통신 무결성을 확보할 수 있음. * 유의사항 * 일부 제품만 제공 * 반송 데이터에 의한 다중 제어 시 충돌 발생할 수 있음. * 다중 모터 제어 시 반송 데이터 수신 시간을 감안하여야 함. .. raw:: pdf PageBreak 7. 피드백 응답 (Feedback Response) -------------------------------------------- .. list-table:: :header-rows: 1 :widths: 6 13 28 27 26 * - 구분 - Mode(hex) - BCA, DCA - SA, SB, SC - SM(구형 SC) * - 31 - 0xD0 - Ping 응답 - Ping 응답 - Ping 응답 * - 32 - 0xD1 - 위치 피드백 응답 - 위치 피드백 응답 - 속도 피드백 응답 * - 33 - 0xD2 - 속도 피드백 응답 - 속도 피드백 응답 - 속도제어기 피드백 응답 * - 34 - 0xD3 - 위치제어기 피드백 응답 - 위치제어기 피드백 응답 - 통신 응답시간 피드백 응답 * - 35 - 0xD4 - 속도제어기 피드백 응답 - 속도제어기 피드백 응답 - 외부 감속비 피드백 응답 * - 36 - 0xD5 - 통신 응답시간 피드백 응답 - 통신 응답시간 피드백 응답 - 제어 On/Off 피드백 응답 * - 37 - 0xD6 - 모터 정격속도 피드백 응답 - 외부 감속비 피드백 응답 - 펌웨어 버전 피드백 응답 * - 38 - 0xD7 - 분해능 피드백 응답 - 제어 On/Off 피드백 응답 - 지원안함 * - 39 - 0xD8 - 감속비 피드백 응답 - 위치제어 모드 피드백 응답 - 지원안함 * - 40 - 0xD9 - 제어 On/Off 피드백 응답 - 절대엔코더 피드백 응답 - 지원안함 * - 41 - 0xDA - 위치제어 모드 피드백 응답 - Echo 모드 피드백 응답 - 지원안함 * - 42 - 0xDB - 제어방향 피드백 응답 - 지원안함 - 지원안함 * - 43 - 0xFD - 펌웨어 버전 응답 - 펌웨어 버전 응답 - 펌웨어 버전 응답 7.1 공통 유의사항 +++++++++++++++++++ .. list-table:: :header-rows: 1 :widths: 25 75 * - 항목 - 설명 * - 요청/응답 구조 - 피드백 응답(0xD0~0xDB)은 반드시 요청 명령(0xA0~0xAB) 이후에만 전송됩니다. * - 응답 주기 - 10ms~50ms 간격 이내에서 요청해야 안정적이며, 폴링 속도가 과도하면 통신 과부하가 발생할 수 있습니다. * - 통신 충돌 방지 - 여러 노드가 동시에 피드백하지 않도록 각 ID의 응답시간을 차등 설정해야 합니다. * - Checksum 검증 - 수신 데이터는 반드시 Checksum을 계산하여 검증해야 합니다. (NOT(Sum of bytes)) * - 피드백 오류 발생 시 - 데이터 깨짐 또는 지연 발생 시 RS485 선로(쉴드, GND, 종단저항)를 확인해야 합니다. * - Broadcast 제한 - Broadcast(0xFF)로 요청하면 모든 노드가 응답하여 충돌이 발생하므로 개별 ID로 요청해야 합니다. Broadcast를 사용하려는 경우에는 통신 응답시간을 설정하여 충돌을 회피해야 합니다. * 위치: 0.01도 단위 * 속도 피드백 상의 위치는 0.1도 단위이다. * 속도: 0.1RPM 단위 * 전류: 100mA 단위 * 방향: 0x00(CCW), 0x01(CW) 7.2.1 Ping 응답 ********************** - 기능설명 - 장치가 통신 가능 상태인지 확인하는 기본 응답이다. - 요청에 대한 회신으로, ID 및 연결 상태를 검증할 때 사용한다. - 유의사항 - 통신 정상 여부 확인용으로 사용 - 응답이 없을 경우 배선, GND, Baudrate 확인 - Broadcast ID(``0xFF``)로 Ping 요청하여 응답이 충돌하면 응답을 못 받을 수 있음 7.2.2 위치 피드백 응답 **************************** - 기능설명 - 현재 **회전 방향**, **위치**, **속도**, **전류** 정보를 반환한다. - 유의사항 - 위치 데이터 단위는 ``0.01°`` - 속도 단위 ``0.1RPM``, 전류 단위 ``100mA`` - 회전 방향 (``0x00`` = CCW, ``0x01`` = CW) 확인 - 연속 요청 시 10ms 이상 간격 유지 7.2.3 속도 피드백 응답 **************************** - 기능설명 - 현재 **속도**\, **위치**\, **전류**\를 피드백한다. - 위치제어 모드가 아닌 상태에서 실시간 속도 모니터링에 사용된다. - 유의사항 - 단위: 속도 ``0.1RPM``, 위치 ``0.1°`` - 제어 Off 상태에서도 응답 가능 - 고속 폴링 시 CPU 부하 증가 주의 7.2.4 위치제어기 피드백 응답 ******************************* - 기능설명 - 위치제어기의 내부 PID 값(Kp, Ki, Kd) 및 정격전류를 반환한다. - 유의사항 - 단위: 1바이트 = 정규화된 제어 게인 값 - 변경 후 값 확인용으로 사용 - 제어기 설정 후 200ms 이상 대기 후 요청 7.2.5 속도제어기 피드백 응답 ******************************* - 기능설명 - 속도제어기의 PID 값(Kp, Ki, Kd)과 정격전류를 반환한다. - 유의사항 - 설정 값이 EEPROM에 저장되어야 반영 - 속도제어 루프의 안정성 점검용 - 실시간 조정 시 발산 주의 7.2.6 통신 응답시간 피드백 응답 *********************************** - 기능설명 - 장치의 통신 응답 지연시간(100µs 단위)을 반환한다. - 유의사항 - 통신지연 보정용 - 여러 장치가 있을 경우 ID별 시간차 부여 확인 - 값이 0이거나 불규칙하면 버스 충돌 가능성 7.2.7 모터 정격속도 피드백 응답 *********************************** - 기능설명 - 설정된 모터 정격속도(RPM 단위)를 반환한다. - 유의사항 - 단위: ``1RPM`` - 실제 모터 스펙과 일치 확인 - 잘못된 설정 시 제어기 계산 오류 발생 - 속도 프로파일에 도달하지 못할 경우 처리되는 동안 다른 처리가 제한될 수 있음 7.2.8 분해능 피드백 응답 *********************************** - 기능설명 - 엔코더/홀센서의 분해능(pulse 또는 pole)을 반환한다. - 유의사항 - 단위: pulse 또는 pole 수 - 실제 하드웨어와 불일치 시 위치제어 불안정 - 모터 교체 시 반드시 확인 필요 7.2.9 감속비 피드백 응답 *********************************** - 기능설명 - 설정된 감속비(0.1 단위)를 반환한다. - 유의사항 - 단위: ``0.1 ratio`` - 예: ``0x0064`` = 10:1 감속비 - 외부 감속기 존재 시 반드시 설정 확인 7.2.10 제어 On/Off 피드백 응답 *********************************** - 기능설명 - 현재 제어기 On/Off 상태를 반환한다 (``0x00`` = On, ``0x01`` = Off). - 유의사항 - 제어 Off 상태에서는 제어 명령이 무시됨 - On/Off 전환 후 100ms 대기 필요 - EEPROM 기록 중에는 On 금지 7.2.11 위치제어 모드 피드백 응답 *********************************** - 기능설명 - 현재 위치제어 모드가 **절대(0x00)** 인지 **상대(0x01)** 인지를 반환한다. - 유의사항 - 절대모드: 고정 기준좌표 이동 - 상대모드: 현재 위치 기준 이동 - 응용프로그램에서 모드 일치 여부 반드시 확인 7.2.12 제어방향 피드백 응답 *********************************** - 기능설명 - 모터의 제어 기준 방향을 반환한다 (``0x00`` = CCW, ``0x01`` = CW). - 유의사항 - 제어방향 설정과 피드백 일치 여부 확인 - 잘못된 방향 설정 시 위치/속도 반전 - 배선 변경 시 반드시 확인 7.2.13 펌웨어 버전 응답 *********************************** - 기능설명 - 제어기의 펌웨어 버전 정보를 반환한다. - 유의사항 - 버전별 기능 차이 가능 - 펌웨어 업데이트 전/후 비교용 - 버전 불일치 시 명령어 호환성 주의 7.2.14 절대엔코더 피드백 응답 *********************************** - 기능설명 - 절대엔코더가 추가된 제품의 경우 엔코더를 이용하여 모터의 절대 위치를 반환한다. - 유의사항 - 절대엔코더가 포함된 제품에서만 제공 - 엔코더 옵션이 ``A`` 인 제품에서만 지원 7.2.15 Echo 모드 피드백 응답 *********************************** - 기능설명 - Echo(반송) 설정값을 반환한다. - 유의사항 - ``0x00`` : Echo 사용 안 함 - ``0x01`` : Echo 사용 .. raw:: pdf PageBreak 8. 통신 처리 로직 (Communication Logic) ------------------------------------------------ 8.1 송신 구조 ++++++++++++++++++++++++ - 제어기 → 모듈 방향으로 데이터 전송 - 파라미터를 구성하여 패킷 전송 - Checksum 계산 및 Header 포함 필수 8.2 수신 파싱 알고리즘 ++++++++++++++++++++++++++++++++ #. Header (``0xFFFE``) 탐색 #. Data Size 기준으로 프레임 완성 #. Checksum 검증 #. Mode 값 해석 후 명령별 데이터 파싱 8.3 다중 데이터 처리 시 주의사항 ++++++++++++++++++++++++++++++++++++++++ - 수신 중 새로운 Header가 검출될 경우 **이전 패킷 폐기** - Broadcast 명령은 응답하지 않음 - 설정 명령 수행 후 피드백 요청 시 **충분한 지연 시간 확보 필요** .. raw:: pdf PageBreak 9. 예제 (Examples) ------------------------------------------------ 9.1 Python 시리얼 제어 예제 +++++++++++++++++++++++++++++ 예제 소스는 아래 저장소에서 확인할 수 있다. - https://github.com/ckdo8008/nurirobotPython/blob/main/testcode.py .. raw:: pdf Spacer 0 16 .. code-block:: python #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Nurirobot RS-485 control demo for MC/SA series - Read current settings via feedback requests, then re-apply the same settings via "set" commands - Run: Pos&Speed (0x01), AccPos (0x02), AccSpeed (0x03), and (if SA) Open-Loop (0x11) for 1s each, then stop Spec refs: * MC-RS485 V1.0.1: control (0x01~0x03), setters (0x04~0x0F/0x10), feedback (0xA0.. -> 0xD0..FD) * SA-RS485 V1.0.2: Open-Loop drive (0x11) """ import argparse import time from dataclasses import dataclass from typing import List, Dict, Optional import serial try: from serial.rs485 import RS485Settings except Exception: RS485Settings = None 프로토콜 상수와 프레임 파서: .. code-block:: python # ----------------------------- # Common protocol constants # ----------------------------- HEADER = bytes((0xFF, 0xFE)) # Mode values (MC shared) MODE_POS_SPEED = 0x01 # 위치, 속도제어 (direction[1], pos[2](0.01°), spd[2](0.1RPM)) MODE_ACC_POS = 0x02 # 가감속 위치제어 (direction[1], pos[2], arrive[1](0.1s)) MODE_ACC_SPEED = 0x03 # 가감속 속도제어 (direction[1], spd[2], arrive[1](0.1s)) MODE_SET_POS_CTRL = 0x04 # 위치제어기 설정 (Kp,Ki,Kd, rated[0.1A]) MODE_SET_SPD_CTRL = 0x05 # 속도제어기 설정 (Kp,Ki,Kd, rated[0.1A]) MODE_SET_ID = 0x06 # ID 설정 (주의: 실장비 변경 위험) MODE_SET_BAUD = 0x07 # 통신속도 설정 (주의: 즉시 변경) MODE_SET_RESPTIME = 0x08 # 통신 응답시간 설정 (value[100us]) MODE_SET_RATED_SPD = 0x09 # 모터 정격속도 설정 (RPM, 2B) MODE_SET_RESOLUTION = 0x0A # 분해능 설정 (2B) MODE_SET_RATIO = 0x0B # 감속비 설정 (0.1ratio, 2B) MODE_SET_ONOFF = 0x0C # 제어 On/Off (0x00 On, 0x01 Off) MODE_SET_POS_MODE = 0x0D # 위치제어 모드 (0x00 절대, 0x01 상대) MODE_SET_CTRL_DIR = 0x0E # 제어 방향 (0x00 CCW, 0x01 CW) MODE_RESET_POS = 0x0F # 위치 초기화 MODE_RESET_FACTORY = 0x10 # 공장 초기화 (주의) # SA series only MODE_OPEN_LOOP = 0x11 # Open-Loop 구동 (direction[1], duty[2] in 0.01%) # Feedback requests (송신) REQ = { "ping": 0xA0, "pos": 0xA1, "spd": 0xA2, "pos_ctrl": 0xA3, "spd_ctrl": 0xA4, "resp_time": 0xA5, "rated_speed": 0xA6, "resolution": 0xA7, "ratio": 0xA8, "ctrl_onoff": 0xA9, "pos_ctrl_mode": 0xAA, "ctrl_dir": 0xAB, "firmware": 0xCD, } # Feedback responses (수신) FEED = { "ping": 0xD0, "pos": 0xD1, "spd": 0xD2, "pos_ctrl": 0xD3, "spd_ctrl": 0xD4, "resp_time": 0xD5, "rated_speed": 0xD6, "resolution": 0xD7, "ratio": 0xD8, "ctrl_onoff": 0xD9, "pos_ctrl_mode": 0xDA, "ctrl_dir": 0xDB, "firmware": 0xFD, } # ----------------------------- # Bytes / checksum helpers # ----------------------------- def be16(msb: int, lsb: int) -> int: return ((msb & 0xFF) << 8) | (lsb & 0xFF) def to_be16(v: int) -> bytes: return bytes(((v >> 8) & 0xFF, v & 0xFF)) def calc_checksum(frame_with_placeholder: bytes) -> int: # ~(sum of all bytes except header[0:2] and checksum[4]) & 0xFF s = 0 for i, b in enumerate(frame_with_placeholder): if i in (0, 1, 4): continue s = (s + (b & 0xFF)) & 0xFF return (~s) & 0xFF def build_frame(dev_id: int, mode: int, data: bytes = b'') -> bytes: # Frame: FF FE | ID | LEN | CHK | MODE | DATA... # LEN includes CHK + MODE + DATA size = 1 + 1 + len(data) buf = bytearray(6 + len(data)) buf[0:2] = HEADER buf[2] = dev_id & 0xFF buf[3] = size & 0xFF buf[5] = mode & 0xFF if data: buf[6:] = data buf[4] = calc_checksum(buf) return bytes(buf) # ----------------------------- # Stream parser (robust) # ----------------------------- class StreamParser: def __init__(self): self.buf = bytearray() def feed(self, chunk: bytes) -> List[bytes]: frames: List[bytes] = [] if not chunk: return frames self.buf.extend(chunk) while True: start = self.buf.find(HEADER) if start < 0: self.buf.clear() break if start > 0: del self.buf[:start] if len(self.buf) < 6: break length = self.buf[3] total = length + 4 if total < 6: del self.buf[:2] continue if len(self.buf) < total: nxt = self.buf.find(HEADER, 2) if 0 <= nxt < len(self.buf): del self.buf[:nxt] continue break frame = bytes(self.buf[:total]) try: print("Recv Frame:", ' '.join(f"{b:02X}" for b in frame)) except Exception: # 출력 도중 문제가 생겨도 파싱에는 영향이 없도록 무시합니다. pass if calc_checksum(frame) != frame[4]: del self.buf[:2] continue frames.append(frame) del self.buf[:total] return frames 시리얼 링크와 피드백 파싱: .. code-block:: python # ----------------------------- # Serial link # ----------------------------- @dataclass class LinkConfig: port: str baud: int = 9600 timeout: float = 0.2 rs485_rts_enable: bool = False class Link: def __init__(self, cfg: LinkConfig): self.cfg = cfg self.ser: Optional[serial.Serial] = None self.parser = StreamParser() def open(self): self.ser = serial.Serial() self.ser.port = self.cfg.port self.ser.baudrate = self.cfg.baud self.ser.bytesize = serial.EIGHTBITS self.ser.parity = serial.PARITY_NONE self.ser.stopbits = serial.STOPBITS_ONE self.ser.timeout = self.cfg.timeout self.ser.xonxoff = False self.ser.rtscts = False self.ser.dsrdtr = False if self.cfg.rs485_rts_enable and RS485Settings is not None: self.ser.rs485_mode = RS485Settings( rts_level_for_tx=True, rts_level_for_rx=False, delay_before_tx=0.0, delay_before_rx=0.0 ) self.ser.open() def close(self): if self.ser and self.ser.is_open: self.ser.close() def write(self, frame: bytes): self.ser.write(frame) self.ser.flush() try: print("Send Frame:", ' '.join(f"{b:02X}" for b in frame)) except Exception: # 출력 도중 문제가 생겨도 파싱에는 영향이 없도록 무시합니다. pass def read_frames(self) -> List[bytes]: chunk = self.ser.read(1024) return self.parser.feed(chunk) if chunk else [] 피드백 요청 대기: .. code-block:: python # ----------------------------- # Feedback: read current settings (MC spec) # ----------------------------- def request_and_wait(link: Link, dev_id: int, req_mode: int, expect_feed_mode: int, wait_s=0.35): frm = build_frame(dev_id, req_mode, b'') link.write(frm) end = time.monotonic() + wait_s while time.monotonic() < end: for f in link.read_frames(): if len(f) >= 6 and f[2] == (dev_id & 0xFF) and f[5] == (expect_feed_mode & 0xFF): return f time.sleep(0.005) return None 피드백 응답 파싱: .. code-block:: python def parse_feed_pos_ctrl(f: bytes) -> Dict: d = f[6:] return {"kp": d[0], "ki": d[1], "kd": d[2], "rated_current_a": d[3] * 0.1} def parse_feed_spd_ctrl(f: bytes) -> Dict: d = f[6:] return {"kp": d[0], "ki": d[1], "kd": d[2], "rated_current_a": d[3] * 0.1} 현재 설정 읽기: .. code-block:: python def parse_feed_settings(link: Link, dev_id: int) -> Dict: """ 읽을 수 있는 현재 설정/상태를 모두 피드백 요청으로 수집. (D3/D4/D5/D6/D7/D8/D9/DA/DB/FD 등) """ out: Dict = {} q = [ ("pos_ctrl", REQ["pos_ctrl"], FEED["pos_ctrl"], parse_feed_pos_ctrl), ("spd_ctrl", REQ["spd_ctrl"], FEED["spd_ctrl"], parse_feed_spd_ctrl), ("resp_time", REQ["resp_time"], FEED["resp_time"], lambda f: {"resptime_100us": f[6]}), ("rated_speed", REQ["rated_speed"], FEED["rated_speed"], lambda f: {"rated_rpm": be16(f[6], f[7])}), ("resolution", REQ["resolution"], FEED["resolution"], lambda f: {"resolution": be16(f[6], f[7])}), ("ratio", REQ["ratio"], FEED["ratio"], lambda f: {"ratio": be16(f[6], f[7]) / 10.0}), ("ctrl_onoff", REQ["ctrl_onoff"], FEED["ctrl_onoff"], lambda f: {"on": (f[6] == 0x00)}), ("pos_ctrl_mode", REQ["pos_ctrl_mode"], FEED["pos_ctrl_mode"], lambda f: {"pos_mode": "absolute" if f[6]==0x00 else "relative"}), ("ctrl_dir", REQ["ctrl_dir"], FEED["ctrl_dir"], lambda f: {"ctrl_dir": "CCW" if f[6]==0x00 else "CW"}), ("firmware", REQ["firmware"], FEED["firmware"], lambda f: {"fw_version": f[6]}), ] for key, req_m, feed_m, parser in q: fr = request_and_wait(link, dev_id, req_m, feed_m) if fr: out[key] = parser(fr) else: out[key] = None return out 설정 및 제어 명령: .. code-block:: python # ----------------------------- # Setters (MC spec + SA Open-Loop) # ----------------------------- def set_pos_ctrl(link: Link, dev_id: int, kp: int, ki: int, kd: int, rated_current_a: float): data = bytes((kp & 0xFF, ki & 0xFF, kd & 0xFF, int(round(rated_current_a * 10)) & 0xFF)) link.write(build_frame(dev_id, MODE_SET_POS_CTRL, data)) def set_spd_ctrl(link: Link, dev_id: int, kp: int, ki: int, kd: int, rated_current_a: float): data = bytes((kp & 0xFF, ki & 0xFF, kd & 0xFF, int(round(rated_current_a * 10)) & 0xFF)) link.write(build_frame(dev_id, MODE_SET_SPD_CTRL, data)) def set_resp_time(link: Link, dev_id: int, hundred_us: int): link.write(build_frame(dev_id, MODE_SET_RESPTIME, bytes((hundred_us & 0xFF,)))) def set_rated_speed(link: Link, dev_id: int, rpm: int): link.write(build_frame(dev_id, MODE_SET_RATED_SPD, to_be16(max(1, min(65533, int(rpm)))))) def set_resolution(link: Link, dev_id: int, res: int): link.write(build_frame(dev_id, MODE_SET_RESOLUTION, to_be16(max(1, min(65533, int(res)))))) def set_ratio(link: Link, dev_id: int, ratio: float): raw = max(1, min(65533, int(round(ratio * 10.0)))) link.write(build_frame(dev_id, MODE_SET_RATIO, to_be16(raw))) def set_onoff(link: Link, dev_id: int, on: bool): link.write(build_frame(dev_id, MODE_SET_ONOFF, bytes((0x00 if on else 0x01,)))) def set_pos_mode(link: Link, dev_id: int, absolute: bool): link.write(build_frame(dev_id, MODE_SET_POS_MODE, bytes((0x00 if absolute else 0x01,)))) def set_ctrl_dir(link: Link, dev_id: int, cw: bool): link.write(build_frame(dev_id, MODE_SET_CTRL_DIR, bytes((0x01 if cw else 0x00,)))) def reset_pos(link: Link, dev_id: int): link.write(build_frame(dev_id, MODE_RESET_POS, b'')) # ----------------------------- # Controllers (run then stop) # ----------------------------- def cmd_pos_speed(link: Link, dev_id: int, direction_cw: bool, pos_deg: float, spd_rpm: float): pos_raw = max(0, min(65533, int(round(pos_deg * 100.0)))) # 0.01° spd_raw = max(0, min(65533, int(round(spd_rpm * 10.0)))) # 0.1RPM data = bytes((0x01 if direction_cw else 0x00,)) + to_be16(pos_raw) + to_be16(spd_raw) link.write(build_frame(dev_id, MODE_POS_SPEED, data)) def cmd_acc_pos(link: Link, dev_id: int, direction_cw: bool, pos_deg: float, arrive_s: float): pos_raw = max(0, min(65533, int(round(pos_deg * 100.0)))) # 0.01° arr_raw = max(1, min(255, int(round(arrive_s * 10.0)))) # 0.1s data = bytes((0x01 if direction_cw else 0x00,)) + to_be16(pos_raw) + bytes((arr_raw,)) link.write(build_frame(dev_id, MODE_ACC_POS, data)) def cmd_acc_speed(link: Link, dev_id: int, direction_cw: bool, spd_rpm: float, arrive_s: float): spd_raw = max(0, min(65533, int(round(spd_rpm * 10.0)))) # 0.1RPM arr_raw = max(1, min(255, int(round(arrive_s * 10.0)))) # 0.1s data = bytes((0x01 if direction_cw else 0x00,)) + to_be16(spd_raw) + bytes((arr_raw,)) link.write(build_frame(dev_id, MODE_ACC_SPEED, data)) def cmd_open_loop(link: Link, dev_id: int, direction_cw: bool, duty_percent: float): # SA 전용: duty in 0.01% (0..100.00%) duty_raw = max(0, min(10000, int(round(duty_percent * 100.0)))) data = bytes((0x01 if direction_cw else 0x00,)) + to_be16(duty_raw) link.write(build_frame(dev_id, MODE_OPEN_LOOP, data)) def safe_stop(link: Link, dev_id: int): # 위치제어, 가감속 위치제어에서 사용하면 위치가 초기화됨 # 스톨 등의 위험상황에서 사용하는 코드 # 속도를 0RPM으로 짧게 감속 후 제어 Off cmd_acc_speed(link, dev_id, direction_cw=False, spd_rpm=0.0, arrive_s=0.2) time.sleep(0.25) set_onoff(link, dev_id, on=False) # 0x01 = Off time.sleep(0.05) 데모 실행 흐름: .. code-block:: python # ----------------------------- # Demo flow # ----------------------------- def demo(series: str, port: str, dev_id: int, baud: int, rs485_rts_enable: bool): link = Link(LinkConfig(port=port, baud=baud, rs485_rts_enable=rs485_rts_enable)) link.open() try: print(f"[OPEN] {port} @{baud}bps, id=0x{dev_id:02X}, series={series}") # 1) 현재 설정값 읽기 (feedback) cur = parse_feed_settings(link, dev_id) print("\n[CURRENT SETTINGS]") for k, v in cur.items(): print(f"- {k}: {v}") # 2) 읽은 설정을 그대로 설정(송신)으로 반영 (안전 데모) if cur.get("pos_ctrl"): set_pos_ctrl(link, dev_id, **cur["pos_ctrl"]) if cur.get("spd_ctrl"): set_spd_ctrl(link, dev_id, **cur["spd_ctrl"]) if cur.get("resp_time"): set_resp_time(link, dev_id, cur["resp_time"]["resptime_100us"]) if cur.get("rated_speed"): set_rated_speed(link, dev_id, cur["rated_speed"]["rated_rpm"]) if cur.get("resolution"): set_resolution(link, dev_id, cur["resolution"]["resolution"]) if cur.get("ratio"): set_ratio(link, dev_id, cur["ratio"]["ratio"]) if cur.get("pos_ctrl_mode"): set_pos_mode(link, dev_id, cur["pos_ctrl_mode"]["pos_mode"] == "absolute") if cur.get("ctrl_dir"): set_ctrl_dir(link, dev_id, cw=(cur["ctrl_dir"]["ctrl_dir"] == "CW")) if cur.get("ctrl_onoff"): set_onoff(link, dev_id, on=cur["ctrl_onoff"]["on"]) time.sleep(0.05) # 3) 제어 데모: 각 1초 구동 후 정지 print("\n[DEMO] 제어 On") set_onoff(link, dev_id, on=True) # 제어 On (0x00) time.sleep(0.05) # (a) 위치, 속도제어 1s print("[RUN] 위치, 속도제어 (0x01) -> 1s") # 예: CCW로 180.00도, 5.0RPM cmd_pos_speed(link, dev_id, direction_cw=False, pos_deg=180.00, spd_rpm=5.0) # MC 스펙: dir/pos(0.01°)/spd(0.1RPM) :contentReference[oaicite:3]{index=3} time.sleep(1.0) safe_stop(link, dev_id) print("[STOP] done\n") time.sleep(0.2) set_onoff(link, dev_id, on=True); time.sleep(0.05) # (b) 가감속 위치제어 1s print("[RUN] 가감속 위치제어 (0x02) -> 1s") # 예: CW로 360.00도, 도달시간 1.0s cmd_acc_pos(link, dev_id, direction_cw=True, pos_deg=360.00, arrive_s=1.0) # MC 스펙: dir/pos/arrive(0.1s) :contentReference[oaicite:4]{index=4} time.sleep(1.0) safe_stop(link, dev_id) print("[STOP] done\n") time.sleep(0.2) set_onoff(link, dev_id, on=True); time.sleep(0.05) # (c) 가감속 속도제어 1s print("[RUN] 가감속 속도제어 (0x03) -> 1s") # 예: CCW로 100RPM, 도달시간 0.5s cmd_acc_speed(link, dev_id, direction_cw=False, spd_rpm=100.0, arrive_s=0.5) # MC 스펙: dir/spd/arrive(0.1s) :contentReference[oaicite:5]{index=5} time.sleep(1.0) safe_stop(link, dev_id) print("[STOP] done\n") set_onoff(link, dev_id, on=True); time.sleep(0.05) print("[RUN] Open-Loop (0x11) -> 1s") # 예: CCW 50% duty cmd_open_loop(link, dev_id, direction_cw=False, duty_percent=50.0) # SA 스펙: dir/duty(0.01%) :contentReference[oaicite:6]{index=6} time.sleep(1.0) safe_stop(link, dev_id) print("[STOP] done\n") print("[DEMO] 완료") finally: link.close() 진입점: .. code-block:: python def main(): ap = argparse.ArgumentParser(description="Nurirobot RS-485 MC/SA control demo") ap.add_argument("--series", choices=["MC", "SA"], default="MC", help="제품 시리즈 (MC 기본, SA는 Open-Loop 지원)") ap.add_argument("--port", default="COM10",help="시리얼 포트 (예: /dev/ttyUSB0, COM5)") ap.add_argument("--id", type=lambda x: int(x, 0), default=0x00, help="장치 ID (기본 0x00)") ap.add_argument("--baud", type=int, default=115200, help="Baudrate (기본 9600)") ap.add_argument("--rts485", action="store_true", help="어댑터가 RTS 기반 DE/RE 전환 필요 시 사용") args = ap.parse_args() demo(args.series, args.port, args.id, args.baud, args.rts485) if __name__ == "__main__": main() 9.2 C 시리얼 제어 예제 ++++++++++++++++++++++++++ 예제 소스는 아래 저장소에서 확인할 수 있다. - https://github.com/ckdo8008/nurirobotPython/blob/main/test.c .. raw:: pdf Spacer 0 16 .. code-block:: cpp #include