From 50946b417ce569aae7ae056e57d0bd8e1f30f20b Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 3 Apr 2016 19:53:59 +0100 Subject: [PATCH] Add pins database Related to @lurch's comments on #148, this PR contains a database of pins for each Pi revision, along with various other bits of miscellany (I might've gotten a bit carried away here...). Any corrections/extensions welcome! --- docs/api_exc.rst | 6 + docs/api_pins.rst | 37 ++- gpiozero/__init__.py | 8 + gpiozero/exc.py | 9 + gpiozero/pins/data.py | 696 +++++++++++++++++++++++++++++++++++++++ gpiozero/pins/native.py | 11 +- gpiozero/pins/pigpiod.py | 14 +- gpiozero/pins/rpigpio.py | 11 +- gpiozero/pins/rpio.py | 9 +- 9 files changed, 775 insertions(+), 26 deletions(-) create mode 100644 gpiozero/pins/data.py diff --git a/docs/api_exc.rst b/docs/api_exc.rst index fe6e461..9587975 100644 --- a/docs/api_exc.rst +++ b/docs/api_exc.rst @@ -93,6 +93,12 @@ Errors .. autoexception:: PinPWMFixedValue +.. autoexception:: PinMultiplePins + +.. autoexception:: PinNoPins + +.. autoexception:: PinUnknownPi + Warnings ======== diff --git a/docs/api_pins.rst b/docs/api_pins.rst index 71c0270..e08b4ac 100644 --- a/docs/api_pins.rst +++ b/docs/api_pins.rst @@ -46,7 +46,8 @@ can pass a :class:`Pin` object itself:: led = LED(NativePin(16)) This is particularly useful with implementations that can take extra parameters -such as :class:`PiGPIOPin` which can address pins on remote machines:: +such as :class:`~gpiozero.pins.pigpiod.PiGPIOPin` which can address pins on +remote machines:: from gpiozero.pins.pigpiod import PiGPIOPin from gpiozero import LED @@ -73,40 +74,46 @@ to utilize pins that are part of IO extender chips. For example:: RPiGPIOPin ========== -.. currentmodule:: gpiozero.pins.rpigpio - -.. autoclass:: RPiGPIOPin +.. autoclass:: gpiozero.pins.rpigpio.RPiGPIOPin RPIOPin ======= -.. currentmodule:: gpiozero.pins.rpio - -.. autoclass:: RPIOPin +.. autoclass:: gpiozero.pins.rpio.RPIOPin PiGPIOPin ========= -.. currentmodule:: gpiozero.pins.pigpiod - -.. autoclass:: PiGPIOPin +.. autoclass:: gpiozero.pins.pigpiod.PiGPIOPin NativePin ========= -.. currentmodule:: gpiozero.pins.native - -.. autoclass:: NativePin +.. autoclass:: gpiozero.pins.native.NativePin Abstract Pin ============ -.. currentmodule:: gpiozero.pins - .. autoclass:: Pin :members: + +Utilities +========= + +The pins module also contains a database of information about the various +revisions of Raspberry Pi. This is used internally to raise warnings when +non-physical pins are used, or to raise exceptions when pull-downs are +requested on pins with physical pull-up resistors attached. The following +functions and classes can be used to query this database: + +.. autofunction:: pi_info + +.. autoclass:: PiBoardInfo + +.. autoclass:: PinInfo + diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index 6ac96ea..1f50e16 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -8,6 +8,11 @@ from __future__ import ( from .pins import ( Pin, ) +from .pins.data import ( + PiBoardInfo, + PinInfo, + pi_info, +) from .exc import ( GPIOZeroError, DeviceClosed, @@ -37,6 +42,9 @@ from .exc import ( PinPWMError, PinPWMUnsupported, PinPWMFixedValue, + PinUnknownPi, + PinMultiplePins, + PinNoPins, GPIOZeroWarning, SPIWarning, SPISoftwareFallback, diff --git a/gpiozero/exc.py b/gpiozero/exc.py index de2a530..b6fbe82 100644 --- a/gpiozero/exc.py +++ b/gpiozero/exc.py @@ -97,6 +97,15 @@ class PinPWMUnsupported(PinPWMError, AttributeError): class PinPWMFixedValue(PinPWMError, AttributeError): "Error raised when attempting to initialize PWM on an input pin" +class PinUnknownPi(PinError, RuntimeError): + "Error raised when gpiozero doesn't recognize a revision of the Pi" + +class PinMultiplePins(PinError, RuntimeError): + "Error raised when multiple pins support the requested function" + +class PinNoPins(PinError, RuntimeError): + "Error raised when no pins support the requested function" + class GPIOZeroWarning(Warning): "Base class for all warnings in GPIO Zero" diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py new file mode 100644 index 0000000..133b94f --- /dev/null +++ b/gpiozero/pins/data.py @@ -0,0 +1,696 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +str = type('') + +import io +from collections import namedtuple + +from ..exc import PinUnknownPi, PinMultiplePins + + +# Some useful constants for describing pins + +V1_8 = '1V8' +V3_3 = '3V3' +V5 = '5V' +GND = 'GND' +NC = 'NC' # not connected +GPIO0 = 'GPIO0' +GPIO1 = 'GPIO1' +GPIO2 = 'GPIO2' +GPIO3 = 'GPIO3' +GPIO4 = 'GPIO4' +GPIO5 = 'GPIO5' +GPIO6 = 'GPIO6' +GPIO7 = 'GPIO7' +GPIO8 = 'GPIO8' +GPIO9 = 'GPIO9' +GPIO10 = 'GPIO10' +GPIO11 = 'GPIO11' +GPIO12 = 'GPIO12' +GPIO13 = 'GPIO13' +GPIO14 = 'GPIO14' +GPIO15 = 'GPIO15' +GPIO16 = 'GPIO16' +GPIO17 = 'GPIO17' +GPIO18 = 'GPIO18' +GPIO19 = 'GPIO19' +GPIO20 = 'GPIO20' +GPIO21 = 'GPIO21' +GPIO22 = 'GPIO22' +GPIO23 = 'GPIO23' +GPIO24 = 'GPIO24' +GPIO25 = 'GPIO25' +GPIO26 = 'GPIO26' +GPIO27 = 'GPIO27' +GPIO28 = 'GPIO28' +GPIO29 = 'GPIO29' +GPIO30 = 'GPIO30' +GPIO31 = 'GPIO31' +GPIO32 = 'GPIO32' +GPIO33 = 'GPIO33' +GPIO34 = 'GPIO34' +GPIO35 = 'GPIO35' +GPIO36 = 'GPIO36' +GPIO37 = 'GPIO37' +GPIO38 = 'GPIO38' +GPIO39 = 'GPIO39' +GPIO40 = 'GPIO40' +GPIO41 = 'GPIO41' +GPIO42 = 'GPIO42' +GPIO43 = 'GPIO43' +GPIO44 = 'GPIO44' +GPIO45 = 'GPIO45' + +# Pin maps for various board revisions and headers + +REV1_P1 = { +# pin func pullup pin func pullup + 1: (V3_3, False), 2: (V5, False), + 3: (GPIO0, True), 4: (V5, False), + 5: (GPIO1, True), 6: (GND, False), + 7: (GPIO4, False), 8: (GPIO14, False), + 9: (GND, False), 10: (GPIO15, False), + 11: (GPIO17, False), 12: (GPIO18, False), + 13: (GPIO21, False), 14: (GND, False), + 15: (GPIO22, False), 16: (GPIO23, False), + 17: (V3_3, False), 18: (GPIO24, False), + 19: (GPIO10, False), 20: (GND, False), + 21: (GPIO9, False), 22: (GPIO25, False), + 23: (GPIO11, False), 24: (GPIO8, False), + 25: (GND, False), 26: (GPIO7, False), + } + +REV2_P1 = { + 1: (V3_3, False), 2: (V5, False), + 3: (GPIO2, True), 4: (V5, False), + 5: (GPIO3, True), 6: (GND, False), + 7: (GPIO4, False), 8: (GPIO14, False), + 9: (GND, False), 10: (GPIO15, False), + 11: (GPIO17, False), 12: (GPIO18, False), + 13: (GPIO27, False), 14: (GND, False), + 15: (GPIO22, False), 16: (GPIO23, False), + 17: (V3_3, False), 18: (GPIO24, False), + 19: (GPIO10, False), 20: (GND, False), + 21: (GPIO9, False), 22: (GPIO25, False), + 23: (GPIO11, False), 24: (GPIO8, False), + 25: (GND, False), 26: (GPIO7, False), + } + +REV2_P5 = { + 1: (V5, False), 2: (V3_3, False), + 3: (GPIO28, False), 4: (GPIO29, False), + 5: (GPIO30, False), 6: (GPIO31, False), + 7: (GND, False), 8: (GND, False), + } + +PLUS_P1 = { + 1: (V3_3, False), 2: (V5, False), + 3: (GPIO2, True), 4: (V5, False), + 5: (GPIO3, True), 6: (GND, False), + 7: (GPIO4, False), 8: (GPIO14, False), + 9: (GND, False), 10: (GPIO15, False), + 11: (GPIO17, False), 12: (GPIO18, False), + 13: (GPIO27, False), 14: (GND, False), + 15: (GPIO22, False), 16: (GPIO23, False), + 17: (V3_3, False), 18: (GPIO24, False), + 19: (GPIO10, False), 20: (GND, False), + 21: (GPIO9, False), 22: (GPIO25, False), + 23: (GPIO11, False), 24: (GPIO8, False), + 25: (GND, False), 26: (GPIO7, False), + 27: (GPIO0, False), 28: (GPIO1, False), + 29: (GPIO5, False), 30: (GND, False), + 31: (GPIO6, False), 32: (GPIO12, False), + 33: (GPIO13, False), 34: (GND, False), + 35: (GPIO19, False), 36: (GPIO16, False), + 37: (GPIO26, False), 38: (GPIO20, False), + 39: (GND, False), 40: (GPIO21, False), + } + +CM_SODIMM = { + 1: (GND, False), 2: ('EMMC DISABLE N', False), + 3: (GPIO0, False), 4: (NC, False), + 5: (GPIO1, False), 6: (NC, False), + 7: (GND, False), 8: (NC, False), + 9: (GPIO2, False), 10: (NC, False), + 11: (GPIO3, False), 12: (NC, False), + 13: (GND, False), 14: (NC, False), + 15: (GPIO4, False), 16: (NC, False), + 17: (GPIO5, False), 18: (NC, False), + 19: (GND, False), 20: (NC, False), + 21: (GPIO6, False), 22: (NC, False), + 23: (GPIO7, False), 24: (NC, False), + 25: (GND, False), 26: (GND, False), + 27: (GPIO8, False), 28: (GPIO28, False), + 29: (GPIO9, False), 30: (GPIO29, False), + 31: (GND, False), 32: (GND, False), + 33: (GPIO10, False), 34: (GPIO30, False), + 35: (GPIO11, False), 36: (GPIO31, False), + 37: (GND, False), 38: (GND, False), + 39: ('GPIO0-27 VREF', False), 40: ('GPIO0-27 VREF', False), + # Gap in SODIMM pins + 41: ('GPIO28-45 VREF', False), 42: ('GPIO28-45 VREF', False), + 43: (GND, False), 44: (GND, False), + 45: (GPIO12, False), 46: (GPIO32, False), + 47: (GPIO13, False), 48: (GPIO33, False), + 49: (GND, False), 50: (GND, False), + 51: (GPIO14, False), 52: (GPIO34, False), + 53: (GPIO15, False), 54: (GPIO35, False), + 55: (GND, False), 56: (GND, False), + 57: (GPIO16, False), 58: (GPIO36, False), + 59: (GPIO17, False), 60: (GPIO37, False), + 61: (GND, False), 62: (GND, False), + 63: (GPIO18, False), 64: (GPIO38, False), + 65: (GPIO19, False), 66: (GPIO39, False), + 67: (GND, False), 68: (GND, False), + 69: (GPIO20, False), 70: (GPIO40, False), + 71: (GPIO21, False), 72: (GPIO41, False), + 73: (GND, False), 74: (GND, False), + 75: (GPIO22, False), 76: (GPIO42, False), + 77: (GPIO23, False), 78: (GPIO43, False), + 79: (GND, False), 80: (GND, False), + 81: (GPIO24, False), 82: (GPIO44, False), + 83: (GPIO25, False), 84: (GPIO45, False), + 85: (GND, False), 86: (GND, False), + 87: (GPIO26, False), 88: ('GPIO46 1V8', False), + 89: (GPIO27, False), 90: ('GPIO47 1V8', False), + 91: (GND, False), 92: (GND, False), + 93: ('DSI0 DN1', False), 94: ('DSI1 DP0', False), + 95: ('DSI0 DP1', False), 96: ('DSI1 DN0', False), + 97: (GND, False), 98: (GND, False), + 99: ('DSI0 DN0', False), 100: ('DSI1 CP', False), + 101: ('DSI0 DP0', False), 102: ('DSI1 CN', False), + 103: (GND, False), 104: (GND, False), + 105: ('DSI0 CN', False), 106: ('DSI1 DP3', False), + 107: ('DSI0 CP', False), 108: ('DSI1 DN3', False), + 109: (GND, False), 110: (GND, False), + 111: ('HDMI CK N', False), 112: ('DSI1 DP2', False), + 113: ('HDMI CK P', False), 114: ('DSI1 DN2', False), + 115: (GND, False), 116: (GND, False), + 117: ('HDMI D0 N', False), 118: ('DSI1 DP1', False), + 119: ('HDMI D0 P', False), 120: ('DSI1 DN1', False), + 121: (GND, False), 122: (GND, False), + 123: ('HDMI D1 N', False), 124: (NC, False), + 125: ('HDMI D1 P', False), 126: (NC, False), + 127: (GND, False), 128: (NC, False), + 129: ('HDMI D2 N', False), 130: (NC, False), + 131: ('HDMI D2 P', False), 132: (NC, False), + 133: (GND, False), 134: (GND, False), + 135: ('CAM1 DP3', False), 136: ('CAM0 DP0', False), + 137: ('CAM1 DN3', False), 138: ('CAM0 DN0', False), + 139: (GND, False), 140: (GND, False), + 141: ('CAM1 DP2', False), 142: ('CAM0 CP', False), + 143: ('CAM1 DN2', False), 144: ('CAM0 CN', False), + 145: (GND, False), 146: (GND, False), + 147: ('CAM1 CP', False), 148: ('CAM0 DP1', False), + 149: ('CAM1 CN', False), 150: ('CAM0 DN1', False), + 151: (GND, False), 152: (GND, False), + 153: ('CAM1 DP1', False), 154: (NC, False), + 155: ('CAM1 DN1', False), 156: (NC, False), + 157: (GND, False), 158: (NC, False), + 159: ('CAM1 DP0', False), 160: (NC, False), + 161: ('CAM1 DN0', False), 162: (NC, False), + 163: (GND, False), 164: (GND, False), + 165: ('USB DP', False), 166: ('TVDAC', False), + 167: ('USB DM', False), 168: ('USB OTGID', False), + 169: (GND, False), 170: (GND, False), + 171: ('HDMI CEC', False), 172: ('VC TRST N', False), + 173: ('HDMI SDA', False), 174: ('VC TDI', False), + 175: ('HDMI SCL', False), 176: ('VC TMS', False), + 177: ('RUN', False), 178: ('VC TDO', False), + 179: ('VDD CORE', False), 180: ('VC TCK', False), + 181: (GND, False), 182: (GND, False), + 183: (V1_8, False), 184: (V1_8, False), + 185: (V1_8, False), 186: (V1_8, False), + 187: (GND, False), 188: (GND, False), + 189: ('VDAC', False), 190: ('VDAC', False), + 191: (V3_3, False), 192: (V3_3, False), + 193: (V3_3, False), 194: (V3_3, False), + 195: (GND, False), 196: (GND, False), + 197: ('VBAT', False), 198: ('VBAT', False), + 199: ('VBAT', False), 200: ('VBAT', False), + } + +# The following data is sourced from a combination of the following locations: +# +# http://elinux.org/RPi_HardwareHistory +# http://elinux.org/RPi_Low-level_peripherals +# https://git.drogon.net/?p=wiringPi;a=blob;f=wiringPi/wiringPi.c#l807 + +PI_REVISIONS = { + # rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers + 'beta': ('B', '?', '2012Q1', 'BCM2835', '?', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + '0002': ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + '0003': ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + '0004': ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '0005': ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '0006': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '0007': ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '0008': ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '0009': ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '000d': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '000e': ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '000f': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + '0010': ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + '0011': ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + '0012': ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), + '0013': ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + '0014': ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + '0015': ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), + #'a01041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + #'a21041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + #'900092': ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), + #'a02082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + #'a22082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + } + + +class PinInfo(namedtuple('PinInfo', ( + 'number', + 'function', + 'pull_up', + ))): + """ + This class is a :func:`~collections.namedtuple` derivative used to + represent information about a pin present on a GPIO header. The following + attributes are defined: + + .. attribute:: number + + An integer containing the physical pin number on the header (starting + from 1 in accordance with convention). + + .. attribute:: function + + A string describing the function of the pin. Some common examples + include "GND" (for pins connecting to ground), "3V3" (for pins which + output 3.3 volts), "GPIO9" (for GPIO9 in the Broadcom numbering + scheme), etc. + + .. attribute:: pull_up + + A bool indicating whether the pin has a physical pull-up resistor + permanently attached (this is usually ``False`` but GPIO2 and GPIO3 + are *usually* ``True``). This is used internally by gpiozero to raise + errors when pull-down is requested on a pin with a physical pull-up + resistor. + """ + + +class PiBoardInfo(namedtuple('PiBoardInfo', ( + 'revision', + 'model', + 'pcb_revision', + 'released', + 'soc', + 'manufacturer', + 'memory', + 'storage', + 'usb', + 'ethernet', + 'wifi', + 'bluetooth', + 'csi', + 'dsi', + 'headers', + ))): + """ + This class is a :func:`~collections.namedtuple` derivative used to + represent information about a particular model of Raspberry Pi. While it is + a tuple, it is strongly recommended that you use the following named + attributes to access the data contained within. + + .. attribute:: revision + + A string indicating the revision of the Pi. This is unique to each + revision and can be considered the "key" from which all other + attributes are derived. However, in itself the string is fairly + meaningless. + + .. attribute:: model + + A string containing the model of the Pi (for example, "B", "B+", "A+", + "2B", "CM" (for the Compute Module), or "Zero"). + + .. attribute:: pcb_revision + + A string containing the PCB revision number which is silk-screened onto + the Pi (on some models). + + .. note:: + + This is primarily useful to distinguish between the model B + revision 1.0 and 2.0 (not to be confused with the model 2B) which + had slightly different pinouts on their 26-pin GPIO headers. + + .. attribute:: released + + A string containing an approximate release date for this revision of + the Pi (formatted as yyyyQq, e.g. 2012Q1 means the first quarter of + 2012). + + .. attribute:: soc + + A string indicating the SoC (`system on a chip`_) that this revision + of the Pi is based upon. + + .. attribute:: manufacturer + + A string indicating the name of the manufacturer (usually "Sony" but a + few others exist). + + .. attribute:: memory + + An integer indicating the amount of memory (in Mb) connected to the + SoC. + + .. note:: + + This can differ substantially from the amount of RAM available + to the operating system as the GPU's memory is shared with the + CPU. When the camera module is activated, at least 128Mb of RAM + is typically reserved for the GPU. + + .. attribute:: storage + + A string indicating the type of bootable storage used with this + revision of Pi, e.g. "SD", "MicroSD", or "eMMC" (for the Compute + Module). + + .. attribute:: usb + + An integer indicating how many USB ports are physically present on + this revision of the Pi. + + .. note:: + + This does *not* include the micro-USB port used to power the Pi. + On the Compute Module this is listed as 0 as the compute module + itself doesn't have any physical USB headers, despite providing one + on the I/O development board and having the pins for one on the + module itself. + + .. attribute:: ethernet + + An integer indicating how many Ethernet ports are physically present + on this revision of the Pi. + + .. attribute:: wifi + + A bool indicating whether this revision of the Pi has wifi built-in. + + .. attribute:: bluetooth + + A bool indicating whether this revision of the Pi has bluetooth + built-in. + + .. attribute:: csi + + An integer indicating the number of CSI (camera) ports available on + this revision of the Pi. + + .. attribute:: dsi + + An integer indicating the number of DSI (display) ports available on + this revision of the Pi. + + .. attribute:: headers + + A dictionary which maps header labels to dictionaries which map + physical pin numbers to :class:`PinInfo` tuples. For example, to obtain + information about pin 12 on header P1 you would query + ``headers['P1'][12]``. + + .. _system on a chip: https://en.wikipedia.org/wiki/System_on_a_chip + """ + + def physical_pins(self, function): + """ + Return the physical pins supporting the specified *function* as a tuple + of ``(header, pin_number)`` where *header* is a string specifying the + header containing the *pin_number*. Note that the return value is a + :class:`set` which is not indexable. Use :func:`physical_pin` if you + are expecting a single return value. + + :param str function: + The pin function you wish to search for. Usually this is something + like "GPIO9" for Broadcom GPIO pin 9, or "GND" for all the pins + connecting to electrical ground. + """ + return { + (header, pin.number) + for (header, pins) in self.headers.items() + for pin in pins.values() + if pin.function == function + } + + def physical_pin(self, function): + """ + Return the physical pin supporting the specified *function*. If no pins + support the desired *function*, this function raises :exc:`PinNoPins`. + If multiple pins support the desired *function*, :exc:`PinMultiplePins` + will be raised (use :func:`physical_pins` if you expect multiple pins + in the result, such as for electrical ground). + + :param str function: + The pin function you wish to search for. Usually this is something + like "GPIO9" for Broadcom GPIO pin 9. + """ + result = self.physical_pins(function) + if len(result) > 1: + raise PinMultiplePins('multiple pins can be used for %s' % function) + elif result: + return result.pop() + else: + raise PinNoPins('no pins can be used for %s' % function) + + def pulled_up(self, function): + """ + Returns a bool indicating whether a physical pull-up is attached to + the pin supporting the specified *function*. Either :exc:`PinNoPins` + or :exc:`PinMultiplePins` may be raised if the function is not + associated with a single pin. + + :param str function: + The pin function you wish to determine pull-up for. Usually this is + something like "GPIO9" for Broadcom GPIO pin 9. + """ + header, number = self.physical_pin(function) + return self.headers[header][number].pull_up + + +_PI_REVISION = None +def _get_pi_revision(): + with io.open('/proc/cpuinfo', 'r') as f: + for line in f: + if line.startswith('Revision'): + revision = line.split(':')[1].strip().lower() + overvolted = revision.startswith('1000') + if overvolted: + revision = revision[4:] + return revision + raise IOError('unable to locate Pi revision in /proc/cpuinfo') + + +def _parse_pi_revision(revision): + # For new-style revisions the value's bit pattern is as follows: + # + # MSB -----------------------> LSB + # uuuuuuuuFMMMCCCCPPPPTTTTTTTTRRRR + # + # uuuuuuuu - Unused + # F - New flag (1=valid new-style revision, 0=old-style) + # MMM - Memory size (0=256, 1=512, 2=1024) + # CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest) + # PPPP - Processor (0=2835, 1=2836, 2=2837) + # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero) + # RRR - Revision (0, 1, or 2) + i = int(revision, base=16) + if not (i & 0x800000): + raise ValueError('cannot parse "%s"; this is not a new-style revision' % revision) + try: + model = { + 0: 'A', + 1: 'B', + 2: 'A+', + 3: 'B+', + 4: '2B', + 6: 'CM', + 8: '3B', + 9: 'Zero', + }[(i & 0xff0) >> 4] + if model in ('A', 'B'): + pcb_revision = { + 0: '1.0', # is this right? + 1: '1.0', + 2: '2.0', + }[i & 0x0f] + else: + pcb_revision = '1.%d' % (i & 0x0f) + released = { + 'A': '2013Q1', + 'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4', + 'A+': '2014Q4', + 'B+': '2014Q3', + '2B': '2015Q1', + 'CM': '2014Q2', + '3B': '2016Q1', + 'Zero': '2015Q4', + }[model] + soc = { + 0: 'BCM2835', + 1: 'BCM2836', + 2: 'BCM2837', + }[(i & 0xf000) >> 12] + manufacturer = { + 0: 'Sony', + 1: 'Egoman', + 2: 'Embest', + }[(i & 0xf0000) >> 16] + memory = { + 0: 256, + 1: 512, + 2: 1024, + }[(i & 0x700000) >> 20] + storage = { + 'A': 'SD', + 'B': 'SD', + 'CM': 'eMMC', + }.get(model, 'MicroSD') + usb = { + 'A': 1, + 'A+': 1, + 'Zero': 1, + 'B': 2, + 'CM': 0, + }.get(model, 4) + ethernet = { + 'A': 0, + 'A+': 0, + 'Zero': 0, + 'CM': 0, + }.get(model, 1) + wifi = { + '3B': True, + }.get(model, False) + bluetooth = { + '3B': True, + }.get(model, False) + csi = { + 'Zero': 0, + 'CM': 2, + }.get(model, 1) + dsi = csi + headers = { + 'A': {'P1': REV2_P1, 'P5': REV2_P5}, + 'B': {'P1': REV2_P1, 'P5': REV2_P5} if pcb_revision == '2.0' else {'P1': REV1_P1}, + 'CM': {'SODIMM': CM_SODIMM}, + }.get(model, {'P1': PLUS_P1}) + except KeyError: + raise ValueError('unable to parse new-style revision "%s"' % revision) + else: + return ( + model, + pcb_revision, + released, + soc, + manufacturer, + memory, + storage, + usb, + ethernet, + wifi, + bluetooth, + csi, + dsi, + headers, + ) + + +def pi_info(revision=None): + """ + Returns a :class:`PiBoardInfo` instance containing information about a + *revision* of the Raspberry Pi. + + :param str revision: + The revision of the Pi to return information about. If this is omitted + or ``None`` (the default), then the library will attempt to determine + the model of Pi it is running on and return information about that. + """ + # cache the result as we can reasonably assume the revision of the Pi isn't + # going to change at runtime... + if revision is None: + global _PI_REVISION + if _PI_REVISION is None: + try: + _PI_REVISION = _get_pi_revision() + except IOError: + _PI_REVISION = 'unknown' + revision = _PI_REVISION + try: + ( + model, + pcb_revision, + released, + soc, + manufacturer, + memory, + storage, + usb, + ethernet, + wifi, + bluetooth, + csi, + dsi, + headers, + ) = PI_REVISIONS[revision] + except KeyError: + try: + ( + model, + pcb_revision, + released, + soc, + manufacturer, + memory, + storage, + usb, + ethernet, + wifi, + bluetooth, + csi, + dsi, + headers, + ) = _parse_pi_revision(revision) + except ValueError: + raise PinUnknownPi('unknown RPi revision "%s"' % revision) + headers = { + header: { + number: PinInfo(number, function, pull_up) + for number, (function, pull_up) in header_data.items() + } + for header, header_data in headers.items() + } + return PiBoardInfo( + revision, + model, + pcb_revision, + released, + soc, + manufacturer, + memory, + storage, + usb, + ethernet, + wifi, + bluetooth, + csi, + dsi, + headers, + ) + + diff --git a/gpiozero/pins/native.py b/gpiozero/pins/native.py index 2bd095f..447aa5d 100644 --- a/gpiozero/pins/native.py +++ b/gpiozero/pins/native.py @@ -17,6 +17,7 @@ from threading import Thread, Event, Lock from collections import Counter from . import Pin, PINS_CLEANUP +from .data import pi_info from ..exc import ( PinInvalidPull, PinInvalidEdges, @@ -198,10 +199,14 @@ class NativePin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} + PI_INFO = None + def __new__(cls, number): if not cls._PINS: cls._MEM = GPIOMemory() PINS_CLEANUP.append(cls._MEM.close) + if cls.PI_INFO is None: + cls.PI_INFO = pi_info() if not (0 <= number < 54): raise ValueError('invalid pin %d specified (must be 0..53)' % number) try: @@ -230,7 +235,7 @@ class NativePin(Pin): self._change_thread = None self._change_event = Event() self.function = 'input' - self.pull = 'up' if number in (2, 3) else 'floating' + self.pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self.bounce = None self.edges = 'both' return self @@ -245,7 +250,7 @@ class NativePin(Pin): def close(self): self.when_changed = None self.function = 'input' - self.pull = 'up' if self.number in (2, 3) else 'floating' + self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating' def _get_function(self): return self.GPIO_FUNCTION_NAMES[(self._MEM[self._func_offset] >> self._func_shift) & 7] @@ -278,7 +283,7 @@ class NativePin(Pin): def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) - if value != 'up' and self._number in (2, 3): + if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self.number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: value = self.GPIO_PULL_UPS[value] diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index d3b9b6c..c591ba6 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -9,6 +9,7 @@ str = type('') import pigpio from . import Pin +from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, @@ -93,7 +94,14 @@ class PiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} + PI_INFO = None + def __new__(cls, number, host='localhost', port=8888): + # XXX What about remote pins? This should probably be instance + # specific rather than class specific for pigpio. Need to check how + # to query remote info though... + if cls.PI_INFO is None: + cls.PI_INFO = pi_info() try: return cls._PINS[(host, port, number)] except KeyError: @@ -107,7 +115,7 @@ class PiGPIOPin(Pin): self._host = host self._port = port self._number = number - self._pull = 'up' if number in (2, 3) else 'floating' + self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self._pwm = False self._bounce = None self._when_changed = None @@ -150,7 +158,7 @@ class PiGPIOPin(Pin): self.frequency = None self.when_changed = None self.function = 'input' - self.pull = 'up' if self.number in (2, 3) else 'floating' + self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating' def _get_function(self): return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)] @@ -187,7 +195,7 @@ class PiGPIOPin(Pin): def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) - if value != 'up' and self._number in (2, 3): + if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value]) diff --git a/gpiozero/pins/rpigpio.py b/gpiozero/pins/rpigpio.py index cc55fdb..99ca0d9 100644 --- a/gpiozero/pins/rpigpio.py +++ b/gpiozero/pins/rpigpio.py @@ -9,6 +9,7 @@ str = type('') from RPi import GPIO from . import Pin +from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, @@ -71,17 +72,21 @@ class RPiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} + PI_INFO = None + def __new__(cls, number): if not cls._PINS: GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) + if cls.PI_INFO is None: + cls.PI_INFO = pi_info() try: return cls._PINS[number] except KeyError: self = super(RPiGPIOPin, cls).__new__(cls) cls._PINS[number] = self self._number = number - self._pull = 'up' if number in (2, 3) else 'floating' + self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self._pwm = None self._frequency = None self._duty_cycle = None @@ -108,7 +113,7 @@ class RPiGPIOPin(Pin): GPIO.setup(self._number, GPIO.OUT, initial=state) def input_with_pull(self, pull): - if pull != 'up' and self._number in (2, 3): + if pull != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[pull]) @@ -154,7 +159,7 @@ class RPiGPIOPin(Pin): def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) - if value != 'up' and self._number in (2, 3): + if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: GPIO.setup(self._number, GPIO.IN, self.GPIO_PULL_UPS[value]) diff --git a/gpiozero/pins/rpio.py b/gpiozero/pins/rpio.py index 776894d..8449ad6 100644 --- a/gpiozero/pins/rpio.py +++ b/gpiozero/pins/rpio.py @@ -14,6 +14,7 @@ import RPIO.PWM from RPIO.Exceptions import InvalidChannelException from . import Pin, PINS_CLEANUP +from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, @@ -62,6 +63,8 @@ class RPIOPin(Pin): GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()} GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} + PI_INFO = None + def __new__(cls, number): if not cls._PINS: RPIO.setmode(RPIO.BCM) @@ -72,13 +75,15 @@ class RPIOPin(Pin): PINS_CLEANUP.append(RPIO.PWM.cleanup) PINS_CLEANUP.append(RPIO.stop_waiting_for_interrupts) PINS_CLEANUP.append(RPIO.cleanup) + if cls.PI_INFO is None: + cls.PI_INFO = pi_info() try: return cls._PINS[number] except KeyError: self = super(RPIOPin, cls).__new__(cls) cls._PINS[number] = self self._number = number - self._pull = 'up' if number in (2, 3) else 'floating' + self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' self._pwm = False self._duty_cycle = None self._bounce = None @@ -145,7 +150,7 @@ class RPIOPin(Pin): def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) - if value != 'up' and self._number in (2, 3): + if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: RPIO.setup(self._number, RPIO.IN, self.GPIO_PULL_UPS[value])