├── tests ├── __init__.py ├── local-tests.sh └── test_l293d.py ├── _config.yml ├── l293d ├── __init__.py ├── config.py └── driver.py ├── MANIFEST ├── requirements.txt ├── setup.cfg ├── Makefile ├── Pipfile ├── docs ├── about │ ├── sources.md │ ├── support.md │ ├── license.md │ └── contributing.md ├── index.md ├── user-guide │ ├── configuration.md │ ├── python-scripts.md │ ├── hardware-setup.md │ └── installation.md └── methods │ └── clockwise-anticlockwise.md ├── mkdocs.yml ├── .github └── workflows │ └── ci.yml ├── LICENSE.txt ├── README.md ├── .gitignore ├── setup.py └── Pipfile.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /l293d/__init__.py: -------------------------------------------------------------------------------- 1 | from l293d.driver import * 2 | 3 | __version__ = '0.3.4' 4 | 5 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | l293d/__init__.py 5 | l293d/config.py 6 | l293d/driver.py 7 | -------------------------------------------------------------------------------- /tests/local-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # To be run as ./tests/local-tests.sh 4 | 5 | # Abort on errors 6 | set -e 7 | 8 | # Flake8 code linting (config in setup.cfg) 9 | flake8 10 | 11 | # Run tests in Python 2 12 | py.test tests/ 13 | 14 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==17.4.0 2 | configparser==3.5.0 3 | enum34==1.1.6 4 | flake8==3.5.0 5 | funcsigs==1.0.2 6 | mccabe==0.6.1 7 | more-itertools==4.1.0 8 | pluggy==0.6.0 9 | py==1.5.3 10 | pycodestyle==2.3.1 11 | pyflakes==1.6.0 12 | pytest==3.5.0 13 | six==1.11.0 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [flake8] 5 | exclude = 6 | __init__.py 7 | venv*, 8 | .git, 9 | __pycache__, 10 | build, 11 | dist 12 | 13 | ignore = 14 | E122, # continuation line missing indentation or outdented 15 | E126 # continuation line over-indented for hanging indent 16 | 17 | [bdist_wheel] 18 | universal=1 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | python setup.py install 3 | 4 | freeze requirements: 5 | # https://stackoverflow.com/a/40167445/5127934 6 | pip freeze | grep -v "pkg-resources" > requirements.txt 7 | 8 | clean: 9 | rm -f l293d/*.pyc 10 | rm -f *.pyc 11 | 12 | test: 13 | tests/local-tests.sh 14 | 15 | dist: 16 | python3 setup.py sdist bdist_wheel 17 | 18 | upload: 19 | twine upload dist/* 20 | 21 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | name = "pypi" 5 | verify_ssl = true 6 | 7 | 8 | [dev-packages] 9 | 10 | 11 | 12 | [packages] 13 | 14 | attrs = "==17.4.0" 15 | configparser = "==3.5.0" 16 | "enum34" = "==1.1.6" 17 | "flake8" = "==3.5.0" 18 | funcsigs = "==1.0.2" 19 | mccabe = "==0.6.1" 20 | pluggy = "==0.6.0" 21 | py = "==1.5.3" 22 | pycodestyle = "==2.3.1" 23 | pyflakes = "==1.6.0" 24 | pytest = "==3.5.0" 25 | -------------------------------------------------------------------------------- /docs/about/sources.md: -------------------------------------------------------------------------------- 1 | # Sources 2 | 3 | - The l293d library was originally based on [this tutorial](https://business.tutsplus.com/tutorials/controlling-dc-motors-using-python-with-a-raspberry-pi--cms-20051) - The circuit diagrams in the docs are from there. 4 | - Some helpful information about the driver chip can be found [here](http://www.rakeshmondal.info/L293D-Motor-Driver). 5 | - You can buy L293D driver chips cheaply online - I bought a [pack of 5 on Amazon](https://www.amazon.co.uk/dp/B008KYMVVY) 6 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: l293d 2 | site_url: http://readthedocs.org/projects/l293d/ 3 | repo_url: https://jamesevickery.github.io/l293d/ 4 | site_description: Python module to drive DC motors from a Raspberry Pi using the L293D chip 5 | site_author: James Vickery 6 | 7 | repo_url: https://github.com/jamesevickery/l293d 8 | 9 | pages: 10 | - Home: index.md 11 | - User Guide: 12 | - Installation: user-guide/installation.md 13 | - Hardware Setup: user-guide/hardware-setup.md 14 | - Python Scripts: user-guide/python-scripts.md 15 | - Configuration: user-guide/configuration.md 16 | - Methods: 17 | - Clockwise and Anticlockwise: methods/clockwise-anticlockwise.md 18 | - About: 19 | - Support: about/support.md 20 | - Contributing: about/contributing.md 21 | - Sources: about/sources.md 22 | - License: about/license.md 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-18.04 8 | strategy: 9 | matrix: 10 | python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10.0-alpha.1'] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | 20 | - name: Display Python version 21 | run: python -c "import sys; print(sys.version)" 22 | 23 | - name: Install test stuff 24 | run: pip install flake8 pytest 25 | 26 | - name: Install l293d 27 | run: python setup.py install 28 | 29 | - name: Run flake8 30 | run: flake8 . --exclude __init__.py 31 | 32 | - name: Run tests 33 | run: py.test tests/ 34 | -------------------------------------------------------------------------------- /docs/about/support.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | If you have any ideas that would make this library even more beautiful, please [submit an issue](https://github.com/jamesevickery/l293d/issues) - or if you're feeling particularly helpful then I would be delighted if you'd fork and submit a pull request! If you want to contribute but don't have an idea, the [issues page](https://github.com/jamesevickery/l293d/issues) is rarely empty and any help would be very much appreciated - especially if the issue is tagged as '[help wanted](https://github.com/jamesevickery/l293d/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)'. 4 | 5 | Feel free to contact me if you have any questions about use or development of `l293d`. Either [submit an issue](https://github.com/jamesevickery/l293d/issues), email me at [dev@jamesvickery.net](mailto:dev@jamesvickery.net?Subject=L293D) or tweet/DM me [on Twitter](https://twitter.com/jamesevickery). 6 | 7 | -------------------------------------------------------------------------------- /docs/about/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | ### The MIT License (MIT) 4 | 5 | *Copyright © 2016-2018 James Vickery* 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /docs/about/contributing.md: -------------------------------------------------------------------------------- 1 | #Contributing 2 | 3 | Install development requirements: 4 | 5 | pip install -r requirements.txt 6 | 7 | To contribute, any pull requests are happily accepted, but any large changes should be suggested in an issue first. 8 | 9 | Code should work with both Python 2 & 3, and be [PEP8](https://www.python.org/dev/peps/pep-0008/)-compliant. 10 | [Travis CI](https://travis-ci.org/jamesevickery/l293d) automatically checks code complies with PEP8 and that the tests (in [`tests/`](https://github.com/jamesevickery/l293d/tree/master/tests) directory) all pass. 11 | 12 | 13 | ## GitHub 14 | 15 | This project is hosted on GitHub, at [https://github.com/jamesevickery/l293d](https://github.com/jamesevickery/l293d). 16 | 17 | Feel free to star/fork 18 | 19 | 20 | ## Contributors 21 | 22 | - [@jamesevickery](https://github.com/jamesevickery) 23 | - [@alxwrd](https://github.com/alxwrd) 24 | - [@the-zebulan](https://github.com/the-zebulan) 25 | - [@surajnarwade](https://github.com/surajnarwade) 26 | - [@vlvrd](https://github.com/vlvrd) 27 | - [@PatOConnor43](https://github.com/PatOConnor43) 28 | - [@SmithcS](https://github.com/SmithcS) 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 to Present - James Vickery 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 | # [L293D driver](https://jmsv.github.io/l293d/) 2 | *Python module to drive DC motors from a Raspberry Pi using the L293D chip* 3 | 4 | [![PyPI version](https://badge.fury.io/py/l293d.svg)](https://badge.fury.io/py/l293d) 5 | [![Python versions](https://img.shields.io/pypi/pyversions/l293d.svg)](https://pypi.python.org/pypi/l293d) 6 | [![Build Status](https://travis-ci.org/jmsv/l293d.svg?branch=master)](https://travis-ci.org/jmsv/l293d) 7 | [![Documentation Status](https://readthedocs.org/projects/l293d/badge/?version=latest)](http://l293d.readthedocs.io/en/latest/?badge=latest) 8 | [![Requirements Status](https://requires.io/github/jmsv/l293d/requirements.svg?branch=master)](https://requires.io/github/jmsv/l293d/requirements/?branch=master) 9 | [![Contributors](https://img.shields.io/github/contributors/jmsv/l293d.svg)](https://github.com/jmsv/l293d/graphs/contributors) 10 | [![Wheel Support](https://img.shields.io/pypi/wheel/l293d.svg)](https://pypi.python.org/pypi/l293d) 11 | 12 | --- 13 | 14 | - This README was formerly used as the complete l293d documentation - 15 | it has now been moved to [l293d.readthedocs.io](http://l293d.readthedocs.io/en/latest/) 16 | 17 | - If you have any ideas, suggestions or problems, please 18 | [open an issue](https://github.com/jmsv/l293d/issues/new). 19 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # [L293D driver](https://jamesevickery.github.io/l293d/) 2 | *Python module to drive DC motors from a Raspberry Pi using the L293D chip* 3 | 4 | [![PyPI version](https://badge.fury.io/py/l293d.svg)](https://badge.fury.io/py/l293d) 5 | [![Python versions](https://img.shields.io/pypi/pyversions/l293d.svg)](https://pypi.python.org/pypi/l293d) 6 | [![Build Status](https://travis-ci.org/jamesevickery/l293d.svg?branch=master)](https://travis-ci.org/jamesevickery/l293d) 7 | [![Documentation Status](https://readthedocs.org/projects/l293d/badge/?version=latest)](http://l293d.readthedocs.io/en/latest/?badge=latest) 8 | [![Requirements Status](https://requires.io/github/jamesevickery/l293d/requirements.svg?branch=master)](https://requires.io/github/jamesevickery/l293d/requirements/?branch=master) 9 | [![Contributors](https://img.shields.io/github/contributors/jamesevickery/l293d.svg)](https://github.com/jamesevickery/l293d/graphs/contributors) 10 | [![Wheel Support](https://img.shields.io/pypi/wheel/l293d.svg)](https://pypi.python.org/pypi/l293d) 11 | 12 | --- 13 | 14 | If you have any ideas, suggestions or problems, please 15 | [open an issue](https://github.com/jamesevickery/l293d/issues/new). 16 | 17 | l293d is installable via PyPI, using `pip install l293d`. However, this isn't always the very latest version. For the latest, clone the GitHub repo and run `python setup.py install`. 18 | -------------------------------------------------------------------------------- /docs/user-guide/configuration.md: -------------------------------------------------------------------------------- 1 | The following config values change how the module works, and can be changed after importing `l293d`. 2 | 3 | ## Test Mode 4 | 5 | Test mode (`test_mode`) is a parameter of the l293d config. By default it's off (False) although if there is a problem importing `RPi.GPIO` when `l293d.driver` is imported, test mode is enabled. 6 | 7 | To stop this from happening, install the RPi.GPIO Python package: 8 | 9 | ```bash 10 | pip install RPi.GPIO 11 | ``` 12 | 13 | _To change the value of `test_mode`, use `l293d.Config.test_mode = value`, where value is True or False._ 14 | 15 | ## Verbosity 16 | 17 | Verbosity (`verbose`) is another True/False config value. l293d only prints textual output when `verbose` is True; which it is, by default. 18 | 19 | _To change the value of `verbose`, use `l293d.Config.verbose = value`, where value is True or False._ 20 | 21 | 22 | ## Pin Numbering 23 | 24 | Pin numbering (`pin_numbering`) is either BOARD or BCM. These are different ways of numbering the Raspberry Pi's pins. BOARD numbering refers to the physical location of the pins, whereas BCM refers to the Broadcom pin number. A good pinout diagram labelling the pin & BCM numbers can be found at [pinout.xyz](https://pinout.xyz/). 25 | 26 | _To change the value of `pin_numbering`, use `l293d.Config.pin_numbering = value`, where value is either 'BOARD' or 'BCM'._ 27 | 28 | This value should only be changed before any motors have been defined. If you try to call `l293d.Config.set_pin_numbering` after defining a motor, an exception (`ValueError`) is raised. 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # pytest cache 2 | .pytest_cache/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # pypirc configuration 10 | .pypirc 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | venv/ 89 | venv*/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | .idea/ 98 | 99 | # mkdocs 100 | site/ 101 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from codecs import open 4 | from os import path 5 | from setuptools import setup 6 | 7 | here = path.abspath(path.dirname(__file__)) 8 | 9 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 10 | readme = f.read() 11 | 12 | with open(path.join(here, 'l293d/__init__.py'), encoding='utf8') as f: 13 | version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1) 14 | 15 | repo = 'https://github.com/jmsv/l293d' 16 | 17 | setup( 18 | name='l293d', 19 | packages=['l293d'], 20 | version=version, 21 | author='James Vickery', 22 | author_email='dev@jamesvickery.net', 23 | description=('A Python module to drive motors using ' 24 | 'an L293D via Raspberry Pi GPIO'), 25 | long_description=readme, 26 | long_description_content_type='text/markdown', 27 | license='MIT', 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Programming Language :: Python', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3', 34 | 'Programming Language :: Python :: 3.4', 35 | 'Programming Language :: Python :: 3.5', 36 | 'Programming Language :: Python :: 3.6', 37 | 'Programming Language :: Python :: 3.7', 38 | 'Programming Language :: Python :: 3.8', 39 | 'Programming Language :: Python :: 3.9', 40 | 'Programming Language :: Python :: 3.10', 41 | 'Intended Audience :: Developers', 42 | 'Natural Language :: English', 43 | 'Operating System :: POSIX :: Linux' 44 | ], 45 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.2.*, <4', 46 | keywords=['raspberry', 'pi', 'gpio', 'l293d', 'chip', 'motor', 'driver'], 47 | url=repo, 48 | project_urls={ 49 | 'Bug Reports': '%s/issues' % repo, 50 | 'Source': repo 51 | }, 52 | download_url='%s/archive/v%s.tar.gz' % (repo, version), 53 | ) 54 | -------------------------------------------------------------------------------- /docs/user-guide/python-scripts.md: -------------------------------------------------------------------------------- 1 | This documentation should help create a basic Python script to get a motor working. 2 | I'm currently working on better documentation for the methods used, as some have functionality not documented here. 3 | 4 | ### 1. Import the module 5 | 6 | ```python 7 | import l293d 8 | ``` 9 | 10 | 11 | ### 2. Define motors 12 | 13 | In this example, the GPIO pin numbers will be the same as listed in [Hardware Setup](hardware-setup.md). 14 | 15 | ```python 16 | motor1 = l293d.DC(22, 18, 16) 17 | ``` 18 | 19 | In some cases, it may be necessary to use some pins that aren't considered valid, but we can force it 20 | by expliciting the `force_selection` parameter as `True` in DC class initialization (by default, this 21 | parameter is `False`). 22 | 23 | ```python 24 | motor1 = l293d.DC(19, 21, 23, force_selection=True) 25 | ``` 26 | 27 | In this case, 'motor1' is what we're calling the DC motor object. You can call it whatever you want, 28 | for example `wheel_motor`, `london_eye` or `spinny_thing`. 29 | 30 | The numbers correspond to which GPIO pins are connected to L293D pins 1, 2 and 7 respectively: the pins we set up in [Hardware Setup](hardware-setup.md). 31 | 32 | 33 | ### 3. Control motors 34 | 35 | The statements to make the motor(s) spin are as follows: 36 | 37 | - `motor1.clockwise()` 38 | - `motor1.anticlockwise()` 39 | - `motor1.stop()` 40 | 41 | If, `clockwise()` and `anticlockwise()` spin the motor the wrong way, swap the two motor connections to 42 | the L293D chip, as explained in [Hardware Setup: Adding a motor](hardware-setup.md#adding-a-motor). 43 | 44 | I strongly recommend looking at the [Clockwise & Anticlockwise](../methods/clockwise-anticlockwise.md) docs - 45 | these methods are more powerful than demonstrated above. 46 | 47 | 48 | ### 4. Cleanup 49 | 50 | I recommend that at the end of your script, you include the line: `l293d.cleanup()`, to cleanup the GPIO pins being used by the l293d library. This avoids damage to the GPIO pins; see [here](http://raspi.tv/2013/rpi-gpio-basics-3-how-to-exit-gpio-programs-cleanly-avoid-warnings-and-protect-your-pi). 51 | 52 | It would also be a good idea to set up '`try` `catch`' around motor driving calls to cleanup if any exceptions are raised. 53 | -------------------------------------------------------------------------------- /docs/methods/clockwise-anticlockwise.md: -------------------------------------------------------------------------------- 1 | The `DC.clockwise()` and `DC.anticlockwise()` methods can 2 | take optional parameters to extend their functionality 3 | 4 | ## Parameters 5 | 6 | 7 | ### `duration` 8 | 9 | - **Type:** Number - _`int` or `float`_ 10 | - **Default:** `None` - _Turns motor on until `stop` is manually called_ 11 | 12 | The `duration` parameter can be used to make the motor spin for a number of seconds. 13 | For example, use `motor.clockwise(duration=3)` to make a motor (`DC` object named `motor`) 14 | spin clockwise for 3 seconds. 15 | 16 | 17 | ### `wait` 18 | 19 | - **Type:** Boolean - _`bool`_ 20 | - **Default:** `True` - _Method doesn't return until motor has stopped_ 21 | 22 | If the `duration` parameter is being used to make the motor spin for a number of seconds, 23 | the `wait` parameter can be used 24 | 25 | 26 | ### `speed` 27 | 28 | - **Type:** Tuple - _`tuple`_ / _`PWM`_ 29 | - **Default:** `100` - _Motor runs at a 100% duty cycle (full speed)_ 30 | 31 | The `speed` parameter can be used to control how fast the motor spins using 32 | [PWM (Pulse Width Modulation)](https://en.wikipedia.org/wiki/Pulse-width_modulation). 33 | 34 | `speed` can be either a tuple or an integer. When using a tuple, order matters 35 | and it should be `(frequency, duty_cycle)`. 36 | 37 | There is also the option to be explict and use the `l293d.PWM` namedtuple which 38 | takes the 2 keyword arguments: `l293d.PWM(freq=x, cycle=x)`. 39 | 40 | #### PWM examples 41 | 42 | ```python 43 | import l293d 44 | 45 | motor = l293d.DC(22, 18, 16) 46 | 47 | # pre-define a pwm 48 | pwm = l293d.PWM(freq=30, cycle=70) 49 | motor.clockwise(speed=pwm) 50 | 51 | # or use it directly in the method call 52 | motor.clockwise(speed=l293d.PWM(freq=50, cycle=50)) 53 | 54 | # keywords aren't required 55 | motor.clockwise(speed=l293d.PWM(50, 50)) 56 | 57 | # normal tuples work too 58 | motor.clockwise(speed=(20, 30)) 59 | 60 | # an integer can be used if you want the same frequency and duty cycle 61 | motor.clockwise(speed=50) 62 | ``` 63 | 64 | 65 | 66 | ## Implementation Examples 67 | 68 | ```python 69 | import l293d 70 | motor = l293d.DC(15, 18, 11) 71 | ``` 72 | 73 | 74 | - `motor.clockwise()` 75 | 76 | Turns `motor` on in the clockwise direction. Doesn't stop until `motor.stop()` is called 77 | 78 | - `motor.anticlockwise(3.5)` 79 | 80 | As `duration` is the first parameter of the `anticlockwise` and `clockwise` methods, 81 | the line above would make `motor` spin for 3.5 seconds before the method returns 82 | 83 | - `motor.clockwise(7, wait=False)` 84 | 85 | `motor` spins for 7 seconds, but the method returns immediately. 86 | This means that any code following this line won't be delayed. 87 | -------------------------------------------------------------------------------- /docs/user-guide/hardware-setup.md: -------------------------------------------------------------------------------- 1 | ### You will need: 2 | 3 | - Raspberry Pi 4 | - L293D chip(s) 5 | - DC motor(s) 6 | - Power-pack (4x AA or similar) 7 | - Breadboard and wires 8 | 9 | The L293D driver chips are very cheap to buy: I bought a bag of five [from Amazon](http://www.amazon.co.uk/dp/B008KYMVVY). Unless you intend to use more than two motors, only one driver chip is required; each L293D can drive up to two motors. 10 | 11 | ### 1. Powering the L293D chip 12 | 13 | Power and ground setup - the chip should bridge the middle of the breadboard: 14 | 15 | - The Pi's 5V → L293D pin 16 (see below image for numbering format) 16 | - An empty power rail → L293D pin 8 17 | - The Pi's ground (GND) → Breadboard ground rail(s) 18 | - Ground rail(s) → L293D pins 4, 5, 12, and 13 pins (the middle ones) 19 | 20 | ![pin numbering image](http://i.imgur.com/RLGyWst.png?2) 21 | 22 | The circuit should look like this: 23 | 24 | ![power pins image](http://i.imgur.com/awtfujg.png?1) 25 | 26 | ### 2. Data wires 27 | 28 | The GPIO pins used in this example can be substitued for other valid pins, as long as continuity is maintained when [setting up a Python script](#python-scripts). 29 | 30 | The Pi's GPIO needs to be wired to the L293D's data pins via the breadboard, as follows: 31 | 32 | - GPIO 25 (pin 22) → L293D pin 1 33 | - GPIO 24 (pin 18) → L293D pin 2 34 | - GPIO 23 (pin 16) → L293D pin 7 35 | 36 | Your circuit should now look something like this: 37 | 38 | ![data pins image](http://i.imgur.com/h5OQFZT.png?1) 39 | 40 | ### 3. Adding a motor 41 | 42 | - Motor wire 1 → L293D pin 3 43 | - Motor wire 2 → L293D pin 6 44 | 45 | ![one motor image](http://i.imgur.com/0PWp7vN.png?1) 46 | 47 | You will also need to connect the battery pack to the power rail and the common ground rail - the one that connects to the L293D's pin 8. 48 | 49 | _Note: It doesn't matter which motor wire is connected to 3 or 6, although this will affect the direction. When you've set up a [Python script](#python-scripts), if `clockwise()` makes the motor spin anti-clockwise, the two motor wires should be swapped._ 50 | 51 | ### 4. Adding another motor (optional) 52 | 53 | This is similar to how the first motor was connected, but the other side of the chip is used. 54 | 55 | Data wires: 56 | 57 | - GPIO 11 (pin 23) → L293D pin 9 58 | - GPIO 9 (pin 21) → L293D pin 10 59 | - GPIO 10 (pin 19) → L293D pin 15 60 | 61 | Motor wires: 62 | 63 | - Motor wire 1 → L293D pin 11 64 | - Motor wire 2 → L293D pin 14 65 | 66 | The circuit should now look something like this: 67 | 68 | ![two motors image](http://i.imgur.com/ryYQOr4.png?1) 69 | 70 | More motors can be used with additional L293Ds. Just set up another chip as demonstrated above - each chip can drive a maximum of 2 motors. 71 | -------------------------------------------------------------------------------- /docs/user-guide/installation.md: -------------------------------------------------------------------------------- 1 | To install the Python library: 2 | 3 | These instructions assume you're using Linux bash, although the library can also be installed in other environments for testing. 4 | 5 | ## [PyPI](https://pypi.python.org/pypi/l293d/) 6 | 7 | pip install l293d 8 | 9 | l293d is installable via PyPI, using the above command. 10 | However, this might not be always the very latest (development) version. 11 | If you're happy using the version on the Python Package Index, 12 | run the `pip install l293d` command and skip to instruction [5](#5-test). 13 | To install the latest version, follow the below instructions. 14 | 15 | Remember to prefix the pip install command with `sudo` if you wish to install this package globally. 16 | 17 | 18 | ## From GitHub 19 | 20 | 21 | #### 1. Clone code from GitHub 22 | 23 | $ git clone https://github.com/jamesevickery/l293d.git 24 | 25 | #### 2. Navigate to the l293d folder 26 | 27 | $ cd l293d/ 28 | 29 | #### 3. Install dependencies 30 | 31 | Python 2: 32 | 33 | $ sudo apt-get install python-dev python-pip 34 | 35 | Python 3: 36 | 37 | $ sudo apt-get install python3-pip 38 | 39 | Install RPi.GPIO (Pi only): 40 | 41 | $ sudo pip install RPi.GPIO 42 | 43 | Also installable via `apt-get`, although `pip` (as above) is recommended: 44 | 45 | $ sudo apt-get install RPi.GPIO 46 | 47 | Installing `RPi.GPIO` is required to drive motors on the Raspberry Pi, although in other environments, [test mode](#test-mode) is automatically enabled if `RPi.GPIO` isn't found. 48 | 49 | #### 4. Install the library 50 | 51 | $ sudo python setup.py install 52 | 53 | Or for use with Python 3, swap `python` for `python3` in the command above. 54 | 55 | #### 5. Test 56 | 57 | $ python 58 | >>> import l293d 59 | 60 | Again, `python3` may be used instead of `python`, although remember that installations for different Python versions are independent. 61 | 62 | Once l293d has been successfully installed, it can be used to drive motors. See [Python Scripts](python-scripts.md) for more info. 63 | 64 | 65 | ## Virtualenv 66 | 67 | l293d can be installed in a virtualenv, like you can with other packages: 68 | 69 | #### 1. Install 70 | 71 | pip install virtualenv 72 | 73 | #### 2. Create virtualenv 74 | 75 | virtualenv venv_name 76 | 77 | #### 3. Activate virtualenv 78 | 79 | source venv_name/bin/activate 80 | 81 | #### 4. Install l293d 82 | 83 | Install using one of the methods above: from [PyPI](#pypi) or from [GitHub](#from-github). 84 | You don't need to use `sudo`, as l293d should install within the virtualenv. 85 | 86 | #### 5. Deactivate 87 | 88 | Once you've finished using the virtualenv: 89 | 90 | deactivate 91 | 92 | To learn more about virtualenv, I found [this site](http://python-guide-pt-br.readthedocs.io/en/latest/dev/virtualenvs/) useful. 93 | -------------------------------------------------------------------------------- /l293d/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class ConfigMeta(type): 6 | 7 | def __getattr__(cls, attr): 8 | """ 9 | Try and call get_`attr` on the cls. 10 | """ 11 | try: 12 | return cls.__dict__["get_" + attr].__func__(cls) 13 | except KeyError: 14 | raise AttributeError( 15 | "object '{}' has no attribute '{}'".format( 16 | cls.__name__, attr)) 17 | 18 | def __setattr__(cls, attr, value): 19 | """ 20 | Try and call set_`attr` on the cls. 21 | """ 22 | if cls.__name__ not in attr: 23 | # First try calling the set_ method. This will allow the set_ 24 | # method to perform it's checks, then recursively call this method. 25 | try: 26 | cls.__dict__["set_" + attr].__func__(cls, value) 27 | except KeyError: 28 | raise AttributeError( 29 | "object '{}' has no attribute '{}'".format( 30 | cls.__name__, attr)) 31 | else: 32 | # Now that the set_ method has done it's checks, we can use super() 33 | # to actually set the value. 34 | super(ConfigMeta, cls).__setattr__(attr, value) 35 | 36 | 37 | def with_metaclass(mcls): 38 | """ 39 | Allows compatibility for metaclass in python2 and python3 40 | """ 41 | def decorator(cls): 42 | body = vars(cls).copy() 43 | body.pop("__dict__", None) 44 | body.pop("__weakref__", None) 45 | return mcls(cls.__name__, cls.__bases__, body) 46 | return decorator 47 | 48 | 49 | @with_metaclass(ConfigMeta) 50 | class Config(object): 51 | __verbose = True 52 | __test_mode = False 53 | __pin_numbering = 'BOARD' 54 | pins_in_use = [] 55 | 56 | @classmethod 57 | def set_verbose(cls, value): 58 | if type(value) == bool: 59 | cls.__verbose = value 60 | else: 61 | raise TypeError('verbose must be either True or False') 62 | 63 | @classmethod 64 | def get_verbose(cls): 65 | return cls.__verbose 66 | 67 | @classmethod 68 | def set_test_mode(cls, value): 69 | if type(value) == bool: 70 | cls.__test_mode = value 71 | else: 72 | raise TypeError('test_mode must be either True or False') 73 | 74 | @classmethod 75 | def get_test_mode(cls): 76 | return cls.__test_mode 77 | 78 | @classmethod 79 | def set_pin_numbering(cls, value): 80 | if type(value) != str: 81 | raise TypeError('pin_numbering must be a string:' 82 | '\'BOARD\' or \'BCM\'') 83 | value = str(value).upper() 84 | if cls.pins_in_use: 85 | raise ValueError('Pin numbering format cannot be changed ' 86 | 'if motors already exist. Set this at ' 87 | 'the start of your script.') 88 | if not (value == 'BOARD' or value == 'BCM'): 89 | raise ValueError( 90 | 'Pin numbering format must be \'BOARD\' or \'BCM\'') 91 | cls.__pin_numbering = value 92 | print("Pin numbering format set: " + value) 93 | return value 94 | 95 | @classmethod 96 | def get_pin_numbering(cls): 97 | return cls.__pin_numbering 98 | -------------------------------------------------------------------------------- /tests/test_l293d.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from sys import version_info 3 | 4 | # Python 2: reload is built-in 5 | if version_info.major == 3: 6 | if version_info.minor < 4: 7 | # Python 3.0 - 3.3: deprecated since Python 3.4 in favour of importlib 8 | from imp import reload 9 | else: 10 | # Python 3.4+ 11 | from importlib import reload 12 | 13 | 14 | class L293DTestCase(unittest.TestCase): 15 | def test_import(self): 16 | """ 17 | Import driver module, then assertTrue. 18 | If the import fails, an exception is 19 | raised and the assertion is never reached 20 | """ 21 | import l293d as d 22 | print(str(d)) 23 | self.assertTrue(True) 24 | reload(d.driver) # Revert changes 25 | 26 | def test_create_motor_with_force_selection(self): 27 | """ 28 | Test DC class instance creation with explicity 29 | force_selection parameter 30 | """ 31 | import l293d as d 32 | cases = (([33, 36, 37], False), ([19, 21, 23], True)) 33 | for pins, force_selection in cases: 34 | motor = d.DC(*pins, force_selection=force_selection) 35 | self.assertEqual(d.pins_in_use, pins) 36 | motor.remove() 37 | reload(d.driver) 38 | 39 | def test_pins_string_list(self): 40 | """ 41 | Test that the list of pins is returned correctly 42 | """ 43 | import l293d as d 44 | print(d.pins_in_use) 45 | motor = d.DC(29, 7, 13) 46 | self.assertEqual(motor.pins_string_list(), '[29, 7 and 13]') 47 | reload(d.driver) 48 | 49 | def test_pins_are_valid_board_1(self): 50 | """ 51 | Test for valid pins when all others are in use 52 | """ 53 | import l293d as d 54 | d.pins_in_use = [7, 11, 12, 13, 15, 29, 31, 32, 33, 36, 37] 55 | self.assertTrue(d.pins_are_valid([22, 18, 16])) 56 | reload(d.driver) 57 | 58 | def test_pins_are_valid_board_2(self): 59 | """ 60 | Test for valid pins when a pin is already in use 61 | """ 62 | import l293d as d 63 | d.pins_in_use = [7, 11, 12] 64 | try: 65 | valid = d.pins_are_valid([31, 36, 11]) 66 | if valid: 67 | self.assertFalse(True) 68 | except Exception: 69 | self.assertFalse(False) 70 | reload(d.driver) 71 | 72 | def test_motor_can_be_removed(self): 73 | """ 74 | Test that a motor can be created and removed 75 | """ 76 | import l293d as d 77 | original_pins = d.pins_in_use 78 | motor = d.DC(29, 7, 13) 79 | motor.remove() 80 | self.assertEqual(d.pins_in_use, original_pins) 81 | reload(d.driver) 82 | 83 | def test_pin_numbering_lock(self): 84 | """ 85 | Test that pin_numbering can't be changed after a motor's definition 86 | """ 87 | import l293d as d 88 | d.Config.pin_numbering = 'BcM' 89 | m1 = d.DC(4, 5, 6) 90 | error = 'No error' 91 | try: 92 | d.Config.pin_numbering = 'BoaRD' 93 | except ValueError as e: 94 | error = str(e) 95 | self.assertEqual( 96 | error, 'Pin numbering format cannot be changed ' 97 | 'if motors already exist. Set this at ' 98 | 'the start of your script.') 99 | m1.remove() 100 | d.Config.pin_numbering = 'BOARD' 101 | reload(d.driver) 102 | 103 | def test_getting_config(self): 104 | """ 105 | Test that Config supports both get_ and properties 106 | """ 107 | import l293d as d 108 | 109 | self.assertEqual(d.Config.pin_numbering, d.Config.get_pin_numbering()) 110 | 111 | def test_setting_config(self): 112 | """ 113 | Test that Config supports both set_ and properties 114 | """ 115 | import l293d as d 116 | 117 | d.Config.set_verbose(False) 118 | self.assertEqual(d.Config.verbose, d.Config.get_verbose()) 119 | 120 | reload(d.driver) 121 | 122 | d.Config.verbose = False 123 | self.assertEqual(d.Config.verbose, d.Config.get_verbose()) 124 | 125 | 126 | if __name__ == '__main__': 127 | unittest.main() 128 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "2b37880de120732da2d908c68ad866b0ddaae8b2a3cba6baac7ac7df9ee6639e" 5 | }, 6 | "host-environment-markers": { 7 | "implementation_name": "cpython", 8 | "implementation_version": "3.5.2", 9 | "os_name": "posix", 10 | "platform_machine": "x86_64", 11 | "platform_python_implementation": "CPython", 12 | "platform_release": "4.13.0-37-generic", 13 | "platform_system": "Linux", 14 | "platform_version": "#42~16.04.1-Ubuntu SMP Wed Mar 7 16:03:28 UTC 2018", 15 | "python_full_version": "3.5.2", 16 | "python_version": "3.5", 17 | "sys_platform": "linux" 18 | }, 19 | "pipfile-spec": 6, 20 | "requires": {}, 21 | "sources": [ 22 | { 23 | "name": "pypi", 24 | "url": "https://pypi.python.org/simple", 25 | "verify_ssl": true 26 | } 27 | ] 28 | }, 29 | "default": { 30 | "attrs": { 31 | "hashes": [ 32 | "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", 33 | "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" 34 | ], 35 | "version": "==17.4.0" 36 | }, 37 | "configparser": { 38 | "hashes": [ 39 | "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" 40 | ], 41 | "version": "==3.5.0" 42 | }, 43 | "enum34": { 44 | "hashes": [ 45 | "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", 46 | "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", 47 | "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", 48 | "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850" 49 | ], 50 | "version": "==1.1.6" 51 | }, 52 | "flake8": { 53 | "hashes": [ 54 | "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37", 55 | "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0" 56 | ], 57 | "version": "==3.5.0" 58 | }, 59 | "funcsigs": { 60 | "hashes": [ 61 | "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", 62 | "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" 63 | ], 64 | "version": "==1.0.2" 65 | }, 66 | "mccabe": { 67 | "hashes": [ 68 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 69 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 70 | ], 71 | "version": "==0.6.1" 72 | }, 73 | "more-itertools": { 74 | "hashes": [ 75 | "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", 76 | "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", 77 | "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" 78 | ], 79 | "version": "==4.1.0" 80 | }, 81 | "pluggy": { 82 | "hashes": [ 83 | "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" 84 | ], 85 | "version": "==0.6.0" 86 | }, 87 | "py": { 88 | "hashes": [ 89 | "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a", 90 | "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881" 91 | ], 92 | "version": "==1.5.3" 93 | }, 94 | "pycodestyle": { 95 | "hashes": [ 96 | "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", 97 | "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" 98 | ], 99 | "version": "==2.3.1" 100 | }, 101 | "pyflakes": { 102 | "hashes": [ 103 | "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", 104 | "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" 105 | ], 106 | "version": "==1.6.0" 107 | }, 108 | "pytest": { 109 | "hashes": [ 110 | "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", 111 | "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1" 112 | ], 113 | "version": "==3.5.0" 114 | }, 115 | "six": { 116 | "hashes": [ 117 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", 118 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" 119 | ], 120 | "version": "==1.11.0" 121 | } 122 | }, 123 | "develop": {} 124 | } 125 | -------------------------------------------------------------------------------- /l293d/driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | 6 | from collections import namedtuple 7 | from threading import Thread 8 | from time import sleep 9 | 10 | from l293d.config import Config 11 | 12 | 13 | def v_print(string): 14 | """ 15 | Print if verbose 16 | :param string: Text to print if verbose 17 | :return: True if printed, otherwise False 18 | """ 19 | if Config.get_verbose(): 20 | print("[l293d]: %s" % str(string)) 21 | return True 22 | return False 23 | 24 | 25 | # Import GPIO 26 | try: 27 | import RPi.GPIO as GPIO 28 | except ImportError: 29 | GPIO = None 30 | Config.test_mode = True 31 | v_print( 32 | "Can't import RPi.GPIO; test mode has been enabled:\n" 33 | "http://l293d.rtfd.io/en/latest/user-guide/configuration/#test-mode") 34 | 35 | if not Config.test_mode: 36 | GPIO.setwarnings(False) 37 | 38 | # Set GPIO mode 39 | if not Config.test_mode: 40 | pin_num = Config.pin_numbering 41 | v_print('Setting GPIO mode: {}'.format(pin_num)) 42 | GPIO.setmode(getattr(GPIO, pin_num)) 43 | 44 | pins_in_use = Config.pins_in_use # Lists pins in use (all motors) 45 | 46 | 47 | class DC(object): 48 | """ 49 | A class for a motor wired to the L293D chip where 50 | motor_pins[0] is pinA is L293D pin1 or pin9 : On or off 51 | motor_pins[1] is pinB is L293D pin2 or pin10 : Anticlockwise positive 52 | motor_pins[2] is pinC is L293D pin7 or pin15 : Clockwise positive 53 | """ 54 | 55 | def __init__(self, pin_a=0, pin_b=0, pin_c=0, force_selection=False): 56 | # Assign parameters to list 57 | self.motor_pins = [0 for x in range(3)] 58 | self.motor_pins[0] = pin_a 59 | self.motor_pins[1] = pin_b 60 | self.motor_pins[2] = pin_c 61 | 62 | self.pwm = None 63 | 64 | self.pin_numbering = Config.pin_numbering 65 | 66 | self.reversed = False 67 | 68 | # Check pins are valid 69 | if pins_are_valid(self.motor_pins, force_selection): 70 | self.exists = True 71 | # Append to global list of pins in use 72 | for pin in self.motor_pins: 73 | pins_in_use.append(pin) 74 | # Set up GPIO mode for pins 75 | self.gpio_setup() 76 | 77 | def gpio_setup(self): 78 | """ 79 | Set GPIO.OUT for each pin in use 80 | """ 81 | for pin in self.motor_pins: 82 | if not Config.test_mode: 83 | GPIO.setup(pin, GPIO.OUT) 84 | 85 | def drive_motor(self, direction=1, duration=None, wait=True, speed=100): 86 | """ 87 | Method called by other functions to drive L293D via GPIO 88 | """ 89 | self.check() 90 | 91 | if not speed: 92 | speed = 0 93 | if isinstance(speed, int): 94 | # If speed is an integer, change it to a tuple 95 | speed = (speed, speed) 96 | # Unpack speed into PWM, this works even if a PWM tuple was passed in 97 | speed = PWM(*speed) 98 | 99 | if self.reversed: 100 | direction *= -1 101 | if not Config.test_mode: 102 | if direction == 0: # Then stop motor 103 | self.pwm.stop() 104 | else: # Spin motor 105 | # Create a PWM object to control the 'enable pin' for the chip 106 | self.pwm = GPIO.PWM(self.motor_pins[0], speed.freq) 107 | # Set first direction GPIO level 108 | GPIO.output(self.motor_pins[direction], GPIO.HIGH) 109 | # Set second direction GPIO level 110 | GPIO.output(self.motor_pins[direction * -1], GPIO.LOW) 111 | # Start PWM on the 'enable pin' 112 | self.pwm.start(speed.cycle) 113 | # If duration has been specified, sleep then stop 114 | if duration is not None and direction != 0: 115 | stop_thread = Thread(target=self.stop, args=(duration,)) 116 | # Sleep in thread 117 | stop_thread.start() 118 | if wait: 119 | # If wait is true, the main thread is blocked 120 | stop_thread.join() 121 | 122 | def pins_string_list(self): 123 | """ 124 | Return readable list of pins 125 | """ 126 | return '[{}, {} and {}]'.format(*self.motor_pins) 127 | 128 | def __move_motor(self, direction, duration, wait, action, speed): 129 | """ 130 | Uses drive_motor to spin the motor in `direction` 131 | """ 132 | self.check() 133 | if Config.verbose: 134 | v_print('{action} {reversed}motor at ' 135 | '{pin_nums} pins {pin_str}'.format( 136 | action=action, 137 | reversed='reversed' if self.reversed else '', 138 | pin_nums=self.pin_numbering, 139 | pin_str=self.pins_string_list())) 140 | 141 | self.drive_motor(direction=direction, duration=duration, 142 | wait=wait, speed=speed) 143 | 144 | def clockwise(self, duration=None, wait=True, speed=100): 145 | """ 146 | Spin the motor clockwise 147 | """ 148 | self.__move_motor(1, duration, wait, 'spinning clockwise', speed) 149 | 150 | def anticlockwise(self, duration=None, wait=True, speed=100): 151 | """ 152 | Spin the motor anticlockwise 153 | """ 154 | self.__move_motor(-1, duration, wait, 'spinning anticlockwise', speed) 155 | 156 | def stop(self, after=0): 157 | """ 158 | Stop the motor. If 'after' is specified, sleep for amount of time 159 | """ 160 | if after > 0: 161 | sleep(after) 162 | self.__move_motor(0, after, True, 'stopping', None) 163 | 164 | def remove(self): 165 | """ 166 | Remove motor 167 | """ 168 | if self.exists: 169 | for m_pin in self.motor_pins: 170 | if m_pin in pins_in_use: 171 | pins_in_use.remove(m_pin) 172 | self.exists = False 173 | else: 174 | v_print('Motor has already been removed') 175 | 176 | def check(self): 177 | """ 178 | Check the motor exists. If not, an exception is raised 179 | """ 180 | if not self.exists: 181 | raise ValueError('Motor has been removed. ' 182 | 'If you wish to use this motor again, ' 183 | 'you must redefine it.') 184 | 185 | 186 | PWM = namedtuple("PWM", ["freq", "cycle"]) 187 | 188 | 189 | class Motor(object): 190 | def __init__(self, pin_a=0, pin_b=0, pin_c=0): 191 | raise DeprecationWarning('The Motor class has been deprecated. ' 192 | 'Please use \'DC\' or \'Stepper\' instead.') 193 | 194 | 195 | class Stepper(object): 196 | def __init__(self): 197 | raise FutureWarning('Stepper motors are not yet supported. Go to ' 198 | 'https://github.com/jamesevickery/l293d/issues/20 ' 199 | 'for more info') 200 | 201 | 202 | def pins_are_valid(pins, force_selection=False): 203 | """ 204 | Check the pins specified are valid for pin numbering in use 205 | """ 206 | # Pin numbering, used below, should be 207 | # a parameter of this function (future) 208 | if Config.pin_numbering == 'BOARD': # Set valid pins for BOARD 209 | valid_pins = [ 210 | 7, 11, 12, 13, 15, 16, 18, 22, 29, 31, 32, 33, 36, 37 211 | ] 212 | elif Config.pin_numbering == 'BCM': # Set valid pins for BCM 213 | valid_pins = [ 214 | 4, 5, 6, 12, 13, 16, 17, 18, 22, 23, 24, 25, 26, 27 215 | ] 216 | else: # pin_numbering value invalid 217 | raise ValueError("pin_numbering must be either 'BOARD' or 'BCM'.") 218 | for pin in pins: 219 | pin_int = int(pin) 220 | if pin_int not in valid_pins and force_selection is False: 221 | err_str = ( 222 | "GPIO pin number must be from list of valid pins: %s" 223 | "\nTo use selected pins anyway, set force_selection=True " 224 | "in function call." % str(valid_pins)) 225 | raise ValueError(err_str) 226 | if pin in pins_in_use: 227 | raise ValueError('GPIO pin {} already in use.'.format(pin)) 228 | return True 229 | 230 | 231 | def cleanup(): 232 | """ 233 | Call GPIO cleanup method 234 | """ 235 | if not Config.test_mode: 236 | try: 237 | GPIO.cleanup() 238 | v_print('GPIO cleanup successful.') 239 | except Exception: 240 | v_print('GPIO cleanup failed.') 241 | else: 242 | # Skip GPIO cleanup if GPIO calls are not being made (test_mode) 243 | v_print('Cleanup not needed when test_mode is enabled.') 244 | --------------------------------------------------------------------------------