Control with Python
We describe the VESC control method using python.
1. Using CAN Custom Control Message
VESC์์ ์ฌ์ฉํ๋ CAN ํต์ ๋ฉ์์ง์ค, CAN FORWARD ๊ธฐ๋ฅ์ ์ด์ฉ๋๋ CAN PACKET ID๊ฐ ์๋ค. ์ด๋ ์๋ 'CAN_PACKET_ID' ์ค, 5์ ํด๋นํ๋ 'CAN_PACKET_FILL_RX_BUFFER' ์ 7์ ํด๋นํ๋ 'CAN_PACKET_PROCESS_RX_BUFFER' ๋ฅผ ์ด์ฉํ๋ฉด ๋ชฉ VESC์ ์์์ ๋ช ๋ น์ด๋ฅผ ์ ์กํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค. ์ด๋ ๊ธฐ์กด์ ์ ๋ฅ, Duty, ์์น, ์๋ ๋ฑ์ ์์น๋ช ๋ น์ ์๋ก ๋ค๋ฅธ CAN Message๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์์์, ํน์ VESC ID์ ์์์ ๋ช ๋ น์ ํ๋์ ํต์ผ๋ CAN Message ํํ๋ก ๋ณด๋ผ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค. ๋จ, ๊ธฐ์กด ๋ฐฉ๋ฒ์ ๋นํด Message์ ๊ธธ์ด๊ฐ ๋ค์ ๋์ด๋๋ ๋จ์ ์ ์๋ค. ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ ์ด๋ฌํ ๋ฐฉ์์ CAN Custom Control Message ์ ์ก ๋ฐฉ์์ผ๋ก ๋ถ๋ฅด๋๋ก ํ๋ค.
// CAN commands
typedef enum {
CAN_PACKET_SET_DUTY = 0,
CAN_PACKET_SET_CURRENT,
CAN_PACKET_SET_CURRENT_BRAKE,
CAN_PACKET_SET_RPM,
CAN_PACKET_SET_POS,
CAN_PACKET_FILL_RX_BUFFER,
CAN_PACKET_FILL_RX_BUFFER_LONG,
CAN_PACKET_PROCESS_RX_BUFFER,
CAN_PACKET_PROCESS_SHORT_BUFFER,
CAN_PACKET_STATUS,
CAN_PACKET_SET_CURRENT_REL,
CAN_PACKET_SET_CURRENT_BRAKE_REL,
CAN_PACKET_SET_CURRENT_HANDBRAKE,
CAN_PACKET_SET_CURRENT_HANDBRAKE_REL,
CAN_PACKET_STATUS_2,
CAN_PACKET_STATUS_3,
CAN_PACKET_STATUS_4,
CAN_PACKET_PING,
CAN_PACKET_PONG,
CAN_PACKET_DETECT_APPLY_ALL_FOC,
CAN_PACKET_DETECT_APPLY_ALL_FOC_RES,
CAN_PACKET_CONF_CURRENT_LIMITS,
CAN_PACKET_CONF_STORE_CURRENT_LIMITS,
CAN_PACKET_CONF_CURRENT_LIMITS_IN,
CAN_PACKET_CONF_STORE_CURRENT_LIMITS_IN,
CAN_PACKET_CONF_FOC_ERPMS,
CAN_PACKET_CONF_STORE_FOC_ERPMS,
CAN_PACKET_STATUS_5,
CAN_PACKET_POLL_TS5700N8501_STATUS,
CAN_PACKET_CONF_BATTERY_CUT,
CAN_PACKET_CONF_STORE_BATTERY_CUT,
CAN_PACKET_SHUTDOWN,
CAN_PACKET_IO_BOARD_ADC_1_TO_4,
CAN_PACKET_IO_BOARD_ADC_5_TO_8,
CAN_PACKET_IO_BOARD_ADC_9_TO_12,
CAN_PACKET_IO_BOARD_DIGITAL_IN,
CAN_PACKET_IO_BOARD_SET_OUTPUT_DIGITAL,
CAN_PACKET_IO_BOARD_SET_OUTPUT_PWM,
CAN_PACKET_BMS_V_TOT,
CAN_PACKET_BMS_I,
CAN_PACKET_BMS_AH_WH,
CAN_PACKET_BMS_V_CELL,
CAN_PACKET_BMS_BAL,
CAN_PACKET_BMS_TEMPS,
CAN_PACKET_BMS_HUM,
CAN_PACKET_BMS_SOC_SOH_TEMP_STAT
} CAN_PACKET_ID;
CAN Custom Control Message ์ ์ก ๋ฐฉ์์ ์๋์ ๊ฐ๋ค. ์๋๋ USBํต์ ์ ํตํด CAN Custom Control Message ์ ์ก ๋ฐฉ์์ผ๋ก ๋ณด๋ด์ง ๋ฐ์ดํฐ์ ์๋ก ์ด๋ฅผ ํด์ํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์์ USB๋ฅผ ํตํด ์ ์ก๋ CAN Custom Control Message์ ํด์๊ฒฐ๊ณผ 101๋ฒ ๋ชจํฐ์ DPS ์ ์ด๋ฅผ ๊ฐํ๋ ๋ช ๋ น์ด์์ ์ ์ ์๋ค. ์ด๋ฅผ ํตํด ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๊ฐ๋ฅํ CAN Custom Control Message์ ํฌ๋ฉง์ ์๊ฐํด๋ณผ ์ ์๊ณ ๋ค์๊ณผ ๊ฐ์ ํํ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
์ ์ด๋ฐฉ์(COMM_PACKET_ID)์ ์๋ ๋์ด๋ ์ ์ด ๋ช ๋ น์ด๋ค์ด ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
D1 D2 D3 D4 ์ ์ค๋ ค ๋ณด๋ด์ง๋ ๊ฐ์ ์ ํจ์ซ์ 10^n ๋ฐฐ๋ฅผ ๊ณฑํ ๊ฐ์ ๋ณด๋ด๊ธฐ ๋๋ฌธ์ ๋ฐ๋ ์ชฝ์์ ๋์ผํ ๊ฐ์ผ๋ก ๋๋ ์ค์ผ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์๋ฏธ์๋ ์ซ์๋ก ๋ณํ๋จ์ ์ฃผ์ํด์ผํ๋ค.
๋ชจํฐ์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ ์ด์ธ Duty์ ์ด๋ฅผ ์ํด์๋ COMM_SET_DUTY๋ฅผ ์ฌ์ฉํ๊ณ ์ด๋ Duty๊ฐ์ 1์ด 100% ๋ํฐ๋ฅผ, 0์ด 0%๋ฅผ ์๋ฏธํ๋ค.
์ ๋ฅ์ ์ด๋ฅผ ์ํด์ COMM_SET_CURRENT๋ฅผ ์ฌ์ฉํ๊ณ ์ด๋ ์ ์ด๊ฐ์ ๋จ์๋ A(์ํ์ด)๊ฐ์ ์ ์กํ๋ค. ์ ๋ฅ์ ์ด๋ฅผ ์ด์ฉํ์ฌ ๋ชจํฐ์ Brake๋ฅผ ์ธ๊ฐํ ์ ์๋๋ฐ ์ด๋ ์ฌ์ฉํ๋ ๊ฒ์ด COMM_SET_CURRENT_BRAKE ์ด๋ค. A ๋จ์๋ก ๊ฐ์ ์ ์กํ๋ค.
๋ชจํฐ์ ์๋์ ์ด๋ฅผ ์ํด์๋ COMM_SET_DPS๋ฅผ ์ฌ์ฉํ๋ค. Degree ๋จ์์ ๊ฐ์ ์ ์กํด์ผํ๋ฉฐ 100 DPS๋ฅผ ์ ์กํ๋ฉด ์ด๋น 100degree์ ์๋๋ก ํ์ ํ๋ค. ์ด๋ ํ์ ๊ฐ์๋๋ COMM_SET_DPS_AMAX (deg/sec^2)์์ ์ค์ ํ ๊ฐ์ ๋ฐ๋ฅด๋ฉฐ ํ์ ์ต๊ณ ์๋๋ COMM_SET_DPS_VMAX (deg/sec)์ผ๋ก ์ ํ๋๋ค. ๋ณธ ์๋์ ์ด๋ ๊ฐ๋ ฅํ Gain์ ์์น์ ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ Reference๋ฅผ ์ฌ๋ค๋ฆฌ๊ผด ์๋ Profile์ ์ํด ์์ฑํ์ฌ ์ ์ดํ๋ ๊ฒ์ผ๋ก ์ ์์์๋ ๊ณ ํ ํฌ, ๊ณ ์ ๋ฐ๋ ์ ์ด๊ฐ ๊ฐ๋ฅํ๋ค.
๋ชจํฐ์ ์ ๋ ์์น์ ์ด๋ COMM_SET_SERVO๋ฅผ ์ด์ฉํ๊ณ ์ด๋ ์ ์กํ๋ ๊ฐ์ ์์ฝ๋์ ์ ๋ ์์น ๊ฐ๋๊ฐ์ degree ๋จ์๋ก ์ ์กํ๋ค. COMM_SET_SERVO๋ฅผ ์ด์ฉํ๋ฉด ์ธ๊ฐํ ์ ๋์์น๋ฅผ ๊ธฐ์กด์ ์ค์ ํ ์๋ ์ต๋๊ฐ (COMM_SET_DPS_VMAX )์ ๋ฐ๋ผ ๋ชฉํ์์น๋ก ์ด๋ํ๋ค. ํ์จ์ด์์ ๊ธฐ๋ณธ์ ์ผ๋ก COMM_SET_DPS_VMAX ๊ฐ์ ์ต๋๋ก ์ค์ ํด๋์์ผ๋ฏ๋ก Application ์ํฉ์ ๋ฐ๋ผ ์ด๋ฅผ ๋ฏธ๋ฆฌ ์กฐ์ ํ, COMM_SET_SERVO์ ์ธํํด์ผ ์์ ํ๋ค.
๋จ์ํ, Position Lock ๊ธฐ๋ฅ์ ์ํ๋ค๋ฉด COMM_SET_DPS ์ ๋ชฉํ๊ฐ์ 0์ผ๋ก ์ค์ ํด๋์ผ๋ฉด ํ์ฌ ์์ฝ๋ ์์น์์ Position Lock์ด ๊ฐ๋ฅํ๋ค. ์ดํ ๋ชจํฐ๋ฅผ Release ํ๊ณ ์ถ์ ๋๋ COMM_SET_RELEASE ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ๋ฉด ๋๊ณ ๋ชฉํ๊ฐ์ ๊ธฐ๋ณธ 0์ผ๋ก ์ค์ ํ๋ฉด ๋๋ค.
COMM_SET_TRAJ ๋ ์ฌ๋ค๋ฆฌ๊ผด ์๋ Profile์ ๋ฐ๋ผ ์์น๋ช ๋ น๊ฐ์ ์์ฑํ์ฌ ์์น์ ์ด๋ฅผ ํ๋๋ฐ Manipulator์ ์์น์ ์ด์ ๋ชฉํ์ง์ ๊น์ง ๋ถ๋๋ฌ๊ฒ ์์ง์ด๊ณ ์ถ์ ๋ ์ฃผ๋ก ์ฌ์ฉํ ์ ์๋ค.
์ง๊ธ๊น์ง ์ค๋ช ํ CAN Custom Control Message ์ ์ก๋ฐฉ์์ Python Code๋ก ์๋ ์์ฑํ์ฌ ๋ํ๋ด์๋ค. CAN ๋ฉ์ธ์ง ์ ์ก์ ์ํด Peak System์ฌ์ PCAN-USB๋ฅผ ์ด์ฉํ์์ผ๋ฉฐ PCANBasic.py ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ์๋ค. CRC Check Sum ๊ณ์ฐ์ ์ํด์๋ VESC ํ์จ์ด์์ ์ ๊ณตํ๋ c์ธ์ด๋ก ์์ฑ๋ crc.c, crc.h ์ dll ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ปดํ์ผ ํ์ฌ ํ์ด์ฌ์์ importํ์ฌ ์ฌ์ฉํ์๋ค. VESC์์๋ crc.c ํ์ผ์ crc table์ด ์ ์ฅ์ด ๋์ด์์ด ์ผ๋ฐ์ ์ธ crc ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋ VESC ํ์จ์ด์์ ์๋ crc.c ํ์ผ์ ๊ทธ๋๋ก ์ด์ฉํด์ผ ํ๋ค. ํ์ด์ฌ ์ฝ๋๋ ์๋ ์ฃผ์์์ ํ์ธ ๊ฐ๋ฅํ๋ฉฐ ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ํ ํด๋น Repository์ ์กด์ฌํ๋ค..
https://github.com/dongilc/PCAN/tree/main/PCAN-Basic%20API/Samples/Python
from PCANBasic import *
'''
# crc check program from vesc firmware, dll file generated by below command
> gcc -fPIC -c crc.c
> gcc --shared -ocrc_vesc.dll crc.o
'''
import ctypes
crc_vesc = ctypes.cdll.LoadLibrary('./crc_vesc.dll')
can_packet_id = {
'CAN_PACKET_SET_DUTY':0,
'CAN_PACKET_SET_CURRENT':1,
'CAN_PACKET_SET_CURRENT_BRAKE':2,
'CAN_PACKET_SET_RPM':3,
'CAN_PACKET_SET_POS':4,
'CAN_PACKET_FILL_RX_BUFFER':5,
'CAN_PACKET_FILL_RX_BUFFER_LONG':6,
'CAN_PACKET_PROCESS_RX_BUFFER':7,
'CAN_PACKET_PROCESS_SHORT_BUFFER':8,
'CAN_PACKET_STATUS':9
}
comm_set_custom = {
'COMM_SET_DUTY':5,
'COMM_SET_CURRENT':6,
'COMM_SET_CURRENT_BRAKE':7,
'COMM_SET_RELEASE':100,
'COMM_SET_DPS':101,
'COMM_SET_DPS_VMAX':102,
'COMM_SET_DPS_AMAX':103,
'COMM_SET_SERVO':104,
'COMM_SET_TRAJ':105
}
global pc1
time_prev = 0
msg_type = {'0x0':'PCAN_MESSAGE_STANDARD',
'0x00':'PCAN_MESSAGE_STANDARD',
'0x1':'PCAN_MESSAGE_RTR',
'0x01':'PCAN_MESSAGE_RTR',
'0x2':'PCAN_MESSAGE_EXTENDED',
'0x02':'PCAN_MESSAGE_EXTENDED',
'0x4':'PCAN_MESSAGE_FD',
'0x04':'PCAN_MESSAGE_FD',
'0x8':'PCAN_MESSAGE_BRS',
'0x08':'PCAN_MESSAGE_BRS',
'0x10':'PCAN_MESSAGE_ESI',
'0x40':'PCAN_MESSAGE_ERRFRAME',
'0x80':'PCAN_MESSAGE_STATUS'}
def pcan_Open():
result = pc1.Initialize(PCAN_USBBUS1, PCAN_BAUD_1M)
if result != PCAN_ERROR_OK:
# An error occurred, get a text describing the error and show it
#
result = pc1.GetErrorText(result)
print(result[1])
else:
print("PCAN-USB (Ch-1) was initialized")
def pcan_Close():
# The USB Channel is released
#
result = pc1.Uninitialize(PCAN_USBBUS1)
if result != PCAN_ERROR_OK:
# An error occurred, get a text describing the error and show it
#
result = pc1.GetErrorText(result)
print(result[1])
else:
print("PCAN-USB (Ch-1) was released")
def ReadMessage():
# We execute the "Read" function of the PCANBasic
#
result = pc1.Read(PCAN_USBBUS1)
if result[0] == PCAN_ERROR_OK:
# We show the received message
#
ProcessMessage(result[1:])
return result[0]
def ReadMessages():
result = PCAN_ERROR_OK,
while (result[0] & PCAN_ERROR_QRCVEMPTY) != PCAN_ERROR_QRCVEMPTY:
result = pc1.Read(PCAN_USBBUS1)
if result[0] != PCAN_ERROR_QRCVEMPTY:
ProcessMessage(result[1:])
else:
if(result[0] != PCAN_ERROR_QRCVEMPTY): print('ERROR_CODE:{}'.format(hex(result[0])))
def ProcessMessage(*args):
global time_prev
theMsg = args[0][0]
itsTimeStamp = args[0][1]
newMsg = TPCANMsgFD()
newMsg.ID = theMsg.ID
newMsg.DLC = theMsg.LEN
for i in range(8 if (theMsg.LEN > 8) else theMsg.LEN):
newMsg.DATA[i] = theMsg.DATA[i]
newMsg.MSGTYPE = theMsg.MSGTYPE
newTimestamp = TPCANTimestampFD()
newTimestamp.value = (itsTimeStamp.micros + 1000 * itsTimeStamp.millis + 0x100000000 * 1000 * itsTimeStamp.millis_overflow)
time = "Timestamp:{:0.3f}sec".format(newTimestamp.value/1000000)
period = newTimestamp.value - time_prev
cycle_time = "Cycle Time:{:0.3f}msec".format(period/1000)
TYPE = "TYPE:{}".format(msg_type[hex(newMsg.MSGTYPE)])
EID = "EID:{}".format(hex(newMsg.ID))
DLC = "DLC:{}".format(newMsg.DLC)
DATA = ' '.join('{:02x}'.format(newMsg.DATA[i]) for i in range(newMsg.DLC))
if newMsg.MSGTYPE == 0x02: # PCAN_MESSAGE_EXTEND
print(time,"|",TYPE,"|",EID,"|",DLC,"|",DATA,"|",cycle_time)
time_prev = newTimestamp.value
def WriteFrame(id, dlc, data):
CANMsg = TPCANMsg()
CANMsg.ID = id
CANMsg.LEN = dlc
CANMsg.MSGTYPE = PCAN_MESSAGE_EXTENDED
for i in range(CANMsg.LEN):
CANMsg.DATA[i] = data[i]
return pc1.Write(PCAN_USBBUS1, CANMsg)
def GetFormatedError(error):
stsReturn = pc1.GetErrorText(error, 0)
if stsReturn[0] != PCAN_ERROR_OK:
return "An error occurred. Error-code's text ({0:X}h) couldn't be retrieved".format(error)
else:
return stsReturn[1]
def VescCustumControl(target_vesc_id, ctrl_type, ctrl_value):
'''
# Custom Control CAN Msg Frame Structure Example, id=0x53, comm_set_custom=0x65=101=COMM_SET_DPS, data=2000000
WriteFrame(0x00000553,8,[0x00, 0x24, 0x05, 0x01, 0xFF, 0x65, 0x00, 0x1E])
WriteFrame(0x00000553,3,[0x07, 0x84, 0x80])
WriteFrame(0x00000753,6,[0x53, 0x00, 0x00, 0x09, 0xBB, 0x4E])
'''
if ctrl_type == 'COMM_SET_DUTY':
value = int(ctrl_value * 100000.0)
elif ctrl_type == 'COMM_SET_POS':
value = int(ctrl_value * 1000000.0)
elif ctrl_type == 'COMM_SET_RPM' or ctrl_type == 'COMM_SET_RELEASE':
value = int(ctrl_value)
else:
value = int(ctrl_value * 1000.0)
command = comm_set_custom[ctrl_type]
d1 = (value >> 24) & 0xFF
d2 = (value >> 16) & 0xFF
d3 = (value >> 8) & 0xFF
d4 = value & 0xFF
data_arr = [d1, d2, d3, d4]
# Frame1, Frame2 is for 'CAN_PACKET_FILL_RX_BUFFER(0x05)'
eid1 = (can_packet_id['CAN_PACKET_FILL_RX_BUFFER'] << 8) | target_vesc_id
dlc1 = 8
# Frame1 = [0x00(length index), 0x24(COMM_CUSTOM_APP_DATA=36), 0x06(host_model=6=CAN), 0x01(number of vesc), 0xFF(Target_vesc_id=default local=255), command, d1, d2]
Frame1 = [0x00, 0x24, 0x06, 0x01, 0xFF, command, d1, d2]
eid2 = (can_packet_id['CAN_PACKET_FILL_RX_BUFFER'] << 8) | target_vesc_id
dlc2 = 3
# Frame2 = [0x07(length index), d3, d4]
Frame2 = [0x07, d3, d4]
# crc calculation only for 9byte data frame (that is except 0x00, 0x07)
data_arr = Frame1[1:] + Frame2[1:]
arr = (ctypes.c_ubyte * len(data_arr))(*data_arr)
crc = crc_vesc.crc16(arr,9)
crch = (crc >> 8) & 0xFF
crcl = crc & 0xFF
# Frame3 is for 'CAN_PACKET_PROCESS_RX_BUFFER(0x07)'
eid3 = (can_packet_id['CAN_PACKET_PROCESS_RX_BUFFER'] << 8) | target_vesc_id
dlc3 = 6
# Frame2 = [target_vesc_id, send, data_length high, data_length low, crch, crcl]
Frame3 = [target_vesc_id, 0x00, 0x00, 0x09, crch, crcl]
'''
# debug print of can msgs
print('Frame1 - eid:', format(hex(eid1)), '| dlc:', format(hex(dlc1)), '| data:', '[{}]'.format(', '.join(hex(x) for x in Frame1)) )
print('Frame2 - eid:', format(hex(eid2)), '| dlc:', format(hex(dlc2)), '| data:', '[{}]'.format(', '.join(hex(x) for x in Frame2)) )
print('Frame3 - eid:', format(hex(eid3)), '| dlc:', format(hex(dlc3)), '| data:', '[{}]'.format(', '.join(hex(x) for x in Frame3)) )
'''
WriteFrame(eid1, dlc1, Frame1)
WriteFrame(eid2, dlc2, Frame2)
WriteFrame(eid3, dlc3, Frame3)
print('Run VESC CAN Direct Control,', ctrl_type,':',ctrl_value)
if __name__ == '__main__':
pc1 = PCANBasic()
# Open PCAN Device
pcan_Open()
'''
# CAN MSG Read Routine
num = 0
# try~ except ํน์ ์์ธ
try:
# ๋ฌดํ ๋ฐ๋ณต
while True:
ReadMessages()
num += 1
# Ctrl + C๋ฅผ ์
๋ ฅํ ๊ฒฝ์ฐ
except KeyboardInterrupt:
print('Total Rcv number is {}, Quit to receive'.format(num))
'''
# CAN MSG Write Routine
#VescCustumControl(83, 'COMM_SET_DUTY',0.1)
#VescCustumControl(83, 'COMM_SET_CURRENT',0.5)
#VescCustumControl(83, 'COMM_SET_CURRENT_BRAKE',5)
#VescCustumControl(83, 'COMM_SET_DPS',0) # position lock on current position
#VescCustumControl(83, 'COMM_SET_DPS',1000)
#VescCustumControl(83, 'COMM_SET_DPS_VMAX',2000)
#VescCustumControl(83, 'COMM_SET_SERVO',0)
VescCustumControl(83, 'COMM_SET_RELEASE',0)
# Close PCAN Device
pcan_Close()
์ ์ฝ๋์์ #(์ฃผ์)์ฒ๋ฆฌ๋ VescCustumControl(~~~)๋ฅผ ํ๋์ฉ ์คํํ์ฌ 83๋ฒ VESC์ ํด๋น ๋ช ๋ น์ด๋ฅผ CAN ํต์ ์ ํตํด ์ ์ก๊ฐ๋ฅํจ์ ํ์ธํ์๋ค.
Last updated