-
Notifications
You must be signed in to change notification settings - Fork 0
Motor Controls Tutorial
The goal of this library is simply to write a program that can control how much we want the wheel to spin. Or more technically we want to be able to control how much current is running through the motor while making it as safe and fail proof as possible.
To accomplish this we use serial communications to send signals to the FSESC that in turn safely applies 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 exist which 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 packetOnce the data is encoded into bytes we can send it across the cable using the pySerial library. Although this library has many advanced uses and different methods we don't need to use all of those features. The only features we really need is 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 you want the program to shut down, not wait eternally for a new plug.
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 essentially prevents from 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()Using these two libraries we can write a program that will send a current of 3000 miliamps to the Motor with these lines
import serial
import pyvesc
port_Object = serial.Serial('COM3', 11520, timeout = 0.1)
message = pyvesc.SetCurrent(3000)
packet = pyvesc.encode(message)
port_Object.write(packet)
port_Object.flush()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 encase the serial write command to loop it would stay at 3000 until the loop exit condition is met.
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.
At this point if you are not familiar with Object-Oriented Programming concepts you should stop and read this and make sure you understand it
We built our class with safety and expandability in mind. We want to make sure that if something goes wrong that 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 CurrentNow if you have much knowledge in programming methods you should realize that the run() method will block anything else from happening resulting in a infinite loop. This is a very difficult problem to solve as we need to be constantly writing to the FSESC without blocking the rest of the code. There are many ways this could be done and all of them make our code extremely dangerous and confusing...
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.
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."
from threading import Thread
def threaded(fn):
def wrapper(*args, **kwargs):
thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
thread.start()
return thread
return wrapperFor the sake of this guide I'm not going to explain this besides saying its a wrapper for a function or method. To use it in our class we simply put:
@threaded
def run(self):
while self.active:
# Constantly write stored current to FSESCAnd 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.
Here is a link to our final code: Motor.py