One of the first issues we had to tackle as a group was developing a method to communicate with the our vehicle using Python. We used a FSESC 4.12 to use for the car, as the stock speed controller was very locked down and was not intended to be used with custom software. To setup the FSESC we used the VescTool software to calibrate it with the motors on our vehicle.

Controlling the Motor

PyVesc

To control the VESC, we use serial communications to send signals that in turn applies a current to the motor. Ordinarily this requires knowledge of serial protocol as well as byte shifts in order to send numbers bigger than 255 over a buffer. Luckily a preexisting library called PyVesc allows us to easily encode the values to be sent.

This allows our encoding method to look like this:

@staticmethod
def current_packet(value) -> bytes:

    message = pyvesc.SetCurrent(value)
    packet = pyvesc.encode(message)
    return packet

PySerial

Once the data is encoded into bytes we can send it across the cable using the pySerial library using the ability to send values to the FSESC.

To use this library we had to initialize a port object with the line:

port_Object = serial.Serial("COM3", 11520, timeout=0.1)

This initializes the port at location COM3 and sets the message speed or baud rate to 11520, the speed required by the FSESC, the final parameter timeout=0.1 is vital for making sure your program does not wait forever for the port to connect. If the port isn’t plugged in, the program will shut down.

Write and Flush

The Serial Class comes with two methods that we need to use. The obvious method is the write(BYTES) method. This method sends bytes through the established connection, the only data type allowed to be sent is Bytes. The less obvious method is the flush() method. This method prevents any unwanted behavior by flushing the buffer every time it is called. Currently we haven’t seen this have direct correlation to any issues, however it keeps only needed values in the buffer.

port_Object.write(PyVesc_Packet)
port_Object.flush()

Final Result

Using these two libraries we can write a program that will send a current of 3000 miliamps to the Motor with these lines.

port_Object = serial.Serial('COM3', 11520, timeout = 0.1)

message = pyvesc.SetCurrent(3000)
packet = pyvesc.encode(message)

port_Object.write(packet)
port_Object.flush()

Complications

If you were to run the code above you might notice that it either doesn’t spin the wheel or it only spins for a couple seconds before fading out. As far as we can tell this is because the FSESC is setup in such a way that it immediately moves on to the next packet which in this case is nothing so the current is set to 3000 for one cycle before falling to 0. If you were to loop the serial write command, it would stay at 3000 until the loop exit condition is met.

The Motor Class

Although the included libraries allow us to simplify writing the motor, we want to simplify it even further. Not just so we can write more pythonic code but also so we can ensure the values passed are safe. To do this we created a class file with individual methods and variables for controlling the motor.

We built our class with safety and expandability in mind. We want to make sure that if something goes wrong, the motor stops in a safe and predefined way. At no point do we want the motor to act outside of our control.

The resulting class looks similar to this:

class Motor:

    def __init__(self, serial_connection):

        try:
            self.FSESC = serial_connection
        except:
            raise Exception('COULD NOT CONNECT TO FSESC')

        # Init basic variables likes the current and when finished:
        print("FSESC Connected")

    def exit(self):
        self.active = False


    def run(self):
        while self.active:

            # Constantly write stored current to FSESC

    def set_current(self, value):
        # Sets Current

Realize that the run() method will block anything else from happening, resulting in an infinite loop. This problem must be approached carefully to prevent out program from running out of memory or losing time in unnecessary tasks.

Also if you look at the __init__() parameters you should see the serial connector is passed into it as an argument, this is done so we can keep all serial initializations in one place.

Threading

What we want is a process running separately from the rest of the code. Python offers a solution in threading. Threading allows us to simulate two different processes running side by side. However the way Python handles threads prevents variables from being shared across threads. So if we start a looping thread constantly sending a current to the FSESC but then change the current from the main thread, it will have no impact on the looping thread. This is done by Python to prevent us from pointing a gun at our foot. Luckily there are ways to get around Pythons “good practices.”

def threaded(fn):
    def wrapper(*args, **kwargs):
        thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
        thread.start()
        return thread
    return wrapper

This snippet used a feature of Python called Decorators that allows us to wrap the run function in a thread so that we can run this loop without blocking the main thread.

@threaded
def run(self):
        while self.active:
            # Constantly write stored current to FSESC

And now when we call motor_Object.run() a separate thread is created where the signal is constantly being written if you want to be able to control it like a normal thread you can simply store it to a variable such as:

running_Thread = motor_Object.run()

With this working we can now create a safe motor class constantly looping without us ever wanting to think about the fact that there are now two threads.

Conclusion

The completed VESC control system allows us to easily send signals to the motor of the Traxxas car. Furthermore, our implementation allows us to use both current and duty cycles, which have their own uses. The expandability of our implementation will allow us to easily edit and add on new feature in the future. These features include integration and speed checks with the IMU as well as integration with the steering system to optimize balancing the car. Completing the VESC aspect allows us to approach the control scheme of the car more easily.

Note: This post was compiled using previous internal documentation.