Skip to content

Commit 3cf7b8a

Browse files
authored
Merge pull request #89 from anecdata/gpio
Example using ESP32 GPIO pins from CircuitPython
2 parents 4c327a3 + 5e5a5a5 commit 3cf7b8a

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed

examples/gpio/esp32spi_gpio.py

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import time
2+
import random
3+
import board
4+
from digitalio import DigitalInOut, Direction
5+
import pulseio
6+
from adafruit_esp32spi import adafruit_esp32spi
7+
8+
9+
# ESP32SPI Digital and Analog Pin Reads & Writes
10+
11+
# This example targets a Feather M4 or ItsyBitsy M4 as the CircuitPython MCU,
12+
# along with either an ESP32 Feather or ESP32 Breakout as Wi-Fi co-processor.
13+
# You may need to choose different pins for other targets.
14+
15+
16+
def esp_reset_all():
17+
# esp.reset() will reset the ESP32 using its RST pin
18+
# side effect is re-initializing ESP32 pin modes and debug output
19+
esp.reset()
20+
time.sleep(1)
21+
# (re-)set NINA serial debug on ESP32 TX
22+
esp.set_esp_debug(True) # False, True
23+
# (re-)set digital pin modes
24+
esp_init_pin_modes(ESP_D_R_PIN, ESP_D_W_PIN)
25+
26+
27+
def esp_init_pin_modes(din, dout):
28+
# ESP32 Digital Input
29+
esp.set_pin_mode(din, 0x0)
30+
31+
# ESP32 Digital Output (no output on pins 34-39)
32+
esp.set_pin_mode(dout, 0x1)
33+
34+
35+
# M4 R/W Pin Assignments
36+
M4_D_W_PIN = DigitalInOut(board.A1) # digital write to ESP_D_R_PIN
37+
M4_D_W_PIN.direction = Direction.OUTPUT
38+
M4_A_R_PIN = pulseio.PulseIn(board.A0, maxlen=64) # PWM read from ESP_A_W_PIN
39+
M4_A_R_PIN.pause()
40+
41+
# ESP32 R/W Pin assignments & connections
42+
ESP_D_R_PIN = 12 # digital read from M4_D_W_PIN
43+
ESP_D_W_PIN = 13 # digital write to Red LED on Feather ESP32 and ESP32 Breakout
44+
# ESP32 Analog Input using ADC1
45+
# esp.set_pin_mode(36, 0x0) # Hall Effect Sensor
46+
# esp.set_pin_mode(37, 0x0) # Not Exposed
47+
# esp.set_pin_mode(38, 0x0) # Not Exposed
48+
# esp.set_pin_mode(39, 0x0) # Hall Effect Sensor
49+
# esp.set_pin_mode(32, 0x0) # INPUT OK
50+
# esp.set_pin_mode(33, 0x0) # DO NOT USE: ESP32SPI Busy/!Rdy
51+
# esp.set_pin_mode(34, 0x0) # INPUT OK
52+
# esp.set_pin_mode(35, 0x0) # INPUT OK (1/2 of Battery on ESP32 Feather)
53+
ESP_A_R_PIN = 32 # analog read from 10k potentiometer
54+
# ESP32 Analog (PWM/LEDC) Output (no output on pins 34-39)
55+
ESP_A_W_PIN = 27 # analog (PWM) write to M4_A_R_PIN
56+
57+
spi = board.SPI()
58+
# Airlift FeatherWing & Bitsy Add-On compatible
59+
esp32_cs = DigitalInOut(board.D13) # M4 Red LED
60+
esp32_ready = DigitalInOut(board.D11)
61+
esp32_reset = DigitalInOut(board.D12)
62+
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
63+
64+
esp_reset_all()
65+
66+
espfirmware = ""
67+
for _ in esp.firmware_version:
68+
if _ != 0:
69+
espfirmware += "{:c}".format(_)
70+
print("ESP32 Firmware:", espfirmware)
71+
72+
print(
73+
"ESP32 MAC: {5:02X}:{4:02X}:{3:02X}:{2:02X}:{1:02X}:{0:02X}".format(
74+
*esp.MAC_address
75+
)
76+
)
77+
78+
# initial digital write values
79+
m4_d_w_val = False
80+
esp_d_w_val = False
81+
82+
while True:
83+
print("\nESP32 DIGITAL:")
84+
85+
# ESP32 digital read
86+
try:
87+
M4_D_W_PIN.value = m4_d_w_val
88+
print("M4 wrote:", m4_d_w_val, end=" ")
89+
# b/c ESP32 might have reset out from under us
90+
esp_init_pin_modes(ESP_D_R_PIN, ESP_D_W_PIN)
91+
esp_d_r_val = esp.set_digital_read(ESP_D_R_PIN)
92+
print("--> ESP read:", esp_d_r_val)
93+
except (RuntimeError, AssertionError) as e:
94+
print("ESP32 Error", e)
95+
esp_reset_all()
96+
97+
# ESP32 digital write
98+
try:
99+
# b/c ESP32 might have reset out from under us
100+
esp_init_pin_modes(ESP_D_R_PIN, ESP_D_W_PIN)
101+
esp.set_digital_write(ESP_D_W_PIN, esp_d_w_val)
102+
print("ESP wrote:", esp_d_w_val, "--> Red LED")
103+
except (RuntimeError) as e:
104+
print("ESP32 Error", e)
105+
esp_reset_all()
106+
107+
print("ESP32 ANALOG:")
108+
109+
# ESP32 analog read
110+
try:
111+
esp_a_r_val = esp.set_analog_read(ESP_A_R_PIN)
112+
print(
113+
"Potentiometer --> ESP read: ",
114+
esp_a_r_val,
115+
" (",
116+
"{:1.1f}".format(esp_a_r_val * 3.3 / 65536),
117+
"v)",
118+
sep="",
119+
)
120+
except (RuntimeError, AssertionError) as e:
121+
print("ESP32 Error", e)
122+
esp_reset_all()
123+
124+
# ESP32 analog write
125+
try:
126+
# don't set the low end to 0 or the M4's pulseio read will stall
127+
esp_a_w_val = random.uniform(0.1, 0.9)
128+
esp.set_analog_write(ESP_A_W_PIN, esp_a_w_val)
129+
print(
130+
"ESP wrote: ",
131+
"{:1.2f}".format(esp_a_w_val),
132+
" (",
133+
"{:d}".format(int(esp_a_w_val * 65536)),
134+
")",
135+
" (",
136+
"{:1.1f}".format(esp_a_w_val * 3.3),
137+
"v)",
138+
sep="",
139+
end=" ",
140+
)
141+
142+
# ESP32 "analog" write is a 1000Hz PWM
143+
# use pulseio to extract the duty cycle
144+
M4_A_R_PIN.clear()
145+
M4_A_R_PIN.resume()
146+
while len(M4_A_R_PIN) < 2:
147+
pass
148+
M4_A_R_PIN.pause()
149+
duty = M4_A_R_PIN[0] / (M4_A_R_PIN[0] + M4_A_R_PIN[1])
150+
print(
151+
"--> M4 read: ",
152+
"{:1.2f}".format(duty),
153+
" (",
154+
"{:d}".format(int(duty * 65536)),
155+
")",
156+
" (",
157+
"{:1.1f}".format(duty * 3.3),
158+
"v)",
159+
" [len=",
160+
len(M4_A_R_PIN),
161+
"]",
162+
sep="",
163+
)
164+
165+
except (RuntimeError) as e:
166+
print("ESP32 Error", e)
167+
esp_reset_all()
168+
169+
# toggle digital write values
170+
m4_d_w_val = not m4_d_w_val
171+
esp_d_w_val = not esp_d_w_val
172+
173+
time.sleep(5)

examples/gpio/gpio.md

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Using ESP32 co-processor GPIO pins with CircuitPython ESP32SPI
2+
3+
As of NINA firmware version 1.3.1, the ESP32SPI library can be used to write digital values to many of the ESP32 GPIO pins using CircuitPython. It can also write "analog" signals using a float between 0 and 1 as the duty cycle (which is converted to an 8-bit integer for use by the NINA firmware). Keep in mind that these are 1000Hz PWM signals using the ESP32 LED Control peripheral, not true analog signals using an on-chip DAC. More information can be found here:
4+
<https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html>
5+
6+
As of NINA firmware version 1.5.0, the ESP32SPI library can be used to read digital signals from many of the ESP32 GPIO pins using CircuitPython. It can also read analog signals using ESP32 on-chip ADC1. The ESP32 can theoretically be set to use between 8 and 12 bits of resolution for analog reads. For our purposes, it is a 12-bit read within the NINA firmware, but the CircuitPython library converts it to a 16-bit integer consistent with CircuitPython `analogio` `AnalogIn`. There's an optional keyword argument in the `set_analog_read(self, pin, atten=ADC_ATTEN_DB_11)` function that changes the attenuation of the analog read, and therefore also changes the effective voltage range of the read. With the default 11dB attenuation, Espressif recommends keeping input voltages between 150mV to 2450mV for best results. More information can be found here:
7+
<https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/adc.html>
8+
9+
## GPIO Pins available to ESP32SPI
10+
11+
```
12+
# ESP32_GPIO_PINS:
13+
# https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI/blob/master/adafruit_esp32spi/digitalio.py
14+
# 0, 1, 2, 4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36, 39
15+
#
16+
# Pins Used for ESP32SPI
17+
# 5, 14, 18, 23, 33
18+
19+
# Avialable ESP32SPI Outputs (digital or 'analog' PWM) with NINA FW >= 1.3.1
20+
#
21+
# Adafruit ESP32 Breakout
22+
# *, 2, 4, 12, R, 15, 16, 17, 19, 21, 22, 25, 26, 27, 32
23+
# Adafruit ESP32 Feather
24+
# 4, 12, R, 15, 16, 17, 19, 21, 22, 25, 26, 27, 32
25+
# TinyPICO
26+
# 4, 15, 19, 21, 22, 25, 26, 27, 32
27+
# Adafruit ESP32 Airlift Breakout†
28+
# G, R, B
29+
# Adafruit ESP32 Airlift Feather†
30+
# G, R, B
31+
# Adafruit ESP32 Airlift Bitsy Add-On†
32+
# G, R, B
33+
34+
# Avialable ESP32SPI Digital Inputs with NINA FW >= 1.5.0
35+
#
36+
# Adafruit ESP32 Breakout
37+
# *, 2, 4, 12, R, 15, 16, 17, 19, 21, 22, 25, 26, 27, 32, 34, 35, 36, 39
38+
# Adafruit ESP32 Feather
39+
# 4, 12, R, 15, 16, 17, 19, 21, 22, 25, 26, 27, 32, 34, 36, 39
40+
# TinyPICO
41+
# 4, 15, 19, 21, 22, 25, 26, 27, 32 CH
42+
43+
# Avialable ESP32SPI Analog Inputs (ADC1) with NINA FW >= 1.5.0
44+
#
45+
# Adafruit ESP32 Breakout
46+
# *, 32, 34, 35, HE, HE
47+
# Adafruit ESP32 Feather
48+
# *, 32, 34, BA, HE, HE
49+
# TinyPICO
50+
# 32, BA
51+
52+
Notes:
53+
* Used for bootloading
54+
G Green LED
55+
R Red LED
56+
B Blue LED
57+
BA On-board connection to battery via 50:50 voltage divider
58+
CH Battery charging state (digital pin)
59+
HE Hall Effect sensor
60+
```
61+
62+
Note that on the Airlift FeatherWing and the Airlift Bitsy Add-On, the ESP32 SPI Chip Select (CS) pin aligns with M4's D13 Red LED pin:
63+
```
64+
esp32_cs = DigitalInOut(board.D13) # M4 Red LED
65+
esp32_ready = DigitalInOut(board.D11)
66+
esp32_reset = DigitalInOut(board.D12)
67+
```
68+
So the Red LED on the main Feather processor will almost always appear to be ON or slightly flickering when ESP32SPI is active.
69+
70+
## ESP32 Reset
71+
72+
Because the ESP32 may reset without indication to the CircuitPython code, putting ESP32 GPIO pins into input mode, `esp.set_digital_write(pin, val)` should be preceded by `esp.set_pin_mode(pin, 0x1)`, with appropriate error handling. Other non-default `esp` states (e.g., `esp.set_esp_debug()`) will also get re-initialized to default settings upon ESP32 reset, so CircuitPython code should anticipate this.
73+
74+
## GPIO on Airlift add-on boards
75+
76+
It should also be possible to do ESP32SPI reads and writes on the Airlift add-on boards, but other than the SPI pins and the green, blue, and red LEDs, the only pins available are RX (GPIO3), TX (GPIO1), and GPIO0, so function is extremely limited. Analog input is ruled out since none of those pins are on ADC1.
77+
78+
The Airlift Breakout has level-shifting on RX and GPIO0, so those could be digital inputs only. TX could be used as a digital input or as a digital or analog (PWM) output.
79+
80+
The Airlift FeatherWing and Bitsy Add-On have no level-shifting since they're designed to be stacked onto their associated M4 microcontrollers, so theoretically RX, TX, and GPIO0 could be used as digital inputs, or as digital or analog (PWM) outputs. It's hard to find a use case for doing this when stacked since RX, TX, and GPIO0 will be connected to M4 GPIO pins.
81+
82+
The Airlift [Metro / Arduino] Shield has level-shifting on RX and GPIO0, with stacking issues similar to the FeatherWings.
83+
84+
The RX, TX, and GPIO0 pins are used for updating the NINA firmware, and have specific behaviors immediately following reboot that need to be considered if reusing them as GPIO. On the Airlift FeatherWing and Bitsy Add-On, there are pads that need to be soldered to connect the pins. NINA does output messages to TX when connected, depending on the esp debug level set.
85+
86+
Ultimately it makes the most sense by far to use a non-stacked full-pinout ESP32 as co-processor for ESP32SPI pin read and write features.

0 commit comments

Comments
 (0)