├── featherwiring.jpg ├── LICENSE.md ├── README.md ├── umaestro.py ├── umaestro.md └── maestro.py /featherwiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC4564/Maestro/HEAD/featherwiring.jpg -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steven L. Jacobs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | maestro.py 2 | ========== 3 | 4 | This Python class supports Pololu's Maestro servo controller over USB serial. Great for use with the Raspberry Pi, but works with Windows too. 5 | 6 | The class includes methods to control servos (position, speed, acceleration), read servo position, and start/stop Maestro scripts. See Pololu's on-line documentation to learn about the full capabilities of this nifty micro-controller. 7 | 8 | ## Setup 9 | 10 | Pololu's Maestro Windows installer sets up the Maestro Control Center, used to configure, test and program the controller. Be sure the Maestro is configured for "USB Dual Port" serial mode, which is [not the default](https://www.pololu.com/docs/0J40/3.c). 11 | 12 | You'll need to have the 'pyserial' Python module installed to use maestro.py. 13 | 14 | If you have pip available, pyserial can be installed as follows: 15 | 16 | python -m pip install pyserial 17 | 18 | or, for python3 19 | 20 | python3 -m pip install pyserial 21 | 22 | Alternatively, you can download pyserial-2.7.tar.gz from http://sourceforge.net/projects/pyserial/files/pyserial/ 23 | 24 | wget http://sourceforge.net/projects/pyserial/files/pyserial/2.7/pyserial-2.7.tar.gz 25 | 26 | and then install 27 | 28 | tar –zxf pyserial-2.7.tar.gz 29 | cd pyserial-2.7 30 | sudo python setup.py install 31 | 32 | Check out http://pyserial.readthedocs.io/en/latest/pyserial.html#installation for other install options. 33 | 34 | To download the maestro.py module issue the following command: 35 | 36 | wget https://raw.githubusercontent.com/FRC4564/maestro/master/maestro.py 37 | 38 | ## How to Use 39 | 40 | Example usage of maestro.py: 41 | 42 | import maestro 43 | servo = maestro.Controller() 44 | servo.setAccel(0,4) #set servo 0 acceleration to 4 45 | servo.setTarget(0,6000) #set servo to move to center position 46 | servo.setSpeed(1,10) #set speed of servo 1 47 | x = servo.getPosition(1) #get the current position of servo 1 48 | servo.close() 49 | 50 | There are other methods provided by the module. The code is well documented, if you'd like to learn more. 51 | 52 | For use on Windows, you'll need to provide the COM port assigned to the Maestro Command Port. You can identify the port by starting Device Manager and looking under Ports (COM & LPT). Here's how to instantiate the controller for Windows for COM port 3. 53 | 54 | import maestro 55 | m = maestro.Controller('COM3') 56 | 57 | ## Permission issue 58 | 59 | If you find that Linux complains about permissions trying to access the ttyACM device, just add your user to the 'dialout' group by issuing the following: 60 | 61 | sudo adduser $USER dialout 62 | 63 | You'll need to reboot for the change to take effect. 64 | 65 | ## Going Further 66 | 67 | The Maestro series of controllers can support much more than just servo control. The PWM-based protocol used to control servos is also compatibile with RC Electronic Speed Controllers (ESCs) to control motor power and direction. There are many motor controller options available for both brushed and brushless motors. 68 | 69 | Beyond servo and motor control, the Maestros can also be used for digital inputs, digital outputs and analog inputs. The setTarget and getPosition methods support setting and reading values for these extended features. You do, however, need to use the Maestro Control Center to change the mode of individual channels from "servo" to either "input" or "output". Read the Maestro documentation on how to properly use these special modes and wire them properly. 70 | 71 | I've found that the many capabilities of the Maestro lends itself nicely to robotics. If you're interested in some robotic applications check out [Basic PiBot](https://github.com/FRC4564/BasicPiBot). Its a simple framework to get you started with making your own interactive and/or autonomous machines. The maestro has proven to be rock solid in this applications. 72 | -------------------------------------------------------------------------------- /umaestro.py: -------------------------------------------------------------------------------- 1 | import board 2 | import busio 3 | # 4 | # umaestro.py - Pololu Maestro Servo Controller for CircuitPython 5 | # 6 | # Steven Jacobs -- Feb 2020 7 | # https://github.com/FRC4564/Maestro/ 8 | # 9 | # For detailed documentation see the full Python implementation in maestro.py. 10 | # 11 | class Controller: 12 | # Maestro must be set to UART mode under Serial Settings (see Pololu Maestro Control Center). 13 | # Tested with Feather M4 and Micro Maestro. Because the Feather is a 3.3v device and the Feather 14 | # a 5v device, level shifting is required. See umaestro.md for details. 15 | def __init__(self, tx=board.TX, rx=board.RX, baud=115200, device=b'\x0c'): 16 | self.uart = busio.UART(tx, rx, baudrate=baud) 17 | self.PololuCmd = b'\xaa' + device 18 | self.Targets = [0] * 24 19 | self.Mins = [0] * 24 20 | self.Maxs = [0] * 24 21 | 22 | # Clean up by releasing the UART pins 23 | def close(self): 24 | self.uart.deinit() 25 | 26 | # Send a Pololu command out the serial port 27 | def sendCmd(self, cmd): 28 | self.uart.write(self.PololuCmd) 29 | self.uart.write(cmd) 30 | 31 | # Set channels min and max value range. 32 | def setRange(self, chan, min, max): 33 | self.Mins[chan] = min 34 | self.Maxs[chan] = max 35 | 36 | # Return Minimum channel range value 37 | def getMin(self, chan): 38 | return self.Mins[chan] 39 | 40 | # Return Maximum channel range value 41 | def getMax(self, chan): 42 | return self.Maxs[chan] 43 | 44 | # Set channel to a specified target value in quarter-microseconds 45 | def setTarget(self, chan, target): 46 | if self.Mins[chan] > 0 and target < self.Mins[chan]: 47 | target = self.Mins[chan] 48 | if self.Maxs[chan] > 0 and target > self.Maxs[chan]: 49 | target = self.Maxs[chan] 50 | lsb = target & 0x7f #7 bits for least significant byte 51 | msb = (target >> 7) & 0x7f #shift 7 and take next 7 bits for msb 52 | cmd = b'\x04' + chr(chan) + chr(lsb) + chr(msb) 53 | self.sendCmd(cmd) 54 | self.Targets[chan] = target 55 | 56 | # Set speed of channel measured as quarter-microseconds/10milliseconds 57 | def setSpeed(self, chan, speed): 58 | lsb = speed & 0x7f #7 bits for least significant byte 59 | msb = (speed >> 7) & 0x7f #shift 7 and take next 7 bits for msb 60 | cmd = b'\x07' + chr(chan) + chr(lsb) + chr(msb) 61 | self.sendCmd(cmd) 62 | 63 | # Set acceleration of channel 64 | def setAccel(self, chan, accel): 65 | lsb = accel & 0x7f #7 bits for least significant byte 66 | msb = (accel >> 7) & 0x7f #shift 7 and take next 7 bits for msb 67 | cmd = b'\x09' + chr(chan) + chr(lsb) + chr(msb) 68 | self.sendCmd(cmd) 69 | 70 | # Get the current position of the specified channel 71 | def getPosition(self, chan): 72 | cmd = b'\x10' + chr(chan) 73 | self.sendCmd(cmd) 74 | lsb = ord(self.uart.read(1)) 75 | msb = ord(self.uart.read(1)) 76 | return (msb << 8) + lsb 77 | 78 | # Is servo still moving? 79 | def isMoving(self, chan): 80 | if self.Targets[chan] > 0: 81 | if self.getPosition(chan) != self.Targets[chan]: 82 | return True 83 | return False 84 | 85 | # Have all servo outputs reached their targets? 86 | def getMovingState(self): 87 | cmd = b'\x13' 88 | self.sendCmd(cmd) 89 | if self.uart.read(1) == b'\x00': 90 | return False 91 | else: 92 | return True 93 | 94 | # Run a Maestro Script subroutine. 95 | # Subroutines are numbered sequentially starting with 0. 96 | # Code the subroutine to either REPEAT infinitely or end with QUIT (RETURN is not valid). 97 | def runScriptSub(self, subNumber): 98 | cmd = b'\x27' + chr(subNumber) 99 | # can pass a param with command 0x28 100 | # cmd = b'\x28' + chr(subNumber) + chr(lsb) + chr(msb) 101 | self.sendCmd(cmd) 102 | 103 | # Stop the current Maestro Script 104 | def stopScript(self): 105 | cmd = b'\x24' 106 | self.sendCmd(cmd) -------------------------------------------------------------------------------- /umaestro.md: -------------------------------------------------------------------------------- 1 | umaestro.py 2 | =========== 3 | 4 | This is a customized verion of maestro.py for microcontrollers running MicroPython/CircuitPython. 5 | 6 | The file [umaestro.py](https://raw.githubusercontent.com/FRC4564/Maestro/master/umaestro.py) was built for an Adafruit Feather M4 running CircuitPython 4.1.x. This should work on other Feather models, as is, and should work on many other devices, as long as they have an available UART. It has the same functionality as maestro.py, but the comments have been stripped down to save on micro-controller memory. See [maestro.py](https://raw.githubusercontent.com/FRC4564/Maestro/master/maestro.py) for more complete documentation. The usage of this class is identical, except that the serial parameters are adapted to allow for pin selection and baudrate. Using the defaults works great. 7 | 8 | ## Setup 9 | 10 | The Maestro must be setup for UART mode (rather than USB mode) to work with the Feather (see Serial Settings on the Pololu Maestro Control Center app). I found that "UART detect baud rate" worked well. 11 | 12 | ## Wiring 13 | 14 | To have full functionality, wiring the Feather to the Maestro requires a Level Shifter (like Adafruit's TXB0104) which allows the 3.3v Feather to safely communicate with the Maestro's 5v TTL serial. This isn't a requirement, if you are willing to sacrifice some functionality. 15 | 16 | Both the Feather and the Maestro need power, and there are multiple approaches to this. The approach I used was to power the Feather over USB and then power the Maestro using the Feather's 5v `USB` pin. Note: The Maestro still needed a separate power source for the servo power rails (I used a 4.8v battery pack). 17 | 18 | ![Wiring - Feather, level shifter, and Micro Maestro](https://raw.githubusercontent.com/FRC4564/maestro/master/featherwiring.jpg) 19 | 20 | For full functionality you need bi-directional serial communications. Below are the wiring details and above is a photo showing what this looks like on a breadboard. Note that I was using a Micro Maestro, so the wiring may differ slightly for other Maestro variants. The photo shows the Maestro with a USB cable connected, but that isn't necessary, since power is provided by the Feather, as mentioned previously. 21 | 22 | Bi-Directional Wiring 23 | 24 | Feather Level Micro 25 | M4 Shifter Maestro 26 | ======= ======= ======= 27 | Gnd ----- Gnd ----- Gnd 28 | 3v ----- LV 29 | USB ----- HV ----- +5 30 | RX ----- LV1 31 | TX ----- LV2 32 | HV1 ----- TX 33 | HV2 ----- RX 34 | 35 | If you only need to control servos or run scripts, then there is a much simpler approach to wiring that doesn't require a level shifter. The functions that get positional data `getPosition()`, `getMovingState()`, and `isMoving()` won't work with this approach, but for most use cases, this is fine. 36 | 37 | Uni-Directional Wiring 38 | 39 | Feather Micro 40 | M4 Maestro 41 | ======= ======= 42 | Gnd ----- Gnd 43 | USB ----- +5 44 | TX ----- RX 45 | 46 | ## Sample Usage 47 | 48 | import umaestro 49 | m=umaestro.Controller() 50 | m.setSpeed(0, 10) #Set channel 0 speed to 10 51 | m.setTarget(0,6000) #Set channel 0 to center position 52 | m.startScriptSub(0) #Run the Maestro script subroutine 0 53 | m.close() 54 | 55 | ## Troubleshooting 56 | 57 | - Make sure the Maestro is set to UART mode. If it isn't the maestro won't respond to commands and the red LED will light to indicate a serial communications fault. 58 | - If the range of movement of a servo is too small, adjust the Channel Settings using Pololu Maestro Control Center. 59 | - If the servos don't move at all, be sure you have a power source plugged into the servo rails. The 5v from the Feather USB pin is only powering the logic side of the Maestro, not the servos. 60 | - Make sure CircuitPython is up to date. I tested with version 4.1.2. Version 3.x.x handled UART communications differently and could be problematic. 61 | - This python module was set up for CircuitPython, but may work fine on with MicroPython. Let me know if you bump into any errors. 62 | - It is fine to have the Feather and Maestro both plugged into your PCs USB ports at the same time. The Maestro Control Center will still get servo position information and show it in real-time, while the Feather can continue to send commands to the Maestro. You can also update Maestro script subroutines and then instantly launch them with the Feather. 63 | -------------------------------------------------------------------------------- /maestro.py: -------------------------------------------------------------------------------- 1 | import serial 2 | from sys import version_info 3 | 4 | PY2 = version_info[0] == 2 #Running Python 2.x? 5 | 6 | # 7 | #--------------------------- 8 | # Maestro Servo Controller 9 | #--------------------------- 10 | # 11 | # Support for the Pololu Maestro line of servo controllers 12 | # 13 | # Steven Jacobs -- Aug 2013 14 | # https://github.com/FRC4564/Maestro/ 15 | # 16 | # These functions provide access to many of the Maestro's capabilities using the 17 | # Pololu serial protocol 18 | # 19 | class Controller: 20 | # When connected via USB, the Maestro creates two virtual serial ports 21 | # /dev/ttyACM0 for commands and /dev/ttyACM1 for communications. 22 | # Be sure the Maestro is configured for "USB Dual Port" serial mode. 23 | # "USB Chained Mode" may work as well, but hasn't been tested. 24 | # 25 | # Pololu protocol allows for multiple Maestros to be connected to a single 26 | # serial port. Each connected device is then indexed by number. 27 | # This device number defaults to 0x0C (or 12 in decimal), which this module 28 | # assumes. If two or more controllers are connected to different serial 29 | # ports, or you are using a Windows OS, you can provide the tty port. For 30 | # example, '/dev/ttyACM2' or for Windows, something like 'COM3'. 31 | def __init__(self,ttyStr='/dev/ttyACM0',device=0x0c): 32 | # Open the command port 33 | self.usb = serial.Serial(ttyStr) 34 | # Command lead-in and device number are sent for each Pololu serial command. 35 | self.PololuCmd = chr(0xaa) + chr(device) 36 | # Track target position for each servo. The function isMoving() will 37 | # use the Target vs Current servo position to determine if movement is 38 | # occuring. Upto 24 servos on a Maestro, (0-23). Targets start at 0. 39 | self.Targets = [0] * 24 40 | # Servo minimum and maximum targets can be restricted to protect components. 41 | self.Mins = [0] * 24 42 | self.Maxs = [0] * 24 43 | 44 | # Cleanup by closing USB serial port 45 | def close(self): 46 | self.usb.close() 47 | 48 | # Send a Pololu command out the serial port 49 | def sendCmd(self, cmd): 50 | cmdStr = self.PololuCmd + cmd 51 | if PY2: 52 | self.usb.write(cmdStr) 53 | else: 54 | self.usb.write(bytes(cmdStr,'latin-1')) 55 | 56 | # Set channels min and max value range. Use this as a safety to protect 57 | # from accidentally moving outside known safe parameters. A setting of 0 58 | # allows unrestricted movement. 59 | # 60 | # ***Note that the Maestro itself is configured to limit the range of servo travel 61 | # which has precedence over these values. Use the Maestro Control Center to configure 62 | # ranges that are saved to the controller. Use setRange for software controllable ranges. 63 | def setRange(self, chan, min, max): 64 | self.Mins[chan] = min 65 | self.Maxs[chan] = max 66 | 67 | # Return Minimum channel range value 68 | def getMin(self, chan): 69 | return self.Mins[chan] 70 | 71 | # Return Maximum channel range value 72 | def getMax(self, chan): 73 | return self.Maxs[chan] 74 | 75 | # Set channel to a specified target value. Servo will begin moving based 76 | # on Speed and Acceleration parameters previously set. 77 | # Target values will be constrained within Min and Max range, if set. 78 | # For servos, target represents the pulse width in of quarter-microseconds 79 | # Servo center is at 1500 microseconds, or 6000 quarter-microseconds 80 | # Typcially valid servo range is 3000 to 9000 quarter-microseconds 81 | # If channel is configured for digital output, values < 6000 = Low ouput 82 | def setTarget(self, chan, target): 83 | # if Min is defined and Target is below, force to Min 84 | if self.Mins[chan] > 0 and target < self.Mins[chan]: 85 | target = self.Mins[chan] 86 | # if Max is defined and Target is above, force to Max 87 | if self.Maxs[chan] > 0 and target > self.Maxs[chan]: 88 | target = self.Maxs[chan] 89 | # 90 | lsb = target & 0x7f #7 bits for least significant byte 91 | msb = (target >> 7) & 0x7f #shift 7 and take next 7 bits for msb 92 | cmd = chr(0x04) + chr(chan) + chr(lsb) + chr(msb) 93 | self.sendCmd(cmd) 94 | # Record Target value 95 | self.Targets[chan] = target 96 | 97 | # Set speed of channel 98 | # Speed is measured as 0.25microseconds/10milliseconds 99 | # For the standard 1ms pulse width change to move a servo between extremes, a speed 100 | # of 1 will take 1 minute, and a speed of 60 would take 1 second. 101 | # Speed of 0 is unrestricted. 102 | def setSpeed(self, chan, speed): 103 | lsb = speed & 0x7f #7 bits for least significant byte 104 | msb = (speed >> 7) & 0x7f #shift 7 and take next 7 bits for msb 105 | cmd = chr(0x07) + chr(chan) + chr(lsb) + chr(msb) 106 | self.sendCmd(cmd) 107 | 108 | # Set acceleration of channel 109 | # This provide soft starts and finishes when servo moves to target position. 110 | # Valid values are from 0 to 255. 0=unrestricted, 1 is slowest start. 111 | # A value of 1 will take the servo about 3s to move between 1ms to 2ms range. 112 | def setAccel(self, chan, accel): 113 | lsb = accel & 0x7f #7 bits for least significant byte 114 | msb = (accel >> 7) & 0x7f #shift 7 and take next 7 bits for msb 115 | cmd = chr(0x09) + chr(chan) + chr(lsb) + chr(msb) 116 | self.sendCmd(cmd) 117 | 118 | # Get the current position of the device on the specified channel 119 | # The result is returned in a measure of quarter-microseconds, which mirrors 120 | # the Target parameter of setTarget. 121 | # This is not reading the true servo position, but the last target position sent 122 | # to the servo. If the Speed is set to below the top speed of the servo, then 123 | # the position result will align well with the acutal servo position, assuming 124 | # it is not stalled or slowed. 125 | def getPosition(self, chan): 126 | cmd = chr(0x10) + chr(chan) 127 | self.sendCmd(cmd) 128 | lsb = ord(self.usb.read()) 129 | msb = ord(self.usb.read()) 130 | return (msb << 8) + lsb 131 | 132 | # Test to see if a servo has reached the set target position. This only provides 133 | # useful results if the Speed parameter is set slower than the maximum speed of 134 | # the servo. Servo range must be defined first using setRange. See setRange comment. 135 | # 136 | # ***Note if target position goes outside of Maestro's allowable range for the 137 | # channel, then the target can never be reached, so it will appear to always be 138 | # moving to the target. 139 | def isMoving(self, chan): 140 | if self.Targets[chan] > 0: 141 | if self.getPosition(chan) != self.Targets[chan]: 142 | return True 143 | return False 144 | 145 | # Have all servo outputs reached their targets? This is useful only if Speed and/or 146 | # Acceleration have been set on one or more of the channels. Returns True or False. 147 | # Not available with Micro Maestro. 148 | def getMovingState(self): 149 | cmd = chr(0x13) 150 | self.sendCmd(cmd) 151 | if self.usb.read() == chr(0): 152 | return False 153 | else: 154 | return True 155 | 156 | # Run a Maestro Script subroutine in the currently active script. Scripts can 157 | # have multiple subroutines, which get numbered sequentially from 0 on up. Code your 158 | # Maestro subroutine to either infinitely loop, or just end (return is not valid). 159 | def runScriptSub(self, subNumber): 160 | cmd = chr(0x27) + chr(subNumber) 161 | # can pass a param with command 0x28 162 | # cmd = chr(0x28) + chr(subNumber) + chr(lsb) + chr(msb) 163 | self.sendCmd(cmd) 164 | 165 | # Stop the current Maestro Script 166 | def stopScript(self): 167 | cmd = chr(0x24) 168 | self.sendCmd(cmd) 169 | 170 | --------------------------------------------------------------------------------