sndmnn_
Posts: 1
Joined: Sat Feb 08, 2020 11:50 pm
Contact: Twitter

Interfacing a Raspberry Pi board with LewanSoul LeArm

Sun Feb 09, 2020 12:12 am

I recenty bought myself a LewanSoul LeArm to learn more about Python, Raspberry Pi and automation.
The user manual specify a Comunication Interface of Secondary Development to control the Servos from GPIO ports. The problem is: I'm having trouble finding out how to use the ports (5V, RX, TX and GND) in the interface. I already connected the arm's board with my Pi 3B, but I'm still struggling to use the RX and TX pins to control the servos.
Can someone explain me how do they work, or point me websites, books, articles etc. to read about this?

This link provides the Servo Controller Communication Protocol: https://www.dropbox.com/sh/b3v81sb9nwir ... 2.pdf?dl=0.

PS: I'm not a programming beginner, but I know little about GPIO ports and Python's GPIO library. My goal is to introduce myself in the IoT/Automation world.
You can find me on github.com/sndmnn or on Twitter @sndmnn_

blimpyway
Posts: 209
Joined: Mon Mar 19, 2018 1:18 pm

Re: Interfacing a Raspberry Pi board with LewanSoul LeArm

Wed Feb 12, 2020 1:46 am

The reason you got little feedback might be you didn't mentioned what a lewansolo is, what it is supposed to do and how

User avatar
scruss
Posts: 2811
Joined: Sat Jun 09, 2012 12:25 pm
Location: Toronto, ON
Contact: Website

Re: Interfacing a Raspberry Pi board with LewanSoul LeArm

Wed Feb 12, 2020 2:30 am

blimpyway wrote:
Wed Feb 12, 2020 1:46 am
The reason you got little feedback might be you didn't mentioned what a lewansolo is, what it is supposed to do and how
It's a robot arm: LeArm Robot.

So, OP: have you got TX→RX and RX→TX, with GND → GND? Might not need 5V or 3V3 at all if the arm is self-powered. Then you need to open the port at 9600 baud.

The manual you linked to (thanks for doing that) looks helpful. I haven't seen that protocol anywhere else, so it's unlikely there are many books on it. So to the first example/command:

Code: Select all

1. Command name: CMD_SERVO_MOVE
Length(Data Length):
Command value: 3
Description: Control the rotation of any servo(you can control anyone as you like),
Length = the number of control servo*3+5
Parameter 1: The number of servo to be controlled
Parameter 2: Lower 8 bits of time value
Parameter 3: Higher 8 bits of time value
Parameter 4: Servo ID number
Parameter 5: Lower 8 bits of the angle position value
Parameter 6: Higher 8 bits of the angle position value
Parameter......: The format is the same as the parameter 4, 5 and 6, control the angle positions of different IDs
For example:
(1). Control the No.1 servo to turn to 2000 position within 1000ms
Header
0x55 0x55
Length
0x08
Command Parameter
0x03 0x01 0xE8 0x03 0x01 0xD0 0x07
So you have to send the bytes (shown in hex here: you need to send them as 8-bit values): 0x55 0x55 0x08 0x03 0x01 0xE8 0x03 0x01 0xD0 0x07. I don't know if it sends any confirmation back, or if you can query positions.
‘Remember the Golden Rule of Selling: “Do not resort to violence.”’ — McGlashan.

dryhops
Posts: 4
Joined: Fri Feb 14, 2020 7:44 pm

Re: Interfacing a Raspberry Pi board with LewanSoul LeArm

Sat Feb 15, 2020 12:48 am

Hi there!

I actually was working on the same problem a few weeks ago. I have a working solution in python. This is not code I consider polished. But it does work.

Code: Select all

#!/usr/bin/env python
from time import sleep
import serial
import binascii
from multiprocessing import Process, Queue

FRAME_HEADER = 0x55
CMD_SERVO_MOVE = 0x03
CMD_ACTION_GROUP_RUN = 0x06
CMD_ACTION_GROUP_STOP = 0x07
CMD_ACTION_GROUP_SPEED = 0x0B
CMD_GET_POSITIONS = 0x15
CMD_GET_BATTERY_VOLTAGE = 0x0F

BATTERY_VOLTAGE = 0x0F
ACTION_GROUP_RUNNING = 0x06
ACTION_GROUP_STOPPED = 0x07
ACTION_GROUP_COMPLETE = 0x08

#maximum and minimum positions each servo can travel
clawLimits = [200,700] #ID1
wristRotLimits = [0,1000] #ID2
wristLimits = [0,1000] #ID3
elbowLimits = [0,700] #ID4
shoulderLimits = [150,800] #ID5
rotationLimits = [0,1000] #ID6

#Serial Queues for reading and writing
rQueue = Queue()
wQueue = Queue()

#converts -135/135 (servo range +/- 30) to 0-1000 (servo position unit)
def anglesToPositions(angles):
	positions = []
	for angle in angles:
		positions.append(int(round((angle+135)/270 * 1000)))
	return positions

#converts 0-1000 (servo position unit as integer) to -135/135 (servo range +/- 30 as float)
def positionsToAngles(positions):
	angles = []
	for pos in positions:
		angles.append((pos/1000 * 270)-135)
	return angles

#private worker method for startSerialProcess()
def openSerialPort(writeQueue,readQueue):
	serialData = serial.Serial(
		port='/dev/ttyS0', #Replace ttyS0 with ttyAM0 for Pi1,Pi2,Pi0
		baudrate = 9600,
		bytesize=serial.EIGHTBITS,
		timeout=None
		)
	#serialData.flushInput()
	#serialData.flushOutput()
	try:
		while True:
			if(serialData.inWaiting()):
				byte = serialData.read(1)
				readQueue.put(byte)
			if(not writeQueue.empty()):
				byte = writeQueue.get()
				serialData.write(byte)
	finally:
		serialData.close()

#Call to begin listining to the serial port. Uses openSerialPort
def startSerialProcess():
	p = Process(target=openSerialPort, args=(wQueue,rQueue))
	p.daemon = True
	p.start()

#Move all servo/angle combos in the time provided
#Ordered by servo ID NOT by J axis
def moveServos(servoIDs, positions, time):
	numOfServos = len(servoIDs)
	#check if each servo has a corresponding time and angle
	if (len(positions) != numOfServos):
		print("Provided positions or times do not match the number of servos requested. Exiting...")
		return
	#print("Moving Servos: " + str(servoIDs))

	packetLength = 5 + 3*numOfServos
	#create packet header
	cmdbytes = bytearray(4)
	cmdbytes[0] = FRAME_HEADER  #header 1
	cmdbytes[1] = FRAME_HEADER  #header 2
	cmdbytes[2] =  packetLength #packet length
	cmdbytes[3] = CMD_SERVO_MOVE  #command number
	cmdbytes.append(numOfServos) #number of servos that will move
	timeLSB = time & 0xFF #least significant bit
	timeMSB = (time >> 8) & 0xFF #most significant bit
	cmdbytes.append(timeLSB)
	cmdbytes.append(timeMSB)

	for i in range(numOfServos):
		#split angle into 2 bytes
		posLSB = positions[i] & 0xFF
		posMSB = (positions[i] >> 8) & 0xFF
		#per servo parameters
		cmdbytes.append(servoIDs[i])
		cmdbytes.append(posLSB)
		cmdbytes.append(posMSB)

	#bytesAsHex = ' '.join(format(x, '02x') for x in cmdbytes)
	#print(bytesAsHex)
	wQueue.put(cmdbytes)
	sleep(time/1000)

#Stop all servos, 1-6
def powerOffServos():
	wQueue.put(bytearray.fromhex("5555091406010203040506"))

#Saved in robot memory
def runActionGroup(groupNumber,iterations=1):
	#create packet header
	cmdbytes = bytearray(7)
	cmdbytes[0] = FRAME_HEADER  #header 1
	cmdbytes[1] = FRAME_HEADER  #header 2
	cmdbytes[2] = 0x05 #packet length
	cmdbytes[3] = CMD_ACTION_GROUP_RUN  #command number
	cmdbytes[4] = groupNumber

	itrLSB = iterations & 0xFF #least significant bit
	itrMSB = (iterations >> 8) & 0xFF #most significant bit
	cmdbytes[5] = itrLSB
	cmdbytes[6] = itrMSB

	bytesAsHex = ' '.join(format(x, '02x') for x in cmdbytes)
	#print(bytesAsHex)
	wQueue.put(cmdbytes)

#Cancel running action group
def stopActionGroup():
	#create packet header
	cmdbytes = bytearray(4)
	cmdbytes[0] = FRAME_HEADER  #header 1
	cmdbytes[1] = FRAME_HEADER  #header 2
	cmdbytes[2] = 0x02 #packet length
	cmdbytes[3] = CMD_ACTION_GROUP_STOP  #command number
	bytesAsHex = ' '.join(format(x, '02x') for x in cmdbytes)
	#print(bytesAsHex)
	wQueue.put(cmdbytes)

#get current servo positions
def readPositions():
	#create packet header
	cmdbytes = bytearray(11)
	cmdbytes[0] = FRAME_HEADER  #header 1
	cmdbytes[1] = FRAME_HEADER  #header 2
	cmdbytes[2] = 0x09 #packet length
	cmdbytes[3] = CMD_GET_POSITIONS  #command number
	cmdbytes[4] = 0x06
	cmdbytes[5] = 0x01
	cmdbytes[6] = 0x02
	cmdbytes[7] = 0x03
	cmdbytes[8] = 0x04
	cmdbytes[9] = 0x05
	cmdbytes[10] = 0x06
	bytesAsHex = ' '.join(format(x, '02x') for x in cmdbytes)
	#print(bytesAsHex)
	wQueue.put(cmdbytes)

#######
# Predefined Positional Commands
#######

#Move all servos to home position
def homeServos():
	moveServos([1,2,3,4,5,6], [200,127,791,207,656,508], 3000)

def closeClawMax():
	moveServos([1], [clawLimits[0]], 500)
	
def closeClawKeg():
	moveServos([1], [390], 500)

def openClaw():
	moveServos([1], [clawLimits[1]], 500)

class ServoComms:
	def __init__(self):
		startSerialProcess()
		# ~ sleep(5)
		# ~ moveServos([4,6], [300,400], 1000)
		# ~ sleep(3)
		#homeServos()
		#readPositions()
		#runActionGroup(7,2)
		#sleep(3)
		# ~ stopActionGroup()


		# ~ while True:
			# ~ if(not rQueue.empty()):
				# ~ byte = rQueue.get()
				# ~ print(binascii.hexlify(byte))

dryhops
Posts: 4
Joined: Fri Feb 14, 2020 7:44 pm

Re: Interfacing a Raspberry Pi board with LewanSoul LeArm

Sat Feb 15, 2020 12:54 am

I've attached the version of the manual that I referenced in the code.
Attachments
LSC Series Servo Controller Communication Protocol V1.2.zip
(91.63 KiB) Downloaded 3 times

dryhops
Posts: 4
Joined: Fri Feb 14, 2020 7:44 pm

Re: Interfacing a Raspberry Pi board with LewanSoul LeArm

Sat Feb 15, 2020 1:04 am

Using a pi 4. Check your GPIO chart for your PI version:
For the hardware, I used a level shifter to convert the 3.3v logic of the Pi to the 5v logic of the robot arm servo controller. V to pin 1, GND to 6, TXD to 8 and RXD to 10. Again, your Pi pins may differ.

Image
Attachments
IMG_202002142_165918539.jpg
IMG_202002142_165918539.jpg (47.76 KiB) Viewed 58 times

Return to “Automation, sensing and robotics”