Skip to content

Commit 3203734

Browse files
authored
Merge pull request #23 from mumuhhh/v2_gpio
feat: GPIO compatible
2 parents 489da82 + f9af98f commit 3203734

7 files changed

Lines changed: 138 additions & 112 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
__pycache__/
2-
mini_bdx_runtime.egg-info/
2+
mini_bdx_runtime.egg-info/
3+
build
4+
dist

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ git checkout v2
116116
pip install -e .
117117
```
118118

119+
In Raspberry Pi 5, you need to perform the following operations
120+
121+
```bash
122+
pip uninstall -y RPi.GPIO
123+
pip install lgpio
124+
```
125+
119126

120127
## Test the IMU
121128

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,64 @@
1-
import RPi.GPIO as GPIO
2-
import numpy as np
1+
import board
2+
import pwmio
3+
import math
34
import time
45

5-
LEFT_ANTENNA_PIN = 13
6-
RIGHT_ANTENNA_PIN = 12
6+
LEFT_ANTENNA_PIN = board.D13
7+
RIGHT_ANTENNA_PIN = board.D12
78
LEFT_SIGN = 1
89
RIGHT_SIGN = -1
10+
MIN_UPDATE_INTERVAL = 1 / 50 # 20ms
911

1012

11-
class Antennas:
12-
def __init__(self):
13-
14-
GPIO.setmode(GPIO.BCM)
15-
GPIO.setup(LEFT_ANTENNA_PIN, GPIO.OUT)
16-
GPIO.setup(RIGHT_ANTENNA_PIN, GPIO.OUT)
17-
18-
self.pwm1 = GPIO.PWM(LEFT_ANTENNA_PIN, 50)
19-
self.pwm2 = GPIO.PWM(RIGHT_ANTENNA_PIN, 50)
13+
def value_to_duty_cycle(v):
14+
pulse_width_ms = 1.5 + (v * 0.5) # 1ms to 2ms
15+
duty_cycle = int((pulse_width_ms / 20) * 65535)
16+
return min(max(duty_cycle, 3277), 6553)
2017

21-
self.pwm1.start(0)
22-
self.pwm2.start(0)
2318

24-
def map_input_to_angle(self, value):
25-
return 90 + (value * 90)
19+
class Antennas:
20+
def __init__(self):
21+
neutral_duty = value_to_duty_cycle(0)
22+
self.pwm_left = pwmio.PWMOut(LEFT_ANTENNA_PIN, frequency=50, duty_cycle=neutral_duty)
23+
self.pwm_right = pwmio.PWMOut(RIGHT_ANTENNA_PIN, frequency=50, duty_cycle=neutral_duty)
2624

2725
def set_position_left(self, position):
28-
self.set_position(1, position, LEFT_SIGN)
26+
self.set_position(self.pwm_left, position, LEFT_SIGN)
2927

3028
def set_position_right(self, position):
31-
self.set_position(2, position, RIGHT_SIGN)
32-
33-
def set_position(self, servo, value, sign=1):
34-
"""
35-
Moves the servo based on an input value in the range [-1, 1].
36-
:param servo: 1 for the first servo, 2 for the second servo
37-
:param value: A float between -1 and 1
38-
"""
29+
self.set_position(self.pwm_right, position, RIGHT_SIGN)
3930

31+
def set_position(self, pwm, value, sign=1):
4032
# if value == 0:
4133
# return
4234
if -1 <= value <= 1:
43-
angle = self.map_input_to_angle(value * sign)
44-
45-
duty = 2 + (angle / 18) # Convert angle to duty cycle (1ms-2ms)
46-
if servo == 1:
47-
self.pwm1.ChangeDutyCycle(duty)
48-
elif servo == 2:
49-
self.pwm2.ChangeDutyCycle(duty)
50-
else:
51-
print("Invalid servo number!")
52-
# time.sleep(0.01) # Allow time for movement
35+
duty_cycle = value_to_duty_cycle(value * sign) # Convert value to duty cycle (1ms-2ms)
36+
pwm.duty_cycle = duty_cycle
5337
else:
5438
print("Invalid input! Enter a value between -1 and 1.")
5539

5640
def stop(self):
57-
self.pwm1.stop()
58-
self.pwm2.stop()
59-
GPIO.cleanup()
41+
time.sleep(MIN_UPDATE_INTERVAL)
42+
self.set_position_left(0)
43+
self.set_position_right(0)
44+
time.sleep(MIN_UPDATE_INTERVAL)
45+
self.pwm_left.deinit()
46+
self.pwm_right.deinit()
6047

6148

6249
if __name__ == "__main__":
6350
antennas = Antennas()
6451

65-
s = time.time()
66-
while True:
67-
antennas.set_position_left(np.sin(2 * np.pi * 1 * time.time()))
68-
antennas.set_position_right(np.sin(2 * np.pi * 1 * time.time()))
52+
try:
53+
start_time = time.monotonic()
54+
current_time = start_time
6955

70-
time.sleep(1 / 50)
56+
while current_time - start_time < 5:
57+
value = math.sin(2 * math.pi * 1 * current_time)
58+
antennas.set_position_left(value)
59+
antennas.set_position_right(value)
60+
time.sleep(MIN_UPDATE_INTERVAL)
61+
current_time = time.monotonic()
7162

72-
if time.time() - s > 5:
73-
break
74-
63+
finally:
64+
antennas.stop()
Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,58 @@
1-
import RPi.GPIO as GPIO
2-
import numpy as np
1+
import board
2+
import digitalio
3+
import random
34
import time
4-
from threading import Thread
5+
from threading import Thread, Event
56

6-
LEFT_EYE_GPIO = 24
7-
RIGHT_EYE_GPIO = 23
7+
LEFT_EYE_PIN = board.D24
8+
RIGHT_EYE_PIN = board.D23
89

910

1011
class Eyes:
11-
def __init__(self):
12-
GPIO.setmode(GPIO.BCM)
13-
GPIO.setwarnings(False)
14-
GPIO.setup(RIGHT_EYE_GPIO, GPIO.OUT)
15-
GPIO.setup(LEFT_EYE_GPIO, GPIO.OUT)
12+
def __init__(self, blink_duration=0.1, min_interval=1.0, max_interval=4.0):
13+
self.left_eye = digitalio.DigitalInOut(LEFT_EYE_PIN)
14+
self.left_eye.direction = digitalio.Direction.OUTPUT
1615

17-
GPIO.output(RIGHT_EYE_GPIO, GPIO.HIGH)
18-
GPIO.output(LEFT_EYE_GPIO, GPIO.HIGH)
16+
self.right_eye = digitalio.DigitalInOut(RIGHT_EYE_PIN)
17+
self.right_eye.direction = digitalio.Direction.OUTPUT
1918

20-
self.blink_duration = 0.1
19+
self.blink_duration = blink_duration
20+
self.min_interval = min_interval
21+
self.max_interval = max_interval
2122

22-
Thread(target=self.run, daemon=True).start()
23+
self._stop_event = Event()
24+
self._thread = Thread(target=self.run, daemon=True)
25+
self._thread.start()
2326

24-
def run(self):
25-
while True:
26-
GPIO.output(RIGHT_EYE_GPIO, GPIO.LOW)
27-
GPIO.output(LEFT_EYE_GPIO, GPIO.LOW)
28-
time.sleep(self.blink_duration)
29-
GPIO.output(RIGHT_EYE_GPIO, GPIO.HIGH)
30-
GPIO.output(LEFT_EYE_GPIO, GPIO.HIGH)
31-
32-
next_blink = np.random.rand() * 4 # seconds
27+
def _set_eyes(self, state):
28+
self.left_eye.value = state
29+
self.right_eye.value = state
3330

34-
time.sleep(next_blink)
31+
def run(self):
32+
try:
33+
while not self._stop_event.is_set():
34+
self._set_eyes(False)
35+
time.sleep(self.blink_duration)
36+
self._set_eyes(True)
37+
next_blink = random.uniform(self.min_interval, self.max_interval)
38+
time.sleep(next_blink)
39+
except Exception as err:
40+
print(f"Error in eye thread: {err}")
41+
self._stop_event.set()
42+
43+
def stop(self):
44+
self._stop_event.set()
45+
self._thread.join()
46+
self._set_eyes(False)
47+
self.left_eye.deinit()
48+
self.right_eye.deinit()
3549

3650

3751
if __name__ == "__main__":
38-
e = Eyes()
39-
while True:
40-
time.sleep(1)
52+
e = Eyes()
53+
try:
54+
while True:
55+
time.sleep(1)
56+
finally:
57+
e.stop()
58+
Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
1-
import RPi.GPIO as GPIO
2-
import numpy as np
3-
4-
5-
LEFT_FOOT_PIN = 22
6-
RIGHT_FOOT_PIN = 27
1+
import board
2+
import digitalio
3+
import time
74

5+
LEFT_FOOT_PIN = board.D22
6+
RIGHT_FOOT_PIN = board.D27
87

98
class FeetContacts:
109
def __init__(self):
11-
GPIO.setwarnings(False) # Ignore warning for now
12-
GPIO.setmode(GPIO.BCM) # Use physical pin numbering
13-
GPIO.setup(LEFT_FOOT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
14-
GPIO.setup(RIGHT_FOOT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
10+
self.left_foot = digitalio.DigitalInOut(LEFT_FOOT_PIN)
11+
self.left_foot.direction = digitalio.Direction.INPUT
12+
self.left_foot.pull = digitalio.Pull.UP
13+
14+
self.right_foot = digitalio.DigitalInOut(RIGHT_FOOT_PIN)
15+
self.right_foot.direction = digitalio.Direction.INPUT
16+
self.right_foot.pull = digitalio.Pull.UP
1517

1618
def get(self):
17-
left = False
18-
right = False
19-
if GPIO.input(LEFT_FOOT_PIN) == GPIO.LOW:
20-
left = True
21-
if GPIO.input(RIGHT_FOOT_PIN) == GPIO.LOW:
22-
right = True
23-
return np.array([left, right])
19+
left = not self.left_foot.value
20+
right = not self.right_foot.value
21+
return [left, right]
2422

23+
def stop(self):
24+
self.left_foot.deinit()
25+
self.right_foot.deinit()
2526

2627
if __name__ == "__main__":
27-
import time
28-
2928
feet_contacts = FeetContacts()
30-
while True:
31-
print(feet_contacts.get())
32-
time.sleep(0.05)
29+
try:
30+
while True:
31+
print(feet_contacts.get())
32+
time.sleep(0.05)
33+
finally:
34+
feet_contacts.stop()
Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
1-
import RPi.GPIO as GPIO
1+
import board
2+
import digitalio
23
import time
34

4-
PROJECTOR_GPIO = 25
5+
PROJECTOR_GPIO = board.D25
6+
57

68
class Projector:
79
def __init__(self):
8-
GPIO.setmode(GPIO.BCM)
9-
GPIO.setwarnings(False)
10-
GPIO.setup(PROJECTOR_GPIO, GPIO.OUT)
11-
12-
GPIO.output(PROJECTOR_GPIO, GPIO.LOW)
10+
self.project = digitalio.DigitalInOut(PROJECTOR_GPIO)
11+
self.project.direction = digitalio.Direction.OUTPUT
1312
self.on = False
1413

1514
def switch(self):
1615
self.on = not self.on
1716

18-
if self.on:
19-
GPIO.output(PROJECTOR_GPIO, GPIO.HIGH)
20-
else:
21-
GPIO.output(PROJECTOR_GPIO, GPIO.LOW)
17+
self.project.value = self.on
18+
19+
def stop(self):
20+
self.project.value = False
21+
self.project.deinit()
2222

2323

2424
if __name__ == "__main__":
2525
p = Projector()
26-
while True:
27-
28-
p.switch()
29-
time.sleep(1)
26+
try:
27+
while True:
28+
p.switch()
29+
time.sleep(1)
30+
finally:
31+
p.stop()

scripts/v2_rl_walk_mujoco.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ def run(self):
330330
except KeyboardInterrupt:
331331
if self.duck_config.antennas:
332332
self.antennas.stop()
333+
if self.duck_config.eyes:
334+
self.eyes.stop()
335+
if self.duck_config.projector:
336+
self.projector.stop()
337+
self.feet_contacts.stop()
333338

334339
if self.save_obs:
335340
pickle.dump(self.saved_obs, open("robot_saved_obs.pkl", "wb"))

0 commit comments

Comments
 (0)