Skip to content

Commit

Permalink
Add SeedStudio SenseCAP D1
Browse files Browse the repository at this point in the history
  • Loading branch information
s-hadinger committed Nov 8, 2024
1 parent 341d55e commit ad78e51
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/Supported-Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- [OBI Socket](devices/OBI-Wifi-Socket.md)
- [OBI Socket 2](devices/OBI-Socket-2.md)
- [OBI Socket IP44 (Black.md)](devices/OBI-WiFi-Socket-IP44.md)
- [SeedStudio SenseCAP D1](devices/SeedStudio-SenseCAP-D1.md)
- [Shelly 1](devices/Shelly-1.md)
- [Shelly 1PM](devices/Shelly-1PM.md)
- [Shelly 2](devices/Shelly-2.md)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_media/devices/SeedStudio-SenseCap-D1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
315 changes: 315 additions & 0 deletions docs/devices/SeedStudio-SenseCAP-D1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
# SeedStudio SenseCAP Indicator D1

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.

![Device Image](_media/devices/SeedStudio-SenseCap-D1.jpg)

Link to purchase the device [SeedStudio web site](https://www.seeedstudio.com/SenseCAP-Indicator-D1L-p-5646.html)

There are variants, all sharing the following features:

- ESP32S3 with 8MB Flash and 8MB PSRAM
- 4 inch 480 x 480 pixels display, ST7701 controller, connected in parallel 8 bits mode to ESP32S3 for maximum display speed
- Capacitive Touchscreen, FT5x06 controller
- SD Card connector
- Dual I2C Groove connectors
- Dual USB-C connectors below and in the back of the device
- Buzzer MLT-8530, Resonant Frequency:2700Hz
- Also contains a RP2040 MCU, Dual ARM Cortex-M0+ up to 133MHz, 2MB of Flash

We will focus below on the "SenseCAP Indicator D1L" which includes:

- internal SGP41 tVOC Air Quality Sensor (Range: 0-40000ppm, Accuracy: 400ppm - 5000ppm ±(50ppm+5% of reading))
- internal SCD40 CO2 Carbon Dioxid Sensors (Range: 1-500 VOC Index Points)
- external AHT20 Temperature and Humidity sensor (Range: -40 ~ + 85 ℃/± 0.3 ℃; 0 ~ 100% RH/± 2% RH (25 ℃))


## ESP32S3 build

The device requires a self-compile with the following options:

- Compile with an environment that uses `board = esp32s3-qio_opi_120`, which enables Quad SPI Flash and Octal SPI PSRAM at 120MHz.
- 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`

TODO: have a readu-to-use `platformio_override.ini` template.

## Configure GPIOs and LVGL Display

Use:

```
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}
Module 0
```

Add the following content in `display.ini` on the device file-system:

```
: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
:V,1,10,8,50,1,10,8,20,0
:S,2,1,1,0,40,20
:IS,41,48,-1,-1
FF,5,77,01,00,00,10
C0,2,3B,00
C1,2,0D,02
C2,2,31,05
C7,1,04
CD,1,08
B0,10,00,11,18,0E,11,06,07,08,07,22,04,12,0F,AA,31,18
B1,10,00,11,19,0E,12,07,08,08,08,22,04,11,11,A9,32,18
FF,5,77,01,00,00,11
B0,1,60
B1,1,32
B2,1,07
B3,1,80
B5,1,49
B7,1,85
B8,1,21
C1,1,78
C2,A1,78
E0,3,00,1B,02
E1,B,08,A0,00,00,07,A0,00,00,00,44,44
E2,C,11,11,44,44,ED,A0,00,00,EC,A0,00,00
E3,4,00,00,11,11
E4,2,44,44
E5,10,0A,E9,D8,A0,0C,EB,D8,A0,0E,ED,D8,A0,10,EF,D8,A0
E6,4,00,00,11,11
E7,2,44,44
E8,10,09,E8,D8,A0,0B,EA,D8,A0,0D,EC,D8,A0,0F,EE,D8,A0
EB,7,02,00,E4,E4,88,00,40
EC,2,3C,00
ED,10,AB,89,76,54,02,FF,FF,FF,FF,FF,FF,20,45,67,98,BA
36,1,10
FF,5,77,01,00,00,13
E1,1,E4
FF,5,77,01,00,00,00
21,0
3A,1,60
11,80
29,80
:B,120,02
:UTI,FT5x06,I2,48,-1,-1
RD A8
CP 11
RTF
RD A3
CP 64
RTF
RT
:UTT
RDM 00 16
MV 2 1
RT
:UTX
MV 3 2
SCL 480 -1
RT
:UTY
MV 5 2
SCL 480 -1
RT
#
```

## Using Air Quality Sensors

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.

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`.

Now you need to flash the `RP2040` and use a simple Micropython script to bridge the UART to I2C bus.

## Flashing and configuring RP2040

# Step 1. Flash Micropython

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.

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`.

# Step 2. Use Thonny

For easy setup, download and install [Thonny](https://thonny.org/):

- Launch Thonny
- Connect to the RP2040: click on the lower right corner and select `MicroPython (RP2040)`
- Copy and paste the Micropython code from below
- Click on "Save", select "RP2040 Device" and choose "main.py" as a filename
- You can hit the "Run Current Script" button (green arrow) to see the script running
- The script will automatically run at power on

Here is how it should look like:
![Thonny console](_media/devices/SeedStudio-SenseCap-D1-Thonny.jpg)

# MicroPython code for RP2040

```python
# below is an example of Micropython code for Seedstudio SenseCap
# that allows to bridge the UART on GPIO 16/17 to I2C on GPIO 20/21

from machine import Pin, I2C
from machine import Pin
from machine import UART, Pin
import time

uart = UART(0, baudrate=115200, tx=Pin(16), rx=Pin(17), timeout=30000, timeout_char=50, txbuf=128, rxbuf=128)
print(f"CFG: UART initialized")

power_i2c = Pin(18, Pin.OUT) # create output pin on GPIO0
power_i2c.on() # set pin to "on" (high) level

i2c = I2C(0, scl=Pin(21), sda=Pin(20), freq=400_000, timeout=1000)

# print(f"I2C: scan {i2c.scan()}")

# i2c_stat:
# 0: no error
# 1: I2C_NACK_ON_ADDRESS
# 2: I2C_NACK_ON_DATA
# 3: I2C_TIME_OUT
i2c_stat = 0
def set_i2c_stat(v):
global i2c_stat
i2c_stat = v

def get_i2c_stat():
global i2c_stat
return i2c_stat


def ignore_until_P():
# read uart until none left or 'P' reached
# return last unprocessed char or None
while True:
c = uart.read(1)
if c is None:
return None # end of receive
if c == b'P':
cur_char = None
return None # end reached

def process_cmd_start():
# return last unprocessed char or None
addr_b = uart.read(1)
if addr_b is None: print("start: no address sent"); return None
addr = addr_b[0] >> 1
is_write = not bool(addr_b[0] & 1)
len_b = uart.read(1)
if len_b is None: print("start: no length sent"); return None
len_i = len_b[0]
cmd_next = None
# dispatch depending on READ or WRITE
if is_write:
payload_b = bytes()
if len_i > 0:
payload_b = uart.read(len_i)
if len(payload_b) < len_i:
print(f"start: payload {payload_b} too small, expected {len_i} bytes")
return None
stop_bit = False
cmd_next = uart.read(1)
if cmd_next == b'P':
stop_bit = True
try:
set_i2c_stat(0)
acks_count = i2c.writeto(addr, payload_b, stop_bit)
#print(f"{acks_count=} {len_i=}")
if acks_count < len_i:
set_i2c_stat(2)
else:
print(f"I2C: [0x{addr:02X}] W '{payload_b.hex()}'")
#print(f"{acks_count=} {len_i=} {get_i2c_stat()=}")
except Exception as error:
#print(f"{error=}")
set_i2c_stat(1) # I2C_NACK_ON_ADDRESS
# if 'S' is followed, return to main loop
if cmd_next == b'S':
return cmd_next
else:
# read
payload_b = b''
#print(f"read: [0x{addr:02X}] {len_i}")
try:
set_i2c_stat(0)
payload_b = i2c.readfrom(addr, len_i, True)
print(f"I2C: [0x{addr:02X}] R '{payload_b.hex()}' {len(payload_b)}/{len_i}")
uart.write(payload_b)
except Exception as error:
print(f"I2C: error while reading from 0x{addr:02X} len={len_i} error '{error}'")
set_i2c_stat(1) # I2C_NACK_ON_ADDRESS
return None
return None


def process_cmd_stop():
# return last unprocessed char or None
return None # do nothing

def process_cmd_read():
# return last unprocessed char or None
# we accept only 1 register for now
reg = uart.read(1)
if reg is None: print("read: no register sent"); return None
cmd_next = uart.read(1)
if cmd_next is None or cmd_next != b'P': print("read: unfinished command"); return None
#
reg = reg[0] # convert to number
if reg == 0x0A: # I2CStat
uart.write(int.to_bytes(get_i2c_stat() | 0xF0))
else:
uart.write(int.to_bytes(0x00))
return None

def process_cmd_write():
# return last unprocessed char or None
print("I2C: ignore 'W' commmand")
return ignore_until_P()

def process_cmd_version():
ignore_until_P()
uart.write(b'Tasmota I2C uart bridge 1.0\x00')
return None

def process_cmd_ignore():
# return last unprocessed char or None
return ignore_until_P()

def process_discard():
# discard all bytes in input
# return last unprocessed char or None
while uart.any() > 1:
uart.read(uart.any())
return None

def run():
cmd = None
while True:
if cmd is None and uart.any() > 0:
cmd = uart.read(1)
if cmd is None:
time.sleep(0.01)
else:
#print(f"SER: received cmd {cmd}")
if cmd == b'S':
cmd = process_cmd_start()
elif cmd == b'P':
cmd = process_cmd_stop()
elif cmd == b'R':
cmd = process_cmd_read()
elif cmd == b'W':
cmd = process_cmd_write()
elif cmd == b'V':
cmd = process_cmd_version()
elif cmd == b'I' or cmd == b'O' or cmd == b'Z':
cmd = process_cmd_ignore()
else:
cmd = process_discard()

run()
```

## Internals

SeedStudio does not provide the detailed schematics, but still provides an overview of GPIO connection:

![SenseCap D1 internals](_media/devices/SeedStudio-SenseCap-D1-Internal.jpg)

0 comments on commit ad78e51

Please sign in to comment.