Skip to content

Commit 265d17a

Browse files
Add support for old Robot Brains with swapped pins (#187)
* Add support for old RB with swapped pins * move wait time to set_en and set_g0 * Apply suggestion from @falkoschindler * Apply suggestion from @falkoschindler * add support for use of espresso in docker * refactor GPIO controller implementation to support gpiod v1 and v2 APIs * some more cleanup --------- Co-authored-by: Falko Schindler <falko@zauberzeug.com>
1 parent ac74817 commit 265d17a

File tree

1 file changed

+98
-40
lines changed

1 file changed

+98
-40
lines changed

espresso.py

Lines changed: 98 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,91 @@
44
import sys
55
import time
66
from contextlib import contextmanager
7-
from typing import Generator, Optional
7+
from typing import Generator
88
from pathlib import Path
99
import argparse
10+
1011
try:
1112
import gpiod
1213
except ImportError:
13-
gpiod = None # type: ignore[assignment]
14-
print('gpiod module not found. Please install it using "sudo apt install python3-gpiod". Ignore this error if you are not on a Jetson board.')
14+
GPIOD_VERSION = None
15+
else:
16+
GPIOD_VERSION = 2 if hasattr(gpiod, 'request_lines') else 1
17+
18+
19+
class GpioController:
20+
def __init__(self, en: str, g0: str) -> None:
21+
pass
22+
23+
def request_outputs(self, consumer: str) -> None:
24+
"""Request output lines."""
25+
26+
def set_value(self, key: str, value: int) -> None:
27+
"""Set the value of a line."""
28+
29+
def release(self) -> None:
30+
"""Release the lines."""
31+
32+
33+
class GpioControllerV1(GpioController):
34+
35+
def __init__(self, en: str, g0: str) -> None:
36+
chip = gpiod.Chip('gpiochip0')
37+
self._lines = {
38+
'en': chip.find_line(en),
39+
'g0': chip.find_line(g0),
40+
}
41+
42+
def request_outputs(self, consumer: str) -> None:
43+
for line in self._lines.values():
44+
line.request(consumer=consumer, type=gpiod.LINE_REQ_DIR_OUT)
45+
46+
def set_value(self, key: str, value: int) -> None:
47+
self._lines[key].set_value(value)
48+
49+
def release(self) -> None:
50+
for line in self._lines.values():
51+
line.release()
52+
53+
54+
class GpioControllerV2(GpioController):
55+
CHIP_PATH = '/dev/gpiochip0'
56+
57+
def __init__(self, en: str, g0: str) -> None:
58+
chip = gpiod.Chip(self.CHIP_PATH)
59+
self._offsets = {
60+
'en': chip.line_offset_from_id(en),
61+
'g0': chip.line_offset_from_id(g0),
62+
}
63+
chip.close()
64+
self._request = None
65+
66+
def request_outputs(self, consumer: str) -> None:
67+
from gpiod.line import Direction # pylint: disable=import-outside-toplevel
68+
config = {offset: gpiod.LineSettings(direction=Direction.OUTPUT) for offset in self._offsets.values()}
69+
self._request = gpiod.request_lines(self.CHIP_PATH, consumer=consumer, config=config)
70+
71+
def set_value(self, key: str, value: int) -> None:
72+
from gpiod.line import Value # pylint: disable=import-outside-toplevel
73+
if self._request:
74+
self._request.set_value(self._offsets[key], Value.ACTIVE if value else Value.INACTIVE)
1575

76+
def release(self) -> None:
77+
if self._request:
78+
self._request.release()
79+
self._request = None
1680

17-
JETPACK: Optional[int] = None
81+
82+
DEFAULT_DEVICE = '/dev/tty.SLAB_USBtoUART'
1883
path = Path('/etc/nv_tegra_release')
1984
if path.exists() and (match := re.search(r'R(\d+)', path.read_text(encoding='utf-8'))):
2085
major = int(match.group(1))
2186
if major == 35:
22-
JETPACK = 5
87+
DEFAULT_DEVICE = '/dev/ttyTHS0'
2388
elif major == 36:
24-
JETPACK = 6
89+
DEFAULT_DEVICE = '/dev/ttyTHS1'
2590
else:
2691
raise RuntimeError(f'Unsupported L4T (Linux for Tegra) version: {major}')
27-
DEFAULT_DEVICE = {
28-
5: '/dev/ttyTHS0',
29-
6: '/dev/ttyTHS1',
30-
None: '/dev/tty.SLAB_USBtoUART',
31-
}[JETPACK]
3292

3393
parser = argparse.ArgumentParser(description='Flash and control an ESP32 microcontroller from a Jetson board')
3494

@@ -38,6 +98,7 @@
3898
parser.add_argument('--bootloader', default='build/bootloader/bootloader.bin', help='Path to bootloader')
3999
parser.add_argument('--partition-table', default='build/partition_table/partition-table.bin',
40100
help='Path to partition table')
101+
parser.add_argument('--swap', action='store_true', help='Swap En and G0 pins for piggyboard version lower than v0.5')
41102
parser.add_argument('--firmware', default='build/lizard.bin', help='Path to firmware binary')
42103
parser.add_argument('--chip', choices=['esp32', 'esp32s3'], default='esp32', help='ESP chip type')
43104
parser.add_argument('-d', '--dry-run', action='store_true', help='Dry run')
@@ -47,6 +108,7 @@
47108
ON = 1 if args.nand else 0
48109
OFF = 0 if args.nand else 1
49110
DRY_RUN = args.dry_run
111+
SWAP = args.swap
50112
CHIP = args.chip
51113
DEVICE = args.device
52114
FLASH_FREQ = {'esp32': '40m', 'esp32s3': '80m'}.get(CHIP, '40m')
@@ -55,54 +117,48 @@
55117
PARTITION_TABLE = args.partition_table
56118
FIRMWARE = args.firmware
57119

58-
if JETPACK:
59-
chip = gpiod.Chip('gpiochip0')
60-
en = chip.find_line('PR.04')
61-
g0 = chip.find_line('PAC.06')
120+
EN = 'PR.04'
121+
G0 = 'PAC.06'
122+
if SWAP:
123+
EN, G0 = G0, EN
124+
gpio = {
125+
None: GpioController,
126+
1: GpioControllerV1,
127+
2: GpioControllerV2,
128+
}[GPIOD_VERSION](EN, G0)
62129

63130

64131
@contextmanager
65132
def _pin_config() -> Generator[None, None, None]:
66133
"""Configure the EN and G0 pins to control the microcontroller."""
67-
if JETPACK:
68-
print_bold('Configuring EN and G0 pins...')
69-
en.request(consumer='espresso', type=gpiod.LINE_REQ_DIR_OUT)
70-
g0.request(consumer='espresso', type=gpiod.LINE_REQ_DIR_OUT)
71-
time.sleep(0.5)
134+
print_bold('Configuring EN and G0 pins...')
135+
gpio.request_outputs(consumer='espresso')
136+
time.sleep(0.5)
72137
yield
73-
if JETPACK:
74-
_release_pins()
138+
_release_pins()
75139

76140

77141
def _release_pins() -> None:
78142
"""Release pins."""
79-
if JETPACK:
80-
en.release()
81-
g0.release()
143+
gpio.release()
82144

83145

84146
@contextmanager
85147
def _flash_mode() -> Generator[None, None, None]:
86148
"""Bring the microcontroller into flash mode."""
87-
if JETPACK:
88-
print_bold('Bringing the microcontroller into flash mode...')
89-
set_en(ON)
90-
time.sleep(0.5)
91-
set_g0(ON)
92-
time.sleep(0.5)
93-
set_en(OFF)
94-
time.sleep(0.5)
149+
print_bold('Bringing the microcontroller into flash mode...')
150+
set_en(ON)
151+
set_g0(ON)
152+
set_en(OFF)
95153
yield
96-
if JETPACK:
97-
_reset()
154+
_reset()
98155

99156

100157
def enable() -> None:
101158
"""Enable the microcontroller."""
102159
print_bold('Enabling the microcontroller...')
103160
with _pin_config():
104161
set_g0(OFF)
105-
time.sleep(0.5)
106162
set_en(OFF)
107163

108164

@@ -123,9 +179,7 @@ def reset() -> None:
123179
def _reset() -> None:
124180
"""Set pins to reset the microcontroller."""
125181
set_g0(OFF)
126-
time.sleep(0.5)
127182
set_en(ON)
128-
time.sleep(0.5)
129183
set_en(OFF)
130184

131185

@@ -184,13 +238,15 @@ def run(*run_args: str) -> bool:
184238
def set_en(value: int) -> None:
185239
print(f' Setting EN pin to {value}')
186240
if not DRY_RUN:
187-
en.set_value(value)
241+
gpio.set_value('en', value)
242+
time.sleep(0.5)
188243

189244

190245
def set_g0(value: int) -> None:
191246
print(f' Setting G0 pin to {value}')
192247
if not DRY_RUN:
193-
g0.set_value(value)
248+
gpio.set_value('g0', value)
249+
time.sleep(0.5)
194250

195251

196252
def print_ok(message: str) -> None:
@@ -207,6 +263,8 @@ def print_fail(message: str) -> None:
207263

208264
def main(command: str) -> None:
209265
print_ok('Espresso dry-running...' if DRY_RUN else 'Espresso running...')
266+
if GPIOD_VERSION is None and not DRY_RUN:
267+
print_fail('Module gpiod not found. Espresso is not able to control the EN and G0 pins.')
210268
if command == 'enable':
211269
enable()
212270
elif command == 'disable':

0 commit comments

Comments
 (0)