Skip to content
This repository was archived by the owner on Sep 30, 2019. It is now read-only.

Commit d4cccdd

Browse files
committed
Add PWM and MCP230xx support.
1 parent 08fb137 commit d4cccdd

File tree

6 files changed

+418
-9
lines changed

6 files changed

+418
-9
lines changed

Adafruit_GPIO/GPIO.py

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ def is_low(self, pin):
6464
"""Return true if the specified pin is pulled low."""
6565
return self.input(pin) == LOW
6666

67+
def output_pins(self, pins):
68+
"""Set multiple pins high or low at once. Pins should be a dict of pin
69+
name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins
70+
will be set to the given values.
71+
"""
72+
# General implementation just loops through pins and writes them out
73+
# manually. This is not optimized, but subclasses can choose to implement
74+
# a more optimal batch output implementation. See the MCP230xx class for
75+
# example of optimized implementation.
76+
for pin, value in pins.iteritems():
77+
self.output(pin, value)
78+
6779

6880
class RPiGPIOAdapter(BaseGPIO):
6981
"""GPIO implementation for the Raspberry Pi using the RPi.GPIO library."""

Adafruit_GPIO/MCP230xx.py

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Copyright (c) 2014 Adafruit Industries
2+
# Author: Tony DiCola
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
import math
22+
23+
import Adafruit_GPIO as GPIO
24+
import Adafruit_GPIO.I2C as I2C
25+
26+
27+
class MCP230xxBase(GPIO.BaseGPIO):
28+
"""Base class to represent an MCP230xx series GPIO extender. Is compatible
29+
with the Adafruit_GPIO BaseGPIO class so it can be used as a custom GPIO
30+
class for interacting with device.
31+
"""
32+
33+
def __init__(self, address, busnum=I2C.get_default_bus()):
34+
"""Initialize MCP230xx at specified I2C address and bus number. If bus
35+
is not specified it will default to the appropriate platform detected bus.
36+
"""
37+
self._i2c = I2C.Device(address, busnum)
38+
# Assume starting in ICON.BANK = 0 mode (sequential access).
39+
# Compute how many bytes are needed to store count of GPIO.
40+
self.gpio_bytes = int(math.ceil(self.NUM_GPIO/8.0))
41+
# Buffer register values so they can be changed without reading.
42+
self.iodir = [0x00]*self.gpio_bytes # Default direction to all inputs.
43+
self.gppu = [0x00]*self.gpio_bytes # Default to pullups disabled.
44+
self.gpio = [0x00]*self.gpio_bytes
45+
# Write current direction and pullup buffer state.
46+
self.write_iodir()
47+
self.write_gppu()
48+
49+
def _validate_pin(self, pin):
50+
# Raise an exception if pin is outside the range of allowed values.
51+
if pin < 0 or pin >= self.NUM_GPIO:
52+
raise ValueError('Invalid GPIO value, must be between 0 and {0}.'.format(self.NUM_GPIO))
53+
54+
def setup(self, pin, value):
55+
"""Set the input or output mode for a specified pin. Mode should be
56+
either GPIO.OUT or GPIO.IN.
57+
"""
58+
self._validate_pin(pin)
59+
# Set bit to 1 for input or 0 for output.
60+
if value == GPIO.IN:
61+
self.iodir[pin/8] |= 1 << (pin%8)
62+
elif value == GPIO.OUT:
63+
self.iodir[pin/8] &= ~(1 << (pin%8))
64+
else:
65+
raise ValueError('Unexpected value. Must be GPIO.IN or GPIO.OUT.')
66+
self.write_iodir()
67+
68+
def output(self, pin, value):
69+
"""Set the specified pin the provided high/low value. Value should be
70+
either GPIO.HIGH/GPIO.LOW or a boolean (True = high).
71+
"""
72+
self._validate_pin(pin)
73+
# Set bit on or off.
74+
if value:
75+
self.gpio[pin/8] |= 1 << (pin%8)
76+
else:
77+
self.gpio[pin/8] &= ~(1 << (pin%8))
78+
# Write GPIO state.
79+
self.write_gpio()
80+
81+
def output_pins(self, pins):
82+
"""Set multiple pins high or low at once. Pins should be a dict of pin
83+
name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins
84+
will be set to the given values.
85+
"""
86+
# Set each changed pin's bit.
87+
for pin, value in pins.iteritems():
88+
if value:
89+
self.gpio[pin/8] |= 1 << (pin%8)
90+
else:
91+
self.gpio[pin/8] &= ~(1 << (pin%8))
92+
# Write GPIO state.
93+
self.write_gpio()
94+
95+
def input(self, pin):
96+
"""Read the specified pin and return GPIO.HIGH/True if the pin is pulled
97+
high, or GPIO.LOW/False if pulled low.
98+
"""
99+
self._validate_pin(pin)
100+
# Get GPIO state.
101+
gpio = self._i2c.readList(self.GPIO, self.gpio_bytes)
102+
# Return True if pin's bit is set.
103+
return (gpio[pin/8] & 1 << (pin%8)) > 0
104+
105+
def pullup(self, pin, enabled):
106+
"""Turn on the pull-up resistor for the specified pin if enabled is True,
107+
otherwise turn off the pull-up resistor.
108+
"""
109+
self._validate_pin(pin)
110+
if enabled:
111+
self.gppu[pin/8] |= 1 << (pin%8)
112+
else:
113+
self.gppu[pin/8] &= ~(1 << (pin%8))
114+
self.write_gppu()
115+
116+
def write_gpio(self, gpio=None):
117+
"""Write the specified byte value to the GPIO registor. If no value
118+
specified the current buffered value will be written.
119+
"""
120+
if gpio is not None:
121+
self.gpio = gpio
122+
self._i2c.writeList(self.GPIO, self.gpio)
123+
124+
def write_iodir(self, iodir=None):
125+
"""Write the specified byte value to the IODIR registor. If no value
126+
specified the current buffered value will be written.
127+
"""
128+
if iodir is not None:
129+
self.iodir = iodir
130+
self._i2c.writeList(self.IODIR, self.iodir)
131+
132+
def write_gppu(self, gppu=None):
133+
"""Write the specified byte value to the GPPU registor. If no value
134+
specified the current buffered value will be written.
135+
"""
136+
if gppu is not None:
137+
self.gppu = gppu
138+
self._i2c.writeList(self.GPPU, self.gppu)
139+
140+
141+
class MCP23017(MCP230xxBase):
142+
"""MCP23017-based GPIO class with 16 GPIO pins."""
143+
# Define number of pins and registor addresses.
144+
NUM_GPIO = 16
145+
IODIR = 0x00
146+
GPIO = 0x12
147+
GPPU = 0x0C
148+
149+
def __init__(self, address=0x20, **kwargs):
150+
super(MCP23017, self).__init__(address, **kwargs)
151+
152+
153+
class MCP23008(MCP230xxBase):
154+
"""MCP23008-based GPIO class with 8 GPIO pins."""
155+
# Define number of pins and registor addresses.
156+
NUM_GPIO = 8
157+
IODIR = 0x00
158+
GPIO = 0x09
159+
GPPU = 0x06
160+
161+
def __init__(self, address=0x20, **kwargs):
162+
super(MCP23008, self).__init__(address, **kwargs)

Adafruit_GPIO/PWM.py

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copyright (c) 2014 Adafruit Industries
2+
# Author: Tony DiCola
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
import Adafruit_GPIO.Platform as Platform
22+
23+
24+
class RPi_PWM_Adapter(object):
25+
"""PWM implementation for the Raspberry Pi using the RPi.GPIO PWM library."""
26+
27+
def __init__(self, rpi_gpio, mode=None):
28+
self.rpi_gpio = rpi_gpio
29+
# Suppress warnings about GPIO in use.
30+
rpi_gpio.setwarnings(False)
31+
# Set board or BCM pin numbering.
32+
if mode == rpi_gpio.BOARD or mode == rpi_gpio.BCM:
33+
rpi_gpio.setmode(mode)
34+
elif mode is not None:
35+
raise ValueError('Unexpected value for mode. Must be BOARD or BCM.')
36+
else:
37+
# Default to BCM numbering if not told otherwise.
38+
rpi_gpio.setmode(rpi_gpio.BCM)
39+
# Store reference to each created PWM instance.
40+
self.pwm = {}
41+
42+
def start(self, pin, dutycycle, frequency_hz=2000):
43+
"""Enable PWM output on specified pin. Set to intiial percent duty cycle
44+
value (0.0 to 100.0) and frequency (in Hz).
45+
"""
46+
if dutycycle < 0.0 or dutycycle > 100.0:
47+
raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
48+
# Make pin an output.
49+
self.rpi_gpio.setup(12, self.rpi_gpio.OUT)
50+
# Create PWM instance and save a reference for later access.
51+
self.pwm[pin] = self.rpi_gpio.PWM(pin, frequency_hz)
52+
# Start the PWM at the specified duty cycle.
53+
self.pwm[pin].start(dutycycle)
54+
55+
def set_duty_cycle(self, pin, dutycycle):
56+
"""Set percent duty cycle of PWM output on specified pin. Duty cycle must
57+
be a value 0.0 to 100.0 (inclusive).
58+
"""
59+
if dutycycle < 0.0 or dutycycle > 100.0:
60+
raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
61+
if pin not in self.pwm:
62+
raise ValueError('Pin {0} is not configured as a PWM. Make sure to first call start for the pin.'.format(pin))
63+
self.pwm[pin].ChangeDutyCycle(dutycycle)
64+
65+
def set_frequency(self, pin, frequency_hz):
66+
"""Set frequency (in Hz) of PWM output on specified pin."""
67+
if pin not in self.pwm:
68+
raise ValueError('Pin {0} is not configured as a PWM. Make sure to first call start for the pin.'.format(pin))
69+
self.pwm[pin].ChangeFrequency(frequency_hz)
70+
71+
def stop(self, pin):
72+
"""Stop PWM output on specified pin."""
73+
if pin not in self.pwm:
74+
raise ValueError('Pin {0} is not configured as a PWM. Make sure to first call start for the pin.'.format(pin))
75+
self.pwm[pin].stop()
76+
del self.pwm[pin]
77+
78+
79+
class BBIO_PWM_Adapter(object):
80+
"""PWM implementation for the BeagleBone Black using the Adafruit_BBIO.PWM
81+
library.
82+
"""
83+
84+
def __init__(self, bbio_pwm):
85+
self.bbio_pwm = bbio_pwm
86+
87+
def start(self, pin, dutycycle, frequency_hz=2000):
88+
"""Enable PWM output on specified pin. Set to intiial percent duty cycle
89+
value (0.0 to 100.0) and frequency (in Hz).
90+
"""
91+
if dutycycle < 0.0 or dutycycle > 100.0:
92+
raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
93+
self.bbio_pwm.start(pin, dutycycle, frequency_hz)
94+
95+
def set_duty_cycle(self, pin, dutycycle):
96+
"""Set percent duty cycle of PWM output on specified pin. Duty cycle must
97+
be a value 0.0 to 100.0 (inclusive).
98+
"""
99+
if dutycycle < 0.0 or dutycycle > 100.0:
100+
raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
101+
self.bbio_pwm.set_duty_cycle(pin, dutycycle)
102+
103+
def set_frequency(self, pin, frequency_hz):
104+
"""Set frequency (in Hz) of PWM output on specified pin."""
105+
self.bbio_pwm.set_frequency(pin, frequency_hz)
106+
107+
def stop(self, pin):
108+
"""Stop PWM output on specified pin."""
109+
self.bbio_pwm.stop(pin)
110+
111+
112+
def get_platform_pwm(**keywords):
113+
"""Attempt to return a PWM instance for the platform which the code is being
114+
executed on. Currently supports only the Raspberry Pi using the RPi.GPIO
115+
library and Beaglebone Black using the Adafruit_BBIO library. Will throw an
116+
exception if a PWM instance can't be created for the current platform. The
117+
returned PWM object has the same interface as the RPi_PWM_Adapter and
118+
BBIO_PWM_Adapter classes.
119+
"""
120+
plat = Platform.platform_detect()
121+
if plat == Platform.RASPBERRY_PI:
122+
import RPi.GPIO
123+
return RPi_PWM_Adapter(RPi.GPIO, **keywords)
124+
elif plat == Platform.BEAGLEBONE_BLACK:
125+
import Adafruit_BBIO.PWM
126+
return BBIO_PWM_Adapter(Adafruit_BBIO.PWM, **keywords)
127+
elif plat == Platform.UNKNOWN:
128+
raise RuntimeError('Could not determine platform.')

setup.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
use_setuptools()
33
from setuptools import setup, find_packages
44

5-
setup(name = 'Adafruit_GPIO',
6-
version = '0.3.0',
7-
author = 'Tony DiCola',
8-
author_email = 'tdicola@adafruit.com',
9-
description = 'Library to provide a cross-platform GPIO interface on the Raspberry Pi and Beaglebone Black using the RPi.GPIO and Adafruit_BBIO libraries.',
10-
license = 'MIT',
11-
url = 'https://github.com/adafruit/Adafruit_Python_GPIO/',
12-
install_requires = ['spidev'],
13-
packages = find_packages())
5+
setup(name = 'Adafruit_GPIO',
6+
version = '0.4.0',
7+
author = 'Tony DiCola',
8+
author_email = 'tdicola@adafruit.com',
9+
description = 'Library to provide a cross-platform GPIO interface on the Raspberry Pi and Beaglebone Black using the RPi.GPIO and Adafruit_BBIO libraries.',
10+
license = 'MIT',
11+
url = 'https://github.com/adafruit/Adafruit_Python_GPIO/',
12+
install_requires = ['spidev'],
13+
packages = find_packages())

test/test_GPIO.py

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ def test_is_high_and_is_low(self):
4444
self.assertFalse(gpio.is_low(1))
4545
self.assertTrue(gpio.is_high(1))
4646

47+
def test_output_pins(self):
48+
gpio = MockGPIO()
49+
gpio.output_pins({0: True, 1: False, 7: True})
50+
self.assertDictEqual(gpio.pin_written, {0: [1], 1: [0], 7: [1]})
51+
4752

4853
class TestRPiGPIOAdapter(unittest.TestCase):
4954
def test_setup(self):

0 commit comments

Comments
 (0)