├── Raspi_MotorHAT ├── __init__.py ├── DCTest.py ├── DualStepperTest.py ├── Raspi_PWM_Servo_Driver.py ├── Raspi_I2C.py └── Raspi_MotorHAT.py ├── .travis.yml ├── setup.py ├── .github └── workflows │ └── pythonapp.yml ├── demos ├── ServoTest.py ├── StepperTest.py └── StackingTest.py ├── LICENSE ├── .gitignore └── README.md /Raspi_MotorHAT/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .Raspi_MotorHAT import * 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | # The Raspberry Pi versions 3 | python: 4 | - "2.7" 5 | - "3.5" 6 | install: 7 | - pip install -r pep8 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="Raspi_MotorHAT", 8 | version="0.0.2", 9 | author="Unknown author", 10 | author_email="Unknown", 11 | description="Code to interface with the Raspi_MotorHAT board.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/orionrobots/Raspi_MotorHAT", 15 | packages=setuptools.find_packages(), 16 | classifiers=( 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: Raspbian", 20 | ), 21 | ) 22 | -------------------------------------------------------------------------------- /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | name: Python application 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 3.7 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.7 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | # pip install -r requirements.txt 20 | - name: Lint with flake8 21 | run: | 22 | pip install flake8 23 | # stop the build if there are Python syntax errors or undefined names 24 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 25 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 26 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 27 | # - name: Test with pytest 28 | # run: | 29 | # pip install pytest 30 | # pytest 31 | -------------------------------------------------------------------------------- /demos/ServoTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function, absolute_import 3 | from Raspi_MotorHAT.Raspi_PWM_Servo_Driver import PWM 4 | import time 5 | 6 | # =========================================================================== 7 | # Example Code 8 | # =========================================================================== 9 | 10 | # Initialise the PWM device using the default address 11 | # bmp = PWM(0x40, debug=True) 12 | pwm = PWM(0x6F) 13 | 14 | servoMin = 150 # Min pulse length out of 4096 15 | servoMax = 600 # Max pulse length out of 4096 16 | 17 | def setServoPulse(channel, pulse): 18 | pulseLength = 1000000 # 1,000,000 us per second 19 | pulseLength //= 60 # 60 Hz 20 | print("%d us per period" % pulseLength) 21 | pulseLength //= 4096 # 12 bits of resolution 22 | print("%d us per bit" % pulseLength) 23 | pulse *= 1000 24 | pulse //= pulseLength 25 | pwm.setPWM(channel, 0, pulse) 26 | 27 | pwm.setPWMFreq(60) # Set frequency to 60 Hz 28 | while (True): 29 | # Change speed of continuous servo on channel O 30 | pwm.setPWM(0, 0, servoMin) 31 | time.sleep(1) 32 | pwm.setPWM(0, 0, servoMax) 33 | time.sleep(1) 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /demos/StepperTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from Raspi_MotorHAT import Raspi_MotorHAT 3 | 4 | import atexit 5 | 6 | # create a default object, no changes to I2C address or frequency 7 | mh = Raspi_MotorHAT(0x6F) 8 | 9 | # recommended for auto-disabling motors on shutdown! 10 | def turnOffMotors(): 11 | mh.getMotor(1).run(Raspi_MotorHAT.RELEASE) 12 | mh.getMotor(2).run(Raspi_MotorHAT.RELEASE) 13 | mh.getMotor(3).run(Raspi_MotorHAT.RELEASE) 14 | mh.getMotor(4).run(Raspi_MotorHAT.RELEASE) 15 | 16 | atexit.register(turnOffMotors) 17 | 18 | myStepper = mh.getStepper(200, 1) # 200 steps/rev, motor port #1 19 | myStepper.setSpeed(30) # 30 RPM 20 | 21 | while (True): 22 | print("Single coil steps") 23 | myStepper.step(100, Raspi_MotorHAT.FORWARD, Raspi_MotorHAT.SINGLE) 24 | myStepper.step(100, Raspi_MotorHAT.BACKWARD, Raspi_MotorHAT.SINGLE) 25 | 26 | print("Double coil steps") 27 | myStepper.step(100, Raspi_MotorHAT.FORWARD, Raspi_MotorHAT.DOUBLE) 28 | myStepper.step(100, Raspi_MotorHAT.BACKWARD, Raspi_MotorHAT.DOUBLE) 29 | 30 | print("Interleaved coil steps") 31 | myStepper.step(100, Raspi_MotorHAT.FORWARD, Raspi_MotorHAT.INTERLEAVE) 32 | myStepper.step(100, Raspi_MotorHAT.BACKWARD, Raspi_MotorHAT.INTERLEAVE) 33 | 34 | print("Microsteps") 35 | myStepper.step(100, Raspi_MotorHAT.FORWARD, Raspi_MotorHAT.MICROSTEP) 36 | myStepper.step(100, Raspi_MotorHAT.BACKWARD, Raspi_MotorHAT.MICROSTEP) 37 | -------------------------------------------------------------------------------- /Raspi_MotorHAT/DCTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function, absolute_import 3 | from .Raspi_MotorHAT import Raspi_MotorHAT 4 | 5 | import time 6 | import atexit 7 | 8 | # create a default object, no changes to I2C address or frequency 9 | mh = Raspi_MotorHAT(addr=0x6f) 10 | 11 | # recommended for auto-disabling motors on shutdown! 12 | def turnOffMotors(): 13 | mh.getMotor(1).run(Raspi_MotorHAT.RELEASE) 14 | mh.getMotor(2).run(Raspi_MotorHAT.RELEASE) 15 | mh.getMotor(3).run(Raspi_MotorHAT.RELEASE) 16 | mh.getMotor(4).run(Raspi_MotorHAT.RELEASE) 17 | 18 | atexit.register(turnOffMotors) 19 | 20 | ################################# DC motor test! 21 | myMotor = mh.getMotor(3) 22 | 23 | # set the speed to start, from 0 (off) to 255 (max speed) 24 | myMotor.setSpeed(150) 25 | myMotor.run(Raspi_MotorHAT.FORWARD) 26 | # turn on motor 27 | myMotor.run(Raspi_MotorHAT.RELEASE) 28 | 29 | 30 | while (True): 31 | print("Forward! ") 32 | myMotor.run(Raspi_MotorHAT.FORWARD) 33 | 34 | print("\tSpeed up...") 35 | for i in range(255): 36 | myMotor.setSpeed(i) 37 | time.sleep(0.01) 38 | 39 | print("\tSlow down...") 40 | for i in reversed(range(255)): 41 | myMotor.setSpeed(i) 42 | time.sleep(0.01) 43 | 44 | print("Backward! ") 45 | myMotor.run(Raspi_MotorHAT.BACKWARD) 46 | 47 | print("\tSpeed up...") 48 | for i in range(255): 49 | myMotor.setSpeed(i) 50 | time.sleep(0.01) 51 | 52 | print("\tSlow down...") 53 | for i in reversed(range(255)): 54 | myMotor.setSpeed(i) 55 | time.sleep(0.01) 56 | 57 | print("Release") 58 | myMotor.run(Raspi_MotorHAT.RELEASE) 59 | time.sleep(1.0) 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | 23 | --- 24 | Derived from: 25 | 26 | Adafruit Python Library for DC + Stepper Motor HAT 27 | > Python library for interfacing with the Adafruit Motor HAT for Raspberry Pi to control DC motors with speed control and Stepper motors with single, double, interleave and microstepping. 28 | > 29 | > Designed specifically to work with the Adafruit Motor Hat 30 | > 31 | > ----> https://www.adafruit.com/product/2348 32 | > 33 | > Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! 34 | > 35 | > Written by Limor Fried for Adafruit Industries. MIT license, all text above must be included in any redistribution 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | # Vscode and virtualenvs 106 | .vscode 107 | .venv/ 108 | .venv3 109 | -------------------------------------------------------------------------------- /Raspi_MotorHAT/DualStepperTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import absolute_import 3 | from .Raspi_MotorHAT import Raspi_MotorHAT 4 | import atexit 5 | import threading 6 | import random 7 | 8 | # create a default object, no changes to I2C address or frequency 9 | mh = Raspi_MotorHAT(0x6F) 10 | 11 | # create empty threads (these will hold the stepper 1 and 2 threads) 12 | st1 = threading.Thread() 13 | st2 = threading.Thread() 14 | 15 | 16 | # recommended for auto-disabling motors on shutdown! 17 | def turnOffMotors(): 18 | mh.getMotor(1).run(Raspi_MotorHAT.RELEASE) 19 | mh.getMotor(2).run(Raspi_MotorHAT.RELEASE) 20 | mh.getMotor(3).run(Raspi_MotorHAT.RELEASE) 21 | mh.getMotor(4).run(Raspi_MotorHAT.RELEASE) 22 | 23 | atexit.register(turnOffMotors) 24 | 25 | myStepper1 = mh.getStepper(200, 1) # 200 steps/rev, motor port #1 26 | myStepper2 = mh.getStepper(200, 2) # 200 steps/rev, motor port #1 27 | myStepper1.setSpeed(60) # 30 RPM 28 | myStepper2.setSpeed(60) # 30 RPM 29 | 30 | 31 | stepstyles = [Raspi_MotorHAT.SINGLE, Raspi_MotorHAT.DOUBLE, Raspi_MotorHAT.INTERLEAVE, Raspi_MotorHAT.MICROSTEP] 32 | 33 | def stepper_worker(stepper, numsteps, direction, style): 34 | #print("Steppin!") 35 | stepper.step(numsteps, direction, style) 36 | #print("Done") 37 | 38 | while (True): 39 | if not st1.isAlive(): 40 | randomdir = random.randint(0, 1) 41 | print("Stepper 1"), 42 | if (randomdir == 0): 43 | dir = Raspi_MotorHAT.FORWARD 44 | print("forward"), 45 | else: 46 | dir = Raspi_MotorHAT.BACKWARD 47 | print("backward"), 48 | randomsteps = random.randint(10,50) 49 | print("%d steps" % randomsteps) 50 | st1 = threading.Thread(target=stepper_worker, args=(myStepper1, randomsteps, dir, stepstyles[random.randint(0,3)],)) 51 | st1.start() 52 | 53 | if not st2.isAlive(): 54 | print("Stepper 2"), 55 | randomdir = random.randint(0, 1) 56 | if (randomdir == 0): 57 | dir = Raspi_MotorHAT.FORWARD 58 | print("forward"), 59 | else: 60 | dir = Raspi_MotorHAT.BACKWARD 61 | print("backward"), 62 | 63 | randomsteps = random.randint(10,50) 64 | print("%d steps" % randomsteps) 65 | 66 | st2 = threading.Thread(target=stepper_worker, args=(myStepper2, randomsteps, dir, stepstyles[random.randint(0,3)],)) 67 | st2.start() 68 | -------------------------------------------------------------------------------- /demos/StackingTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function 3 | from Raspi_MotorHAT import Raspi_MotorHAT 4 | import atexit 5 | import threading 6 | import random 7 | 8 | # bottom hat is default address 0x6f 9 | bottomhat = Raspi_MotorHAT(addr=0x6f) 10 | # top hat has A0 jumper closed, so its address 0x61 11 | tophat = Raspi_MotorHAT(addr=0x61) 12 | 13 | # create empty threads (these will hold the stepper 1, 2 & 3 threads) 14 | stepperThreads = [threading.Thread(), threading.Thread(), threading.Thread()] 15 | 16 | # recommended for auto-disabling motors on shutdown! 17 | def turnOffMotors(): 18 | tophat.getMotor(1).run(Raspi_MotorHAT.RELEASE) 19 | tophat.getMotor(2).run(Raspi_MotorHAT.RELEASE) 20 | tophat.getMotor(3).run(Raspi_MotorHAT.RELEASE) 21 | tophat.getMotor(4).run(Raspi_MotorHAT.RELEASE) 22 | bottomhat.getMotor(1).run(Raspi_MotorHAT.RELEASE) 23 | bottomhat.getMotor(2).run(Raspi_MotorHAT.RELEASE) 24 | bottomhat.getMotor(3).run(Raspi_MotorHAT.RELEASE) 25 | bottomhat.getMotor(4).run(Raspi_MotorHAT.RELEASE) 26 | 27 | atexit.register(turnOffMotors) 28 | 29 | myStepper1 = bottomhat.getStepper(200, 1) # 200 steps/rev, motor port #1 30 | myStepper2 = bottomhat.getStepper(200, 2) # 200 steps/rev, motor port #2 31 | myStepper3 = tophat.getStepper(200, 1) # 200 steps/rev, motor port #1 32 | 33 | myStepper1.setSpeed(60) # 60 RPM 34 | myStepper2.setSpeed(30) # 30 RPM 35 | myStepper3.setSpeed(15) # 15 RPM 36 | 37 | # get a DC motor! 38 | myMotor = tophat.getMotor(3) 39 | # set the speed to start, from 0 (off) to 255 (max speed) 40 | myMotor.setSpeed(150) 41 | # turn on motor 42 | myMotor.run(Raspi_MotorHAT.FORWARD) 43 | 44 | 45 | stepstyles = [Raspi_MotorHAT.SINGLE, Raspi_MotorHAT.DOUBLE, Raspi_MotorHAT.INTERLEAVE] 46 | steppers = [myStepper1, myStepper2, myStepper3] 47 | 48 | def stepper_worker(stepper, numsteps, direction, style): 49 | #print("Steppin!") 50 | stepper.step(numsteps, direction, style) 51 | #print("Done") 52 | 53 | while (True): 54 | for i in range(3): 55 | if not stepperThreads[i].isAlive(): 56 | randomdir = random.randint(0, 1) 57 | print("Stepper %d" % i) 58 | if (randomdir == 0): 59 | dir = Raspi_MotorHAT.FORWARD 60 | print("forward") 61 | else: 62 | dir = Raspi_MotorHAT.BACKWARD 63 | print("backward") 64 | randomsteps = random.randint(10,50) 65 | print("%d steps" % randomsteps) 66 | stepperThreads[i] = threading.Thread(target=stepper_worker, args=(steppers[i], randomsteps, dir, stepstyles[random.randint(0,len(stepstyles)-1)],)) 67 | stepperThreads[i].start() 68 | 69 | # also, lets switch around the DC motor! 70 | myMotor.setSpeed(random.randint(0,255)) # random speed 71 | #myMotor.run(random.randint(0,1)) # random forward/back 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspi_MotorHAT 2 | 3 | My enhancements on the MotorHAT Code. 4 | 5 | ## MotorHAT 6 | 7 | The MotorHAT - also known as "Full Function Stepper Controller" is a Raspberry Pi hat that seems to be commonly available on the internet. The device uses the PCA9685 16 channel servo or I2C to PWM driver chip. 8 | 9 | The hat is a very handy one, with a lot of the functions of the AdaFruit stepper hat: 10 | 11 | * Control 4 DC motors or 2 Stepper motors. 12 | * Control 4 servo motors. 13 | * Cutouts for access to CSI camera interface and DSI display interface. 14 | * Motor power inputs. 15 | 16 | But with some different features: 17 | 18 | * I2c breakout. 19 | * IR input. 20 | * GPIO through connector - you can stack these, just watch the i2C address. 21 | * Fully assembled - no soldering needed. 22 | * Does NOT come with the handy prototyping area on the Adafruit HAT. 23 | 24 | ## Using this library 25 | 26 | Prerequisites: 27 | 28 | sudo apt-get install python3-smbus i2c-tools 29 | 30 | Install the library on the Raspberry Pi with: 31 | 32 | pip install git+https://github.com/orionrobots/Raspi_MotorHAT 33 | 34 | To use a DC motor connected to M1: 35 | 36 | from Raspi_MotorHAT import Raspi_MotorHAT 37 | mh = Raspi_MotorHAT(addr=0x6f) 38 | motor = mh.getMotor(1) 39 | 40 | To set its speed: 41 | 42 | motor.setSpeed(150) 43 | 44 | To set it's direction: 45 | 46 | motor.run(Raspi_MotorHAT.FORWARD) 47 | 48 | Directions are FORWARD, BACKWARD and RELEASE. Speed is a positive integer and varies from 0 to 255. 49 | 50 | Using a servo motor connected to channel 1, see the example under https://github.com/orionrobots/Raspi_MotorHAT/blob/master/Raspi_MotorHAT/ServoTest.py. 51 | 52 | ## Why it exists and accrediting Adafruit 53 | 54 | Finding sample code, a library and documentation has proven to be tricky. I am summarising and enhancing what I've found here. I claim no personal license or ownership. The original code was found at https://sourceforge.net/projects/u-geek/files/HATs/Raspi_MotorHAT/. 55 | 56 | The code appears to be derivative from the Adafruit Stepper hat code, with some changes in naming and I2C addresses, so I've also made this an MIT licensed repo. Here is their license in full: 57 | 58 | > Adafruit Python Library for DC + Stepper Motor HAT 59 | > Python library for interfacing with the Adafruit Motor HAT for Raspberry Pi to control DC motors with speed control and Stepper motors with single, double, interleave and microstepping. 60 | > 61 | > Designed specifically to work with the Adafruit Motor Hat 62 | > 63 | > ----> https://www.adafruit.com/product/2348 64 | > 65 | > Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! 66 | > 67 | > Written by Limor Fried for Adafruit Industries. MIT license, all text above must be included in any redistribution 68 | > 69 | 70 | ## What I am adding 71 | 72 | * Making the code browsable, not just a tarball somewhere. 73 | * Adding python package setup files - you can install this. 74 | 75 | ## Roadmap 76 | 77 | * I plan to try make it Py2/3 Polyglot. This code is Python 2 only. 78 | -------------------------------------------------------------------------------- /Raspi_MotorHAT/Raspi_PWM_Servo_Driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function, absolute_import 3 | import time 4 | import math 5 | from .Raspi_I2C import Raspi_I2C 6 | 7 | # ============================================================================ 8 | # Raspi PCA9685 16-Channel PWM Servo Driver 9 | # ============================================================================ 10 | 11 | class PWM : 12 | # Registers/etc. 13 | __MODE1 = 0x00 14 | __MODE2 = 0x01 15 | __SUBADR1 = 0x02 16 | __SUBADR2 = 0x03 17 | __SUBADR3 = 0x04 18 | __PRESCALE = 0xFE 19 | __LED0_ON_L = 0x06 20 | __LED0_ON_H = 0x07 21 | __LED0_OFF_L = 0x08 22 | __LED0_OFF_H = 0x09 23 | __ALL_LED_ON_L = 0xFA 24 | __ALL_LED_ON_H = 0xFB 25 | __ALL_LED_OFF_L = 0xFC 26 | __ALL_LED_OFF_H = 0xFD 27 | 28 | # Bits 29 | __RESTART = 0x80 30 | __SLEEP = 0x10 31 | __ALLCALL = 0x01 32 | __INVRT = 0x10 33 | __OUTDRV = 0x04 34 | 35 | general_call_i2c = Raspi_I2C(0x00) 36 | 37 | @classmethod 38 | def softwareReset(cls): 39 | "Sends a software reset (SWRST) command to all the servo drivers on the bus" 40 | cls.general_call_i2c.writeRaw8(0x06) # SWRST 41 | 42 | def __init__(self, address=0x40, debug=False): 43 | self.i2c = Raspi_I2C(address) 44 | self.i2c.debug = debug 45 | self.address = address 46 | self.debug = debug 47 | if (self.debug): 48 | print("Reseting PCA9685 MODE1 (without SLEEP) and MODE2") 49 | self.setAllPWM(0, 0) 50 | self.i2c.write8(self.__MODE2, self.__OUTDRV) 51 | self.i2c.write8(self.__MODE1, self.__ALLCALL) 52 | time.sleep(0.005) # wait for oscillator 53 | 54 | mode1 = self.i2c.readU8(self.__MODE1) 55 | mode1 = mode1 & ~self.__SLEEP # wake up (reset sleep) 56 | self.i2c.write8(self.__MODE1, mode1) 57 | time.sleep(0.005) # wait for oscillator 58 | 59 | def setPWMFreq(self, freq): 60 | "Sets the PWM frequency" 61 | prescaleval = 25000000.0 # 25MHz 62 | prescaleval /= 4096.0 # 12-bit 63 | prescaleval /= float(freq) 64 | prescaleval -= 1.0 65 | if (self.debug): 66 | print("Setting PWM frequency to %d Hz" % freq) 67 | print("Estimated pre-scale: %d" % prescaleval) 68 | prescale = math.floor(prescaleval + 0.5) 69 | if (self.debug): 70 | print("Final pre-scale: %d" % prescale) 71 | 72 | oldmode = self.i2c.readU8(self.__MODE1) 73 | newmode = (oldmode & 0x7F) | 0x10 # sleep 74 | self.i2c.write8(self.__MODE1, newmode) # go to sleep 75 | self.i2c.write8(self.__PRESCALE, int(math.floor(prescale))) 76 | self.i2c.write8(self.__MODE1, oldmode) 77 | time.sleep(0.005) 78 | self.i2c.write8(self.__MODE1, oldmode | 0x80) 79 | 80 | def setPWM(self, channel, on, off): 81 | "Sets a single PWM channel" 82 | self.i2c.write8(self.__LED0_ON_L+4*channel, on & 0xFF) 83 | self.i2c.write8(self.__LED0_ON_H+4*channel, on >> 8) 84 | self.i2c.write8(self.__LED0_OFF_L+4*channel, off & 0xFF) 85 | self.i2c.write8(self.__LED0_OFF_H+4*channel, off >> 8) 86 | 87 | def setAllPWM(self, on, off): 88 | "Sets a all PWM channels" 89 | self.i2c.write8(self.__ALL_LED_ON_L, on & 0xFF) 90 | self.i2c.write8(self.__ALL_LED_ON_H, on >> 8) 91 | self.i2c.write8(self.__ALL_LED_OFF_L, off & 0xFF) 92 | self.i2c.write8(self.__ALL_LED_OFF_H, off >> 8) 93 | -------------------------------------------------------------------------------- /Raspi_MotorHAT/Raspi_I2C.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function 3 | import re 4 | import smbus 5 | 6 | # =========================================================================== 7 | # Raspi_I2C Class 8 | # =========================================================================== 9 | 10 | class Raspi_I2C(object): 11 | 12 | @staticmethod 13 | def getPiRevision(): 14 | """Gets the version number of the Raspberry Pi board""" 15 | # Revision list available at: http://elinux.org/RPi_HardwareHistory#Board_Revision_History 16 | try: 17 | with open('/proc/cpuinfo', 'r') as infile: 18 | for line in infile: 19 | # Match a line of the form "Revision : 0002" while ignoring extra 20 | # info in front of the revsion (like 1000 when the Pi was over-volted). 21 | match = re.match(r'Revision\s+:\s+.*(\w{4})$', line) 22 | if match and match.group(1) in ['0000', '0002', '0003']: 23 | # Return revision 1 if revision ends with 0000, 0002 or 0003. 24 | return 1 25 | elif match: 26 | # Assume revision 2 if revision ends with any other 4 chars. 27 | return 2 28 | # Couldn't find the revision, assume revision 0 like older code for compatibility. 29 | return 0 30 | except: 31 | return 0 32 | 33 | @staticmethod 34 | def getPiI2CBusNumber(): 35 | # Gets the I2C bus number /dev/i2c# 36 | return 1 if Raspi_I2C.getPiRevision() > 1 else 0 37 | 38 | def __init__(self, address, busnum=-1, debug=False): 39 | self.address = address 40 | # By default, the correct I2C bus is auto-detected using /proc/cpuinfo 41 | # Alternatively, you can hard-code the bus version below: 42 | # self.bus = smbus.SMBus(0) # Force I2C0 (early 256MB Pi's) 43 | # self.bus = smbus.SMBus(1) # Force I2C1 (512MB Pi's) 44 | self.bus = smbus.SMBus(busnum if busnum >= 0 else Raspi_I2C.getPiI2CBusNumber()) 45 | self.debug = debug 46 | 47 | def reverseByteOrder(self, data): 48 | "Reverses the byte order of an int (16-bit) or long (32-bit) value" 49 | # Courtesy Vishal Sapre 50 | byteCount = len(hex(data)[2:].replace('L','')[::2]) 51 | val = 0 52 | for _ in range(byteCount): 53 | val = (val << 8) | (data & 0xff) 54 | data >>= 8 55 | return val 56 | 57 | def errMsg(self): 58 | print("Error accessing 0x%02X: Check your I2C address" % self.address) 59 | return -1 60 | 61 | def write8(self, reg, value): 62 | "Writes an 8-bit value to the specified register/address" 63 | try: 64 | self.bus.write_byte_data(self.address, reg, value) 65 | if self.debug: 66 | print("I2C: Wrote 0x%02X to register 0x%02X" % (value, reg)) 67 | except IOError: 68 | return self.errMsg() 69 | 70 | def write16(self, reg, value): 71 | "Writes a 16-bit value to the specified register/address pair" 72 | try: 73 | self.bus.write_word_data(self.address, reg, value) 74 | if self.debug: 75 | print("I2C: Wrote 0x%02X to register pair 0x%02X,0x%02X" % 76 | (value, reg, reg+1)) 77 | except IOError: 78 | return self.errMsg() 79 | 80 | def writeRaw8(self, value): 81 | "Writes an 8-bit value on the bus" 82 | try: 83 | self.bus.write_byte(self.address, value) 84 | if self.debug: 85 | print("I2C: Wrote 0x%02X" % value) 86 | except IOError: 87 | return self.errMsg() 88 | 89 | def writeList(self, reg, list): 90 | "Writes an array of bytes using I2C format" 91 | try: 92 | if self.debug: 93 | print("I2C: Writing list to register 0x%02X:" % reg) 94 | print(list) 95 | self.bus.write_i2c_block_data(self.address, reg, list) 96 | except IOError: 97 | return self.errMsg() 98 | 99 | def readList(self, reg, length): 100 | "Read a list of bytes from the I2C device" 101 | try: 102 | results = self.bus.read_i2c_block_data(self.address, reg, length) 103 | if self.debug: 104 | print("I2C: Device 0x%02X returned the following from reg 0x%02X" % 105 | (self.address, reg)) 106 | print(results) 107 | return results 108 | except IOError: 109 | return self.errMsg() 110 | 111 | def readU8(self, reg): 112 | "Read an unsigned byte from the I2C device" 113 | try: 114 | result = self.bus.read_byte_data(self.address, reg) 115 | if self.debug: 116 | print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % 117 | (self.address, result & 0xFF, reg)) 118 | return result 119 | except IOError: 120 | return self.errMsg() 121 | 122 | def readS8(self, reg): 123 | "Reads a signed byte from the I2C device" 124 | try: 125 | result = self.bus.read_byte_data(self.address, reg) 126 | if result > 127: result -= 256 127 | if self.debug: 128 | print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % 129 | (self.address, result & 0xFF, reg)) 130 | return result 131 | except IOError: 132 | return self.errMsg() 133 | 134 | def readU16(self, reg, little_endian=True): 135 | "Reads an unsigned 16-bit value from the I2C device" 136 | try: 137 | result = self.bus.read_word_data(self.address,reg) 138 | # Swap bytes if using big endian because read_word_data assumes little 139 | # endian on ARM (little endian) systems. 140 | if not little_endian: 141 | result = ((result << 8) & 0xFF00) + (result >> 8) 142 | if (self.debug): 143 | print("I2C: Device 0x%02X returned 0x%04X from reg 0x%02X" % (self.address, result & 0xFFFF, reg)) 144 | return result 145 | except IOError: 146 | return self.errMsg() 147 | 148 | def readS16(self, reg, little_endian=True): 149 | "Reads a signed 16-bit value from the I2C device" 150 | try: 151 | result = self.readU16(reg,little_endian) 152 | if result > 32767: result -= 65536 153 | return result 154 | except IOError: 155 | return self.errMsg() 156 | 157 | if __name__ == '__main__': 158 | try: 159 | bus = Raspi_I2C(address=0) 160 | print("Default I2C bus is accessible") 161 | except: 162 | print("Error accessing default I2C bus") 163 | -------------------------------------------------------------------------------- /Raspi_MotorHAT/Raspi_MotorHAT.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function,absolute_import 3 | from .Raspi_PWM_Servo_Driver import PWM 4 | import time 5 | 6 | class Raspi_StepperMotor: 7 | MICROSTEPS = 8 8 | HALF_MICROSTEPS = 4 9 | MICROSTEP_CURVE = [0, 50, 98, 142, 180, 212, 236, 250, 255] 10 | 11 | #MICROSTEPS = 16 12 | # a sinusoidal curve NOT LINEAR! 13 | #MICROSTEP_CURVE = [0, 25, 50, 74, 98, 120, 141, 162, 180, 197, 212, 225, 236, 244, 250, 253, 255] 14 | 15 | def __init__(self, controller, num, steps=200): 16 | self.MC = controller 17 | self.revsteps = steps 18 | self.motornum = num 19 | self.sec_per_step = 0.1 20 | self.steppingcounter = 0 21 | self.currentstep = 0 22 | 23 | num -= 1 24 | 25 | if (num == 0): 26 | self.PWMA = 8 27 | self.AIN2 = 9 28 | self.AIN1 = 10 29 | self.PWMB = 13 30 | self.BIN2 = 12 31 | self.BIN1 = 11 32 | elif (num == 1): 33 | self.PWMA = 2 34 | self.AIN2 = 3 35 | self.AIN1 = 4 36 | self.PWMB = 7 37 | self.BIN2 = 6 38 | self.BIN1 = 5 39 | else: 40 | raise NameError('MotorHAT Stepper must be between 1 and 2 inclusive') 41 | 42 | def setSpeed(self, rpm): 43 | self.sec_per_step = 60.0 / (self.revsteps * rpm) 44 | self.steppingcounter = 0 45 | 46 | def oneStep(self, dir, style): 47 | pwm_a = pwm_b = 255 48 | 49 | # first determine what sort of stepping procedure we're up to 50 | if (style == Raspi_MotorHAT.SINGLE): 51 | if ((self.currentstep // self.HALF_MICROSTEPS) % 2): 52 | # we're at an odd step, weird 53 | if (dir == Raspi_MotorHAT.FORWARD): 54 | self.currentstep += self.HALF_MICROSTEPS 55 | else: 56 | self.currentstep -= self.HALF_MICROSTEPS 57 | else: 58 | # go to next even step 59 | if (dir == Raspi_MotorHAT.FORWARD): 60 | self.currentstep += self.MICROSTEPS 61 | else: 62 | self.currentstep -= self.MICROSTEPS 63 | if (style == Raspi_MotorHAT.DOUBLE): 64 | if not ((self.currentstep // self.HALF_MICROSTEPS) % 2): 65 | # we're at an even step, weird 66 | if (dir == Raspi_MotorHAT.FORWARD): 67 | self.currentstep += self.HALF_MICROSTEPS 68 | else: 69 | self.currentstep -= self.HALF_MICROSTEPS 70 | else: 71 | # go to next odd step 72 | if (dir == Raspi_MotorHAT.FORWARD): 73 | self.currentstep += self.MICROSTEPS 74 | else: 75 | self.currentstep -= self.MICROSTEPS 76 | if (style == Raspi_MotorHAT.INTERLEAVE): 77 | if (dir == Raspi_MotorHAT.FORWARD): 78 | self.currentstep += self.HALF_MICROSTEPS 79 | else: 80 | self.currentstep -= self.HALF_MICROSTEPS 81 | 82 | if (style == Raspi_MotorHAT.MICROSTEP): 83 | if (dir == Raspi_MotorHAT.FORWARD): 84 | self.currentstep += 1 85 | else: 86 | self.currentstep -= 1 87 | 88 | # go to next 'step' and wrap around 89 | self.currentstep += self.MICROSTEPS * 4 90 | self.currentstep %= self.MICROSTEPS * 4 91 | 92 | pwm_a = pwm_b = 0 93 | if (self.currentstep >= 0) and (self.currentstep < self.MICROSTEPS): 94 | pwm_a = self.MICROSTEP_CURVE[self.MICROSTEPS - self.currentstep] 95 | pwm_b = self.MICROSTEP_CURVE[self.currentstep] 96 | elif (self.currentstep >= self.MICROSTEPS) and (self.currentstep < self.MICROSTEPS*2): 97 | pwm_a = self.MICROSTEP_CURVE[self.currentstep - self.MICROSTEPS] 98 | pwm_b = self.MICROSTEP_CURVE[self.MICROSTEPS*2 - self.currentstep] 99 | elif (self.currentstep >= self.MICROSTEPS*2) and (self.currentstep < self.MICROSTEPS*3): 100 | pwm_a = self.MICROSTEP_CURVE[self.MICROSTEPS*3 - self.currentstep] 101 | pwm_b = self.MICROSTEP_CURVE[self.currentstep - self.MICROSTEPS*2] 102 | elif (self.currentstep >= self.MICROSTEPS*3) and (self.currentstep < self.MICROSTEPS*4): 103 | pwm_a = self.MICROSTEP_CURVE[self.currentstep - self.MICROSTEPS*3] 104 | pwm_b = self.MICROSTEP_CURVE[self.MICROSTEPS*4 - self.currentstep] 105 | 106 | 107 | # go to next 'step' and wrap around 108 | self.currentstep += self.MICROSTEPS * 4 109 | self.currentstep %= self.MICROSTEPS * 4 110 | 111 | # only really used for microstepping, otherwise always on! 112 | self.MC._pwm.setPWM(self.PWMA, 0, pwm_a*16) 113 | self.MC._pwm.setPWM(self.PWMB, 0, pwm_b*16) 114 | 115 | # set up coil energizing! 116 | coils = [0, 0, 0, 0] 117 | 118 | if (style == Raspi_MotorHAT.MICROSTEP): 119 | if (self.currentstep >= 0) and (self.currentstep < self.MICROSTEPS): 120 | coils = [1, 1, 0, 0] 121 | elif (self.currentstep >= self.MICROSTEPS) and (self.currentstep < self.MICROSTEPS*2): 122 | coils = [0, 1, 1, 0] 123 | elif (self.currentstep >= self.MICROSTEPS*2) and (self.currentstep < self.MICROSTEPS*3): 124 | coils = [0, 0, 1, 1] 125 | elif (self.currentstep >= self.MICROSTEPS*3) and (self.currentstep < self.MICROSTEPS*4): 126 | coils = [1, 0, 0, 1] 127 | else: 128 | step2coils = [ [1, 0, 0, 0], 129 | [1, 1, 0, 0], 130 | [0, 1, 0, 0], 131 | [0, 1, 1, 0], 132 | [0, 0, 1, 0], 133 | [0, 0, 1, 1], 134 | [0, 0, 0, 1], 135 | [1, 0, 0, 1] ] 136 | coils = step2coils[int(self.currentstep/(self.MICROSTEPS/2))] 137 | 138 | #print "coils state = " + str(coils) 139 | self.MC.setPin(self.AIN2, coils[0]) 140 | self.MC.setPin(self.BIN1, coils[1]) 141 | self.MC.setPin(self.AIN1, coils[2]) 142 | self.MC.setPin(self.BIN2, coils[3]) 143 | 144 | return self.currentstep 145 | 146 | def step(self, steps, direction, stepstyle): 147 | s_per_s = self.sec_per_step 148 | lateststep = 0 149 | 150 | if (stepstyle == Raspi_MotorHAT.INTERLEAVE): 151 | s_per_s = s_per_s / 2.0 152 | if (stepstyle == Raspi_MotorHAT.MICROSTEP): 153 | s_per_s /= self.MICROSTEPS 154 | steps *= self.MICROSTEPS 155 | 156 | print(s_per_s, " sec per step") 157 | 158 | for _ in range(steps): 159 | lateststep = self.oneStep(direction, stepstyle) 160 | time.sleep(s_per_s) 161 | 162 | if (stepstyle == Raspi_MotorHAT.MICROSTEP): 163 | # this is an edge case, if we are in between full steps, lets just keep going 164 | # so we end on a full step 165 | while (lateststep != 0) and (lateststep != self.MICROSTEPS): 166 | lateststep = self.oneStep(dir, stepstyle) 167 | time.sleep(s_per_s) 168 | 169 | class Raspi_DCMotor: 170 | def __init__(self, controller, num): 171 | self.MC = controller 172 | self.motornum = num 173 | pwm = in1 = in2 = 0 174 | 175 | if (num == 0): 176 | pwm = 8 177 | in2 = 9 178 | in1 = 10 179 | elif (num == 1): 180 | pwm = 13 181 | in2 = 12 182 | in1 = 11 183 | elif (num == 2): 184 | pwm = 2 185 | in2 = 3 186 | in1 = 4 187 | elif (num == 3): 188 | pwm = 7 189 | in2 = 6 190 | in1 = 5 191 | else: 192 | raise NameError('MotorHAT Motor must be between 1 and 4 inclusive') 193 | self.PWMpin = pwm 194 | self.IN1pin = in1 195 | self.IN2pin = in2 196 | 197 | def run(self, command): 198 | if not self.MC: 199 | return 200 | if (command == Raspi_MotorHAT.FORWARD): 201 | self.MC.setPin(self.IN2pin, 0) 202 | self.MC.setPin(self.IN1pin, 1) 203 | if (command == Raspi_MotorHAT.BACKWARD): 204 | self.MC.setPin(self.IN1pin, 0) 205 | self.MC.setPin(self.IN2pin, 1) 206 | if (command == Raspi_MotorHAT.RELEASE): 207 | self.MC.setPin(self.IN1pin, 0) 208 | self.MC.setPin(self.IN2pin, 0) 209 | 210 | def setSpeed(self, speed): 211 | if (speed < 0): 212 | speed = 0 213 | if (speed > 255): 214 | speed = 255 215 | self.MC._pwm.setPWM(self.PWMpin, 0, speed*16) 216 | 217 | class Raspi_MotorHAT: 218 | FORWARD = 1 219 | BACKWARD = 2 220 | BRAKE = 3 221 | RELEASE = 4 222 | 223 | SINGLE = 1 224 | DOUBLE = 2 225 | INTERLEAVE = 3 226 | MICROSTEP = 4 227 | 228 | def __init__(self, addr=0x60, freq=1600): 229 | self._i2caddr = addr # default addr on HAT 230 | self._frequency = freq # default @1600Hz PWM freq 231 | self.motors = [ Raspi_DCMotor(self, m) for m in range(4) ] 232 | self.steppers = [ Raspi_StepperMotor(self, 1), Raspi_StepperMotor(self, 2) ] 233 | self._pwm = PWM(addr, debug=False) 234 | self._pwm.setPWMFreq(self._frequency) 235 | 236 | def setPin(self, pin, value): 237 | if (pin < 0) or (pin > 15): 238 | raise NameError('PWM pin must be between 0 and 15 inclusive') 239 | if (value != 0) and (value != 1): 240 | raise NameError('Pin value must be 0 or 1!') 241 | if (value == 0): 242 | self._pwm.setPWM(pin, 0, 4096) 243 | if (value == 1): 244 | self._pwm.setPWM(pin, 4096, 0) 245 | 246 | def getStepper(self, steps, num): 247 | if (num < 1) or (num > 2): 248 | raise NameError('MotorHAT Stepper must be between 1 and 2 inclusive') 249 | return self.steppers[num-1] 250 | 251 | def getMotor(self, num): 252 | if (num < 1) or (num > 4): 253 | raise NameError('MotorHAT Motor must be between 1 and 4 inclusive') 254 | return self.motors[num-1] 255 | --------------------------------------------------------------------------------