Multiple 18v7 motor controllers over USB

I am having a problem. I am building a kit for others to use, and want to avoid having them solder. I have a two wheeled vehicle powered by two dc motors, so I have two 18v7 dc motor controllers all wired up and ready to go. I am controlling them via a BeagleBone black using python. Here is the code:

#!/usr/bin/python
import serial
import time
class MotorControllerOne(object):
    def __init__(self, port="/dev/ttyACM0"):
        self.ser=serial.Serial(port = port)
    def exitSafeStart(self):
        command = chr(0x83)
        self.ser.open()
        self.ser.write(command)
        self.ser.flush()
        self.ser.close()
    def setSpeed(self, speed):
        if speed > 0:
            channelByte = chr(0x85)
        else:
            channelByte = chr(0x86)
        lowTargetByte = chr(speed & 0x1F)
        highTargetByte = chr((speed >> 5) & 0x7F)
        command = channelByte + lowTargetByte + highTargetByte
        self.ser.open()
        self.ser.write(command)
        self.ser.flush()
        self.ser.close()
    def reset(self):
        self.ser.reset()
    def close(self):
        self.ser.close()
class MotorControllerTwo(object):
    def __init__(self, port="/dev/ttyACM1"):
        self.ser=serial.Serial(port = port)
    def exitSafeStart(self):
        command = chr(0x83)
        self.ser.open()
        self.ser.write(command)
        self.ser.flush()
        self.ser.close()
    def setSpeed(self, speed):
        if speed > 0:
            channelByte = chr(0x85)
        else:
            channelByte = chr(0x86)
        lowTargetByte = chr(speed & 0x1F)
        highTargetByte = chr((speed >> 5) & 0x7F)
        command = channelByte + lowTargetByte + highTargetByte
        self.ser.open()
        self.ser.write(command)
        self.ser.flush()
        self.ser.close()
    def reset(self):
        self.ser.reset()
    def close(self):
        self.ser.close()

if __name__=="__main__":
    motor1 = MotorControllerOne()
    motor2 = MotorControllerTwo()
    print 'Ports created'
    motor1.exitSafeStart()
    motor2.exitSafeStart()
    print 'SafeStart'
    motor1.setSpeed(int(2000))
    motor2.setSpeed(int(-2000))
    print 'setSpeed'
    time.sleep(.5)
    motor1.setSpeed(int(0))
    motor2.setSpeed(int(0))
    time.sleep(.5)
    print 'at the end'
    motor1.close()
    motor2.close()
    print 'done'

So here is the problem. This runs once fine, but when I try to run it again, it hangs. When I halt the program I get the following error:

Traceback (most recent call last):
  File "./dcmotor.py", line 59, in <module>
    motor1.exitSafeStart()
  File "./dcmotor.py", line 11, in exitSafeStart
    self.ser.flush()
  File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 491, in flush
    self.drainOutput()
  File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 565, in drainOutput
    termios.tcdrain(self.fd)

When I use the SmcCmd command to try and interrogate the unit, it give me a time out. If I change the code and only talk to one unit, then it runs multiple times just fine.

Any suggestions?

Hello.

I am sorry you are having trouble with your Simple Motor Controllers.

You said SmcCmd was giving you an error message about a timeout. Could you please give me the command you are invoking SmcCmd with, along with the full output from SmcCmd?

I think the main plan for solving this should be to simplify all parts of the system until we find a single connection or line of code that causes the problem. Do you have anything else besides motors, power, and USB connected to the Simple Motor Controllers? If so, I would disconnect it. If you disconnect the motors, does the problem still happen? If you also disconnect the motor power supply, does it still happen?

I looked at your code and only noticed two things of concern:

  • You are closing each serial port twice in a row.
  • You are trying to encode a negative speed; the speed needs to be between 0 and 3200.

You said that a simpler version of the code that only controls one motor works, so it is possible that something in this code was causing the problem. We should be able to simplify your code until we find two very similar programs, with one working and the other not working.

I started simplifying it by removing unused and duplicated code, and the result is below. I also added the line “speed = -speed” in the appropriate place.

#!/usr/bin/python
import serial
import time
class MotorController:
    def __init__(self, port="/dev/ttyACM0"):
        self.ser = serial.Serial(port = port)
    def sendCommand(self, command):
        self.ser.open()
        self.ser.write(command)
        self.ser.flush()
        self.ser.close()
    def exitSafeStart(self):
        self.sendCommand(chr(0x83))
    def setSpeed(self, speed):
        if speed > 0:
            channelByte = chr(0x85)
        else:
            speed = -speed;
            channelByte = chr(0x86)
        lowTargetByte = chr(speed & 0x1F)
        highTargetByte = chr((speed >> 5) & 0x7F)
        self.sendCommand(channelByte + lowTargetByte + highTargetByte)
    def close(self):
        self.ser.close()
        
if __name__=="__main__":
    motor1 = MotorController("/dev/ttyACM0")
    motor2 = MotorController("/dev/ttyACM1")
    print 'Ports created'
    motor1.exitSafeStart()
    motor2.exitSafeStart()
    print 'SafeStart'
    motor1.setSpeed(2000)
    motor2.setSpeed(-2000)
    print 'setSpeed'
    time.sleep(0.5)
    motor1.setSpeed(0)
    motor2.setSpeed(0)
    time.sleep(0.5)
    print 'at the end'
    motor1.close()
    motor2.close()
    print 'done'

You might want to make sure that this code behaves the same way your code did, and then continue simplifying it. Each time you simplify it, you should see if the behavior changes. I would start by removing the two calls to close() at the very end and removing the close method definition from the MotorController class. Then I would add a call to “motor1.exitSafeStart()” at the very end; this should allow you to reproduce the problem by just running the Python script once instead of having to run it twice. (If you still have to run it twice to get the error, that would be very interesting.) Then I would change each call to setSpeed to use a speed of 1 instead of -2000 or 2000.

I edited your first post, but in the future please highlight your code and press the “Code” button above the editing area so that it will be formatting nicely.

What kind of kit are you making and who will be using it?

–David

So, I tried your code, and here is what I got

ubuntu@ubuntu-armhf:~$ cd smc_linux
ubuntu@ubuntu-armhf:~/smc_linux$ ./SmcCmd -l
2 Simple Motor Controllers found:
18v7 #51FF-7306-4980-4956-1527-1287
18v7 #51FF-7406-4980-4956-5842-1287
ubuntu@ubuntu-armhf:~/smc_linux$ emacs debug.py

During this phase I cut and pasted your code, the only change I made was this:

class MotorController:
    def __init__(self, port):
As opposed to
class MotorController:
    def __init__(self, port="/dev/ttyACM0"):
ubuntu@ubuntu-armhf:~/smc_linux$ python debug.py
Ports created
SafeStart
setSpeed
at the end
done
ubuntu@ubuntu-armhf:~/smc_linux$ python debug.py
Ports created
^CTraceback (most recent call last):
  File "debug.py", line 30, in <module>
    motor1.exitSafeStart()
  File "debug.py", line 13, in exitSafeStart
    self.sendCommand(chr(0x83))
  File "debug.py", line 10, in sendCommand
    self.ser.flush()
  File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 491, in flush
    self.drainOutput()
  File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 565, in drainOutput
    termios.tcdrain(self.fd)
KeyboardInterrupt
ubuntu@ubuntu-armhf:~/smc_linux$ ./SmcCmd -l
Error: There was an error getting the list of connected devices.
Error getting serial number string from device (pid=a1, vid=1ffb).
Timeout.
ubuntu@ubuntu-armhf:~/smc_linux$

Then I changed the code, adding these lines:

    print 'at the end'
    motor1.exitSafeStart()
    motor2.exitSafeStart()
    print 'SafeStart the second time'
    motor1.close()
    motor2.close()
    print 'done'

And then did a sudo reboot, ran the program, then ran the program again, with the same result.

ubuntu@ubuntu-armhf:~/smc_linux$ python debug.py
Ports created
SafeStart
setSpeed
at the end
SafeStart the second time
done
ubuntu@ubuntu-armhf:~/smc_linux$ python debug.py
Ports created
^CTraceback (most recent call last):
  File "debug.py", line 30, in <module>
    motor1.exitSafeStart()
  File "debug.py", line 13, in exitSafeStart
    self.sendCommand(chr(0x83))
  File "debug.py", line 10, in sendCommand
    self.ser.flush()
  File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 491, in flush
    self.drainOutput()
  File "/usr/lib/python2.7/dist-packages/serial/serialposix.py", line 565, in drainOutput
    termios.tcdrain(self.fd)
KeyboardInterrupt
ubuntu@ubuntu-armhf:~/smc_linux$ ./SmcCmd -l
Error: There was an error getting the list of connected devices.
Error getting serial number string from device (pid=a1, vid=1ffb).
Timeout.
ubuntu@ubuntu-armhf:~/smc_linux$

It seems more than happy to talk to both ports up and until I exit the program. Then the ports appear unresponsive. I don’t know if there is a way to reset the ports from the command line.

Thanks for your help on this, by the way.

Richard

Hello, Richard.

It is good that the simplified code still exhibits the same behavior. Could you tell me about how your hardware is setup? I asked some questions about it in the first post. Could you try removing MotorController.close and all the calls to it? It shouldn’t be needed because the serial port is already closed after each command.

–David

Pretty straightforward, I am going out of my BeagleBone Black to a powered USB hub (I have tried it both powered and not powered, and it still exhibits the same behavior.) The two controllers are plugged into the USB hub. Power is supplied by an 4 ‘AA’ battery holder.

I removed all the close() statements, and the results are still the same. I’m not sure I can make the code any simpler. Just as a data point, I did the same thing using your C programming example, and saw the similar results. The second time I run the compiled code the program hangs.

Richard

Well, this continues to be an interesting problem. I would think that closing a Python process that has no handles open to any devices should really not have any affect on your USB.

I think we can make the code simpler, but first could you try disconnecting everything from the simple motor controllers besides USB?

–David

OK, I disconnected everything except the USB and still saw the same behavior. The controllers are set up pretty much out of the box, I did run the motor controller SW on my PC, and was able to adjust the speed, just to check that they were working, but I didn’t change any settings.

Anything from a HW jumper perspective I could look at?

Richard

I doubt that the Simple Motor Controller’s jumpers could be causing this problem as long as they are off or in one of the positions recommended by the user’s guide. If you want, you can post a picture of your controllers and we check the jumper positions and anything else that might be visibly wrong.

I tried to reproduce your problem here this morning. I plugged two Simple Motor Controllers (of two different types) into my Raspberry Pi via a USB hub. I ran some code that was very similar to the code in my first post, but for some reason, my version of pyserial (which I installed on Python 2.6) would raise an error when you try to open a serial port that is already open and providing the port in the constructor of the Serial object means the port will be opened. I had to modify the init method to avoid opening the port right away:

def __init__(self, port):
    self.ser=serial.Serial()
    self.ser.port = port

There was a problem with the first USB hub I used, which seemed to not work well with one of my Simple Motor Controllers (the SMC’s green LED was off and just blinking occasionally, which indicates USB suspend mode). After switching to a different hub, I was able to run the Python program successfully many times in a row.

I think it is still worthwhile to simplify the code more so you can get to the point where you have two very similar programs, one working and one not working. How about removing the first call to motor2.setSpeed?

–David

David,

Found the problem. It was with the USB hub I was using. I had a GearHead powered USB hub, so I started replacing cables and components, just to make sure it wasn’t a bad cable. When I replaced the USB hub with a different brand powered USB hub, the system worked fine. Also, I tried one other powered USB hub, and that worked as well.

Sorry for the trouble, but this might be an interesting piece of data for you.

Richard

I am glad you were able to get it working and thank you for letting me know what the problem was!

–David