Skip to content

Commit ad088b4

Browse files
committed
Merge branch 'lgpio-native' of https://github.com/frank-pet/Adafruit_Blinka into lgpio-native
2 parents 58a8e17 + 651192b commit ad088b4

File tree

3 files changed

+134
-72
lines changed

3 files changed

+134
-72
lines changed

src/adafruit_blinka/microcontroller/bcm2711/pin.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
#
33
# SPDX-License-Identifier: MIT
44
"""Broadcom BCM2711 pin names"""
5-
from RPi import GPIO
6-
from adafruit_blinka.microcontroller.bcm283x.pin import Pin
75

8-
GPIO.setmode(GPIO.BCM) # Use BCM pins D4 = GPIO #4
9-
GPIO.setwarnings(False) # shh!
6+
# pylint: disable=unused-import
7+
# importing CHIP to make the lgpio CHIP handle available
8+
from adafruit_blinka.microcontroller.bcm283x.pin import Pin, CHIP
9+
10+
# pylint: enable=unused-import
1011

1112
D0 = Pin(0)
1213
D1 = Pin(1)

src/adafruit_blinka/microcontroller/bcm283x/pin.py

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,68 @@
22
#
33
# SPDX-License-Identifier: MIT
44
"""Broadcom BCM283x pin names"""
5-
from RPi import GPIO
5+
from pathlib import Path
6+
import lgpio
67

7-
GPIO.setmode(GPIO.BCM) # Use BCM pins D4 = GPIO #4
8-
GPIO.setwarnings(False) # shh!
8+
9+
def _get_gpiochip():
10+
"""
11+
Determines the handle of the GPIO chip device to access.
12+
13+
iterate through sysfs to find a GPIO chip device with a driver known to be
14+
used for userspace GPIO access.
15+
"""
16+
for dev in Path("/sys/bus/gpio/devices").glob("gpiochip*"):
17+
drivers = set((dev / "of_node/compatible").read_text().split("\0"))
18+
# check if driver names are intended for userspace control
19+
if drivers & {
20+
"raspberrypi,rp1-gpio",
21+
"raspberrypi,bcm2835-gpio",
22+
"raspberrypi,bcm2711-gpio",
23+
}:
24+
return lgpio.gpiochip_open(int(dev.name[-1]))
25+
# return chip0 as a fallback
26+
return lgpio.gpiochip_open(0)
27+
28+
29+
CHIP = _get_gpiochip()
930

1031

1132
class Pin:
1233
"""Pins dont exist in CPython so...lets make our own!"""
1334

14-
IN = 0
15-
OUT = 1
1635
LOW = 0
1736
HIGH = 1
18-
PULL_NONE = 0
19-
PULL_UP = 1
20-
PULL_DOWN = 2
37+
OFF = LOW
38+
ON = HIGH
39+
40+
# values of lg mode constants
41+
PULL_NONE = 0x80
42+
PULL_UP = 0x20
43+
PULL_DOWN = 0x40
44+
ACTIVE_LOW = 0x02
45+
46+
# drive mode lg constants
47+
OPEN_DRAIN = 0x04
48+
IN = 0x0100
49+
OUT = 0x0200
50+
51+
# LG mode constants
52+
_LG_ALERT = 0x400
53+
_LG_GROUP = 0x800
54+
_LG_MODES = IN | OUT | _LG_ALERT | _LG_GROUP
55+
_LG_PULLS = PULL_NONE | PULL_UP | PULL_NONE | ACTIVE_LOW
56+
_LG_DRIVES = OPEN_DRAIN
2157

2258
id = None
2359
_value = LOW
2460
_mode = IN
2561

62+
# we want exceptions
63+
lgpio.exceptions = True
64+
2665
def __init__(self, bcm_number):
27-
self.id = bcm_number
66+
self.id = bcm_number # pylint: disable=invalid-name
2867

2968
def __repr__(self):
3069
return str(self.id)
@@ -35,40 +74,60 @@ def __eq__(self, other):
3574
def init(self, mode=IN, pull=None):
3675
"""Initialize the Pin"""
3776
if mode is not None:
38-
if mode == self.IN:
39-
self._mode = self.IN
40-
GPIO.setup(self.id, GPIO.IN)
77+
if mode == Pin.IN:
78+
self._mode = Pin.IN
79+
self._set_gpio_mode_in()
4180
elif mode == self.OUT:
42-
self._mode = self.OUT
43-
GPIO.setup(self.id, GPIO.OUT)
81+
self._mode = Pin.OUT
82+
Pin._check_result(lgpio.gpio_claim_output(CHIP, self.id, Pin.LOW))
4483
else:
45-
raise RuntimeError("Invalid mode for pin: %s" % self.id)
84+
raise RuntimeError(f"Invalid mode for pin: {self.id}")
4685
if pull is not None:
47-
if self._mode != self.IN:
48-
raise RuntimeError("Cannot set pull resistor on output")
49-
if pull == self.PULL_UP:
50-
GPIO.setup(self.id, GPIO.IN, pull_up_down=GPIO.PUD_UP)
51-
elif pull == self.PULL_DOWN:
52-
GPIO.setup(self.id, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
86+
if self._mode != Pin.IN:
87+
raise RuntimeError("Can only set pull resistor on input")
88+
if pull in {Pin.PULL_UP, Pin.PULL_DOWN, Pin.PULL_NONE}:
89+
self._set_gpio_mode_in(lflags=pull)
5390
else:
54-
raise RuntimeError("Invalid pull for pin: %s" % self.id)
91+
raise RuntimeError(f"Invalid pull for pin: {self.id}")
5592

5693
def value(self, val=None):
5794
"""Set or return the Pin Value"""
5895
if val is not None:
59-
if val == self.LOW:
96+
if val == Pin.LOW:
6097
self._value = val
61-
GPIO.output(self.id, val)
62-
elif val == self.HIGH:
98+
Pin._check_result(lgpio.gpio_write(CHIP, self.id, val))
99+
elif val == Pin.HIGH:
63100
self._value = val
64-
GPIO.output(self.id, val)
101+
Pin._check_result(lgpio.gpio_write(CHIP, self.id, val))
65102
else:
66103
raise RuntimeError("Invalid value for pin")
67104
return None
68-
return GPIO.input(self.id)
105+
return Pin._check_result(lgpio.gpio_read(CHIP, self.id))
106+
107+
@staticmethod
108+
def _check_result(result):
109+
"""
110+
convert any result other than zero to a text message and pass it back
111+
as a runtime exception. Typical usage: use the lgpio call as the
112+
argument.
113+
"""
114+
if result < 0:
115+
raise RuntimeError(lgpio.error_text(result))
116+
return result
117+
118+
def _set_gpio_mode_in(self, lflags=0):
119+
"""
120+
claim a gpio as input, or modify the flags (PULL_UP, PULL_DOWN, ... )
121+
"""
122+
# This gpio_free may seem redundant, but is required when
123+
# changing the line-flags of an already acquired input line
124+
try:
125+
lgpio.gpio_free(CHIP, self.id)
126+
except lgpio.error:
127+
pass
128+
Pin._check_result(lgpio.gpio_claim_input(CHIP, self.id, lFlags=lflags))
69129

70130

71-
# Pi 1B rev1 only?
72131
D0 = Pin(0)
73132
D1 = Pin(1)
74133

src/adafruit_blinka/microcontroller/bcm283x/pwmio/PWMOut.py

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
# pylint: disable=invalid-name
12
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
23
#
34
# SPDX-License-Identifier: MIT
4-
"""Custom PWMOut Wrapper for Rpi.GPIO PWM Class"""
5-
from RPi import GPIO
5+
# pylint: enable=invalid-name
6+
""" PWMOut Class for lgpio lg library tx_pwm library """
67

7-
GPIO.setmode(GPIO.BCM) # Use BCM pins D4 = GPIO #4
8-
GPIO.setwarnings(False) # shh!
8+
import lgpio
9+
import board # need board to get access to the CHIP object in the pin module
910

1011

1112
# pylint: disable=unnecessary-pass
@@ -18,47 +19,46 @@ class PWMError(IOError):
1819
# pylint: enable=unnecessary-pass
1920

2021

21-
class PWMOut:
22+
class PWMOut: # pylint: disable=invalid-name
2223
"""Pulse Width Modulation Output Class"""
2324

2425
def __init__(self, pin, *, frequency=500, duty_cycle=0, variable_frequency=False):
25-
self._pwmpin = None
26+
if variable_frequency:
27+
print("Variable Frequency is not supported, ignoring...")
28+
self._pin = pin
29+
result = lgpio.gpio_claim_output(
30+
board.pin.CHIP, self._pin.id, lFlags=lgpio.SET_PULL_NONE
31+
)
32+
if result < 0:
33+
raise RuntimeError(lgpio.error_text(result))
34+
self._enabled = False
35+
self._deinited = False
2636
self._period = 0
27-
self._open(pin, duty_cycle, frequency, variable_frequency)
37+
# set frequency
38+
self._frequency = frequency
39+
# set duty
40+
self.duty_cycle = duty_cycle
41+
self.enabled = True
2842

2943
def __del__(self):
3044
self.deinit()
3145

3246
def __enter__(self):
3347
return self
3448

35-
def __exit__(self, t, value, traceback):
49+
def __exit__(self, exc_type, exc_val, exc_tb):
3650
self.deinit()
3751

38-
def _open(self, pin, duty=0, freq=500, variable_frequency=False):
39-
self._pin = pin
40-
GPIO.setup(pin.id, GPIO.OUT)
41-
self._pwmpin = GPIO.PWM(pin.id, freq)
42-
43-
if variable_frequency:
44-
print("Variable Frequency is not supported, continuing without it...")
45-
46-
# set frequency
47-
self.frequency = freq
48-
# set duty
49-
self.duty_cycle = duty
50-
51-
self.enabled = True
52-
5352
def deinit(self):
5453
"""Deinit the PWM."""
55-
if self._pwmpin is not None:
56-
self._pwmpin.stop()
57-
GPIO.cleanup(self._pin.id)
58-
self._pwmpin = None
54+
if not self._deinited:
55+
if self.enabled:
56+
self._enabled = False # turn off the pwm
57+
self._deinited = True
5958

6059
def _is_deinited(self):
61-
if self._pwmpin is None:
60+
"""raise Value error if the object has been de-inited"""
61+
if self._deinited:
6262
raise ValueError(
6363
"Object has been deinitialize and can no longer "
6464
"be used. Create a new object."
@@ -109,7 +109,8 @@ def duty_cycle(self, duty_cycle):
109109
duty_cycle /= 65535.0
110110

111111
self._duty_cycle = duty_cycle
112-
self._pwmpin.ChangeDutyCycle(round(self._duty_cycle * 100))
112+
if self._enabled:
113+
self.enabled = True # turn on with new values
113114

114115
@property
115116
def frequency(self):
@@ -129,8 +130,9 @@ def frequency(self, frequency):
129130
if not isinstance(frequency, (int, float)):
130131
raise TypeError("Invalid frequency type, should be int or float.")
131132

132-
self._pwmpin.ChangeFrequency(round(frequency))
133133
self._frequency = frequency
134+
if self.enabled:
135+
self.enabled = True # turn on with new values
134136

135137
@property
136138
def enabled(self):
@@ -147,19 +149,19 @@ def enabled(self):
147149
@enabled.setter
148150
def enabled(self, value):
149151
if not isinstance(value, bool):
150-
raise TypeError("Invalid enabled type, should be string.")
151-
152-
if value:
153-
self._pwmpin.start(round(self._duty_cycle * 100))
154-
else:
155-
self._pwmpin.stop()
152+
raise TypeError("Invalid enabled type, should be bool.")
156153

154+
frequency = self._frequency if value else 0
155+
duty_cycle = round(self._duty_cycle * 100)
157156
self._enabled = value
157+
result = lgpio.tx_pwm(board.pin.CHIP, self._pin.id, frequency, duty_cycle)
158+
if result < 0:
159+
raise RuntimeError(lgpio.error_text(result))
160+
return result
158161

159162
# String representation
160163
def __str__(self):
161-
return "pin %s (freq=%f Hz, duty_cycle=%f%%)" % (
162-
self._pin,
163-
self.frequency,
164-
self.duty_cycle,
164+
return (
165+
f"pin {self._pin} (freq={self.frequency:f} Hz, duty_cycle="
166+
f"{self.duty_cycle}({round(self.duty_cycle / 655.35)}%)"
165167
)

0 commit comments

Comments
 (0)