Arduino Shield를 이용하여 Dynamixel을 구동했을 때 생기는 Debugging Error에 대해서 알아보고, Dynamixel SDK를 이용하여 ROS 2 AX-12A Control 패키지를 만들어 보려고 한다.
이전 글 참고([Humanoid] Dynamixel AX-12A 동시 구동)
Dynamixel Shield를 Arduino Uno 혹은 Mega에서 사용할 경우, SoftwareSerial 라이브러리를 사용한다.
일반 입출력 데이터 핀을 RX, TX 핀으로 동작할 수 있게 해주는 라이브러리로 이를 통해 RX, TX로 작동하게 된 핀을 소프트웨어 시리얼이라고 한다.
그와 반대로 일반적으로 지정되어 있는 예를 들어 Arduino Uno의 0,1번 핀과 같이 본래 RX, TX 핀을 Hardware Serial이라고 한다.
9600bps에서는 안정저긍로 동작하지만, 그 이상의 속도에서는 데이터 누락, 손실 등의 문제 발생위의 단점 중 속도 제한으로 인한 데이터 누락으로 인해 앞선 Arduino Dynamixel Shield의 Debugging 오류가 생겼던 것이다.
따라서 해당 문제를 해결하기 위해서는 Controller 교체 혹은 PC로 직접 제어 중에 한 가지 방법을 사용해야 한다.
Arduino Shield Debugging Error에 대한 회의를 끝낸 결과, PC로 직접 Dynamixel을 제어하는 방법을 사용하기로 했다.
그에 따라 기존의 ROS 2 Dynamixel SDK를 개선하는 작업을 진행하려고 한다.
기존의 Dynamixel SDK는 통신에 대한 API로 이루어져 있다.
그래서 일반적인 사용자들이 해당 코드를 접하게 될 경우, 코드를 이해하는데 어려움을 겪을 수 있다.
그에 따라, 통신으로 이루어진 API를 직관적인 API를 가지고 있는 HumanoidSDK라는 Class를 만드는 방향으로 개선하려고 한다.
DynamixelSetup : 특정 ID의 Dynamixel 한 개를 SetupDynamixelSetup(id)id : Dynamixel ID 값MultiDynamixelSetup : 여러 개의 Dynamixel를 순차적으로 SetupMultiDynamixelSetup(ids)ids : 여러 개의 Dynamixel ID 값을 담고 있는 ListDynamixelClose : 특정 ID의 Dynamixel 한 개를 CloseDynamixelClose(id)id : Dynamixel ID 값MultiDynamixelClose : 여러 개의 Dynamixel를 순차적으로 CloseMultiDynamixelClose(ids)ids : 여러 개의 Dynamixel ID 값을 담고 있는 ListDynamixelWriteDynamixelWrite(id, position)id : Dynamixel ID 값position : Dynamixel 목표 위치 Tick 값MultiDynamixelWriteMultiDynamixelWrite(ids, positions)ids : 여러 개의 Dynamixel ID 값을 담고 있는 Listpositions: 여러 개의 Dynamixel 목표 위치 Tick 값을 담고 있는 ListDynamixelReadDynamixelRead(id)id : Dynamixel ID 값MultiDynamixelReadMultiDynamixelRead(ids)ids : 여러 개의 Dynamixel ID 값을 담고 있는 Listimport os
if os.name == 'nt':
import msvcrt
def getch():
return msvcrt.getch().decode()
else:
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
def getch():
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
from dynamixel_sdk import *
# Control table address AX-12A
ADDR_AX_TORQUE_ENABLE = 24
ADDR_AX_GOAL_POSITION = 30
ADDR_AX_PRESENT_POSITION = 36
# Protocol version
PROTOCOL_VERSION = 1.0 # See which protocol version is used in the Dynamixel
# Default setting
BAUDRATE = 1000000 # Dynamixel default baudrate : 57600
DEVICENAME = '/dev/ttyUSB0' # Check which port is being used on your controller
TORQUE_DISABLE = 0 # Value for disabling the torque
TORQUE_ENABLE = 1 # Value for enabling the torque
DXL_MINIMUM_POSITION_VALUE = 0 # Dynamixel will rotate between this value
DXL_MAXIMUM_POSITION_VALUE = 1023 # and this value (note that the Dynamixel would not move when the position value is out of movable range. Check e-manual about the range of the Dynamixel you use.)
DXL_MOVING_STATUS_THRESHOLD = 20 # Dynamixel moving status threshold
PortHandler = PortHandler(DEVICENAME)
PacketHandler = PacketHandler(PROTOCOL_VERSION)
index = 0
dxl_goal_position = [[512, 512], [0, 0]] # Goal position
# Open port
if PortHandler.openPort():
print("Succeeded to open the port")
else:
print("Failed to open the port")
print("Press any key to terminate...")
getch()
quit()
# Set port baudrate
if PortHandler.setBaudRate(BAUDRATE):
print("Succeeded to change the baudrate")
else:
print("Failed to change the baudrate")
print("Press any key to terminate...")
getch()
quit()
def DynamixelSetup(id):
dxl_comm_result, dxl_error = PacketHandler.write1ByteTxRx(PortHandler, id, ADDR_AX_TORQUE_ENABLE, TORQUE_ENABLE)
if dxl_comm_result != COMM_SUCCESS:
print("%s" % PacketHandler.getTxRxResult(dxl_comm_result))
elif dxl_error != 0:
print("%s" % PacketHandler.getRxPacketError(dxl_error))
else:
print("Dynamixel has been successfully connected")
def MultiDynamixelSetup(ids):
for id in ids:
DynamixelSetup(id)
def DynamixelClose(id):
dxl_comm_result, dxl_error = PacketHandler.write1ByteTxRx(PortHandler, id, ADDR_AX_TORQUE_ENABLE, TORQUE_DISABLE)
if dxl_comm_result != COMM_SUCCESS:
print("%s" % PacketHandler.getTxRxResult(dxl_comm_result))
elif dxl_error != 0:
print("%s" % PacketHandler.getRxPacketError(dxl_error))
else:
print("Dynamixel has been successfully disconnected")
def MultiDynamixelClose(ids):
for id in ids:
DynamixelClose(id)
def DynamixelRead(id):
dxl_present_position, dxl_comm_result, dxl_error = PacketHandler.read2ByteTxRx(PortHandler, id, ADDR_AX_PRESENT_POSITION)
if dxl_comm_result != COMM_SUCCESS:
print("%s" % PacketHandler.getTxRxResult(dxl_comm_result))
elif dxl_error != 0:
print("%s" % PacketHandler.getRxPacketError(dxl_error))
else:
print("Dynamixel present position is: %d" % dxl_present_position)
def MultiDynamixelRead(ids):
for id in ids:
DynamixelRead(id)
def DynamixelWrite(id, position):
dxl_comm_result, dxl_error = PacketHandler.write2ByteTxRx(PortHandler, id, ADDR_AX_GOAL_POSITION, position)
if dxl_comm_result != COMM_SUCCESS:
print("%s" % PacketHandler.getTxRxResult(dxl_comm_result))
elif dxl_error != 0:
print("%s" % PacketHandler.getRxPacketError(dxl_error))
else:
print("Dynamixel has been successfully connected")
def MultiDynamixelWrite(ids, positions):
for id, position in zip(ids, positions):
DynamixelWrite(id, position)
# Enable Dynamixel Torque for all motors
MultiDynamixelSetup([11, 17])
# Read present position for all motors
MultiDynamixelRead([11, 17])
while 1:
print("Press any key to continue! (or press ESC to quit!)")
if getch() == chr(0x1b):
break
# Write goal position for all motors
MultiDynamixelWrite([11, 17], dxl_goal_position[index])
while 1:
# Read present position for all motors
time.sleep(1)
MultiDynamixelRead([11, 17])
break
if index == 0:
index = 1
else:
index = 0
# Disable Dynamixel Torque for all motors
MultiDynamixelClose([11, 17])
# Close port
PortHandler.closePort()
Write API 개선 필요 : position값까지 이동했는지 확인하면서 해당 값까지 이동했을 때, 멈추게 개선
Angle 제어 : 현재는 Motor의 Tick 값(Encoder 값)을 이용해서 위치 제어를 진행하지만, Angle 값을 통해서 제어할 수 있게 개선 필요
ROS 2 변환 필요 : ROS 2에서 사용할 수 있게 패키지 변환 필요
SoftwareSerial Library : https://docs.arduino.cc/learn/built-in-libraries/software-serial/#softwareserial
[아두이노 중급] 13. 소프트웨어 시리얼(SoftwareSerial) : https://blog.naver.com/darknisia/220808977305