Skip to content

Commit ad78e51

Browse files
committed
Add SeedStudio SenseCAP D1
1 parent 341d55e commit ad78e51

5 files changed

+316
-0
lines changed

docs/Supported-Modules.md

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- [OBI Socket](devices/OBI-Wifi-Socket.md)
4343
- [OBI Socket 2](devices/OBI-Socket-2.md)
4444
- [OBI Socket IP44 (Black.md)](devices/OBI-WiFi-Socket-IP44.md)
45+
- [SeedStudio SenseCAP D1](devices/SeedStudio-SenseCAP-D1.md)
4546
- [Shelly 1](devices/Shelly-1.md)
4647
- [Shelly 1PM](devices/Shelly-1PM.md)
4748
- [Shelly 2](devices/Shelly-2.md)
Loading
Loading
132 KB
Loading
+315
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
# SeedStudio SenseCAP Indicator D1
2+
3+
The device is a 4-Inch Touch Screen IoT development platform powered by ESP32S3 & RP2040. It has variants with LoRa support, and Air Quality support.
4+
5+
![Device Image](_media/devices/SeedStudio-SenseCap-D1.jpg)
6+
7+
Link to purchase the device [SeedStudio web site](https://www.seeedstudio.com/SenseCAP-Indicator-D1L-p-5646.html)
8+
9+
There are variants, all sharing the following features:
10+
11+
- ESP32S3 with 8MB Flash and 8MB PSRAM
12+
- 4 inch 480 x 480 pixels display, ST7701 controller, connected in parallel 8 bits mode to ESP32S3 for maximum display speed
13+
- Capacitive Touchscreen, FT5x06 controller
14+
- SD Card connector
15+
- Dual I2C Groove connectors
16+
- Dual USB-C connectors below and in the back of the device
17+
- Buzzer MLT-8530, Resonant Frequency:2700Hz
18+
- Also contains a RP2040 MCU, Dual ARM Cortex-M0+ up to 133MHz, 2MB of Flash
19+
20+
We will focus below on the "SenseCAP Indicator D1L" which includes:
21+
22+
- internal SGP41 tVOC Air Quality Sensor (Range: 0-40000ppm, Accuracy: 400ppm - 5000ppm ±(50ppm+5% of reading))
23+
- internal SCD40 CO2 Carbon Dioxid Sensors (Range: 1-500 VOC Index Points)
24+
- external AHT20 Temperature and Humidity sensor (Range: -40 ~ + 85 ℃/± 0.3 ℃; 0 ~ 100% RH/± 2% RH (25 ℃))
25+
26+
27+
## ESP32S3 build
28+
29+
The device requires a self-compile with the following options:
30+
31+
- Compile with an environment that uses `board = esp32s3-qio_opi_120`, which enables Quad SPI Flash and Octal SPI PSRAM at 120MHz.
32+
- Enable the following options: `USE_SDCARD`, `USE_I2C_SERIAL`, `USE_AHT2x`, `USE_SGP4X`, `USE_SCD40`, `USE_I2C`, `USE_SPI`, `USE_LVGL`, `USE_DISPLAY_LVGL_ONLY`, `USE_DISPLAY`, `USE_UNIVERSAL_TOUCH`, `USE_UNIVERSAL_DISPLAY`
33+
34+
TODO: have a readu-to-use `platformio_override.ini` template.
35+
36+
## Configure GPIOs and LVGL Display
37+
38+
Use:
39+
40+
```
41+
Template {"NAME":"SenseCAP Indicator D1","GPIO":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11488,11520,0,6210,0,0,0,0,32,641,609,0,1,1,1,0,1,0,0],"FLAG":0,"BASE":1}
42+
Module 0
43+
```
44+
45+
Add the following content in `display.ini` on the device file-system:
46+
47+
```
48+
:H,ST7701,480,480,16,RGB,18,17,16,21,45,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,6
49+
:V,1,10,8,50,1,10,8,20,0
50+
:S,2,1,1,0,40,20
51+
:IS,41,48,-1,-1
52+
FF,5,77,01,00,00,10
53+
C0,2,3B,00
54+
C1,2,0D,02
55+
C2,2,31,05
56+
C7,1,04
57+
CD,1,08
58+
B0,10,00,11,18,0E,11,06,07,08,07,22,04,12,0F,AA,31,18
59+
B1,10,00,11,19,0E,12,07,08,08,08,22,04,11,11,A9,32,18
60+
FF,5,77,01,00,00,11
61+
B0,1,60
62+
B1,1,32
63+
B2,1,07
64+
B3,1,80
65+
B5,1,49
66+
B7,1,85
67+
B8,1,21
68+
C1,1,78
69+
C2,A1,78
70+
E0,3,00,1B,02
71+
E1,B,08,A0,00,00,07,A0,00,00,00,44,44
72+
E2,C,11,11,44,44,ED,A0,00,00,EC,A0,00,00
73+
E3,4,00,00,11,11
74+
E4,2,44,44
75+
E5,10,0A,E9,D8,A0,0C,EB,D8,A0,0E,ED,D8,A0,10,EF,D8,A0
76+
E6,4,00,00,11,11
77+
E7,2,44,44
78+
E8,10,09,E8,D8,A0,0B,EA,D8,A0,0D,EC,D8,A0,0F,EE,D8,A0
79+
EB,7,02,00,E4,E4,88,00,40
80+
EC,2,3C,00
81+
ED,10,AB,89,76,54,02,FF,FF,FF,FF,FF,FF,20,45,67,98,BA
82+
36,1,10
83+
FF,5,77,01,00,00,13
84+
E1,1,E4
85+
FF,5,77,01,00,00,00
86+
21,0
87+
3A,1,60
88+
11,80
89+
29,80
90+
:B,120,02
91+
:UTI,FT5x06,I2,48,-1,-1
92+
RD A8
93+
CP 11
94+
RTF
95+
RD A3
96+
CP 64
97+
RTF
98+
RT
99+
:UTT
100+
RDM 00 16
101+
MV 2 1
102+
RT
103+
:UTX
104+
MV 3 2
105+
SCL 480 -1
106+
RT
107+
:UTY
108+
MV 5 2
109+
SCL 480 -1
110+
RT
111+
#
112+
```
113+
114+
## Using Air Quality Sensors
115+
116+
According to the schematics, ESP32S3 is directly connected in I2C to the `FT5x06` TouchScreen Controller, and to the `PCA8535` IO Expander. The `SCD40`, `SGP41` and `AHT20` are connected in I2C to the `RP2040` MCU so out of reach of Tasmota. For this, we have added the `I2C_SERIAL` interface which allows to access remote I2C devices via a UAR interface using the same Serial protocol as NXP `SC18IM704` chip.
117+
118+
To make it accessible from native I2C drivers, the I2C Serial driver must use bus `1`, and the I2C bus connected to ESP32S3 must use I2C bus `2`.
119+
120+
Now you need to flash the `RP2040` and use a simple Micropython script to bridge the UART to I2C bus.
121+
122+
## Flashing and configuring RP2040
123+
124+
# Step 1. Flash Micropython
125+
126+
To flash the RP2040, you need to insert a pin in the "reset" small hole, and power-up the device while keeping the Reset button pushed. You can then release the Reset button.
127+
128+
RP2040 boots in flash mode, and shows a USB disk. Simply download the latest RPI Pico Micropython firmware (file ending with `.uf2`) from [the official Micropython site](https://micropython.org/download/RPI_PICO/). This was tested with `RPI_PICO-20241025-v1.24.0.uf2`.
129+
130+
# Step 2. Use Thonny
131+
132+
For easy setup, download and install [Thonny](https://thonny.org/):
133+
134+
- Launch Thonny
135+
- Connect to the RP2040: click on the lower right corner and select `MicroPython (RP2040)`
136+
- Copy and paste the Micropython code from below
137+
- Click on "Save", select "RP2040 Device" and choose "main.py" as a filename
138+
- You can hit the "Run Current Script" button (green arrow) to see the script running
139+
- The script will automatically run at power on
140+
141+
Here is how it should look like:
142+
![Thonny console](_media/devices/SeedStudio-SenseCap-D1-Thonny.jpg)
143+
144+
# MicroPython code for RP2040
145+
146+
```python
147+
# below is an example of Micropython code for Seedstudio SenseCap
148+
# that allows to bridge the UART on GPIO 16/17 to I2C on GPIO 20/21
149+
150+
from machine import Pin, I2C
151+
from machine import Pin
152+
from machine import UART, Pin
153+
import time
154+
155+
uart = UART(0, baudrate=115200, tx=Pin(16), rx=Pin(17), timeout=30000, timeout_char=50, txbuf=128, rxbuf=128)
156+
print(f"CFG: UART initialized")
157+
158+
power_i2c = Pin(18, Pin.OUT) # create output pin on GPIO0
159+
power_i2c.on() # set pin to "on" (high) level
160+
161+
i2c = I2C(0, scl=Pin(21), sda=Pin(20), freq=400_000, timeout=1000)
162+
163+
# print(f"I2C: scan {i2c.scan()}")
164+
165+
# i2c_stat:
166+
# 0: no error
167+
# 1: I2C_NACK_ON_ADDRESS
168+
# 2: I2C_NACK_ON_DATA
169+
# 3: I2C_TIME_OUT
170+
i2c_stat = 0
171+
def set_i2c_stat(v):
172+
global i2c_stat
173+
i2c_stat = v
174+
175+
def get_i2c_stat():
176+
global i2c_stat
177+
return i2c_stat
178+
179+
180+
def ignore_until_P():
181+
# read uart until none left or 'P' reached
182+
# return last unprocessed char or None
183+
while True:
184+
c = uart.read(1)
185+
if c is None:
186+
return None # end of receive
187+
if c == b'P':
188+
cur_char = None
189+
return None # end reached
190+
191+
def process_cmd_start():
192+
# return last unprocessed char or None
193+
addr_b = uart.read(1)
194+
if addr_b is None: print("start: no address sent"); return None
195+
addr = addr_b[0] >> 1
196+
is_write = not bool(addr_b[0] & 1)
197+
len_b = uart.read(1)
198+
if len_b is None: print("start: no length sent"); return None
199+
len_i = len_b[0]
200+
cmd_next = None
201+
# dispatch depending on READ or WRITE
202+
if is_write:
203+
payload_b = bytes()
204+
if len_i > 0:
205+
payload_b = uart.read(len_i)
206+
if len(payload_b) < len_i:
207+
print(f"start: payload {payload_b} too small, expected {len_i} bytes")
208+
return None
209+
stop_bit = False
210+
cmd_next = uart.read(1)
211+
if cmd_next == b'P':
212+
stop_bit = True
213+
try:
214+
set_i2c_stat(0)
215+
acks_count = i2c.writeto(addr, payload_b, stop_bit)
216+
#print(f"{acks_count=} {len_i=}")
217+
if acks_count < len_i:
218+
set_i2c_stat(2)
219+
else:
220+
print(f"I2C: [0x{addr:02X}] W '{payload_b.hex()}'")
221+
#print(f"{acks_count=} {len_i=} {get_i2c_stat()=}")
222+
except Exception as error:
223+
#print(f"{error=}")
224+
set_i2c_stat(1) # I2C_NACK_ON_ADDRESS
225+
# if 'S' is followed, return to main loop
226+
if cmd_next == b'S':
227+
return cmd_next
228+
else:
229+
# read
230+
payload_b = b''
231+
#print(f"read: [0x{addr:02X}] {len_i}")
232+
try:
233+
set_i2c_stat(0)
234+
payload_b = i2c.readfrom(addr, len_i, True)
235+
print(f"I2C: [0x{addr:02X}] R '{payload_b.hex()}' {len(payload_b)}/{len_i}")
236+
uart.write(payload_b)
237+
except Exception as error:
238+
print(f"I2C: error while reading from 0x{addr:02X} len={len_i} error '{error}'")
239+
set_i2c_stat(1) # I2C_NACK_ON_ADDRESS
240+
return None
241+
return None
242+
243+
244+
def process_cmd_stop():
245+
# return last unprocessed char or None
246+
return None # do nothing
247+
248+
def process_cmd_read():
249+
# return last unprocessed char or None
250+
# we accept only 1 register for now
251+
reg = uart.read(1)
252+
if reg is None: print("read: no register sent"); return None
253+
cmd_next = uart.read(1)
254+
if cmd_next is None or cmd_next != b'P': print("read: unfinished command"); return None
255+
#
256+
reg = reg[0] # convert to number
257+
if reg == 0x0A: # I2CStat
258+
uart.write(int.to_bytes(get_i2c_stat() | 0xF0))
259+
else:
260+
uart.write(int.to_bytes(0x00))
261+
return None
262+
263+
def process_cmd_write():
264+
# return last unprocessed char or None
265+
print("I2C: ignore 'W' commmand")
266+
return ignore_until_P()
267+
268+
def process_cmd_version():
269+
ignore_until_P()
270+
uart.write(b'Tasmota I2C uart bridge 1.0\x00')
271+
return None
272+
273+
def process_cmd_ignore():
274+
# return last unprocessed char or None
275+
return ignore_until_P()
276+
277+
def process_discard():
278+
# discard all bytes in input
279+
# return last unprocessed char or None
280+
while uart.any() > 1:
281+
uart.read(uart.any())
282+
return None
283+
284+
def run():
285+
cmd = None
286+
while True:
287+
if cmd is None and uart.any() > 0:
288+
cmd = uart.read(1)
289+
if cmd is None:
290+
time.sleep(0.01)
291+
else:
292+
#print(f"SER: received cmd {cmd}")
293+
if cmd == b'S':
294+
cmd = process_cmd_start()
295+
elif cmd == b'P':
296+
cmd = process_cmd_stop()
297+
elif cmd == b'R':
298+
cmd = process_cmd_read()
299+
elif cmd == b'W':
300+
cmd = process_cmd_write()
301+
elif cmd == b'V':
302+
cmd = process_cmd_version()
303+
elif cmd == b'I' or cmd == b'O' or cmd == b'Z':
304+
cmd = process_cmd_ignore()
305+
else:
306+
cmd = process_discard()
307+
308+
run()
309+
```
310+
311+
## Internals
312+
313+
SeedStudio does not provide the detailed schematics, but still provides an overview of GPIO connection:
314+
315+
![SenseCap D1 internals](_media/devices/SeedStudio-SenseCap-D1-Internal.jpg)

0 commit comments

Comments
 (0)