44import sys
55import time
66from contextlib import contextmanager
7- from typing import Generator , Optional
7+ from typing import Generator
88from pathlib import Path
99import argparse
10+
1011try :
1112 import gpiod
1213except 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'
1883path = Path ('/etc/nv_tegra_release' )
1984if 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
3393parser = argparse .ArgumentParser (description = 'Flash and control an ESP32 microcontroller from a Jetson board' )
3494
3898parser .add_argument ('--bootloader' , default = 'build/bootloader/bootloader.bin' , help = 'Path to bootloader' )
3999parser .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' )
41102parser .add_argument ('--firmware' , default = 'build/lizard.bin' , help = 'Path to firmware binary' )
42103parser .add_argument ('--chip' , choices = ['esp32' , 'esp32s3' ], default = 'esp32' , help = 'ESP chip type' )
43104parser .add_argument ('-d' , '--dry-run' , action = 'store_true' , help = 'Dry run' )
47108ON = 1 if args .nand else 0
48109OFF = 0 if args .nand else 1
49110DRY_RUN = args .dry_run
111+ SWAP = args .swap
50112CHIP = args .chip
51113DEVICE = args .device
52114FLASH_FREQ = {'esp32' : '40m' , 'esp32s3' : '80m' }.get (CHIP , '40m' )
55117PARTITION_TABLE = args .partition_table
56118FIRMWARE = 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
65132def _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
77141def _release_pins () -> None :
78142 """Release pins."""
79- if JETPACK :
80- en .release ()
81- g0 .release ()
143+ gpio .release ()
82144
83145
84146@contextmanager
85147def _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
100157def 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:
123179def _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:
184238def 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
190245def 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
196252def print_ok (message : str ) -> None :
@@ -207,6 +263,8 @@ def print_fail(message: str) -> None:
207263
208264def 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