Skip to content

LILYGO® T-Deck Support #8558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rgrizzell opened this issue Nov 5, 2023 · 17 comments · Fixed by #8563
Closed

LILYGO® T-Deck Support #8558

rgrizzell opened this issue Nov 5, 2023 · 17 comments · Fixed by #8563
Labels
board New board or update to a single board enhancement esp32-s3
Milestone

Comments

@rgrizzell
Copy link

rgrizzell commented Nov 5, 2023

LILYGO® T-Deck

LILYGO® T-Deck

Specifications

CPU: ESP32-S3
Flash: 16MB
PSRAM: 8MB
Display: 2.8" ST7789 (320x240)
Radio: 2.4Ghz WiFi, Bluetooth 5, and LoRa (optional)


Enter Flash Mode (Download Mode)

  1. Press and hold the RESET button.
  2. Press and hold the Trackball button (PIN 0).
  3. Release the RESET button while still holding the Trackball button.
  4. Release the Trackball button after 1 second.
@rgrizzell
Copy link
Author

I'll be using this issue to document my attempt to port CircuitPython to the LILYGO T-Deck.

@rgrizzell rgrizzell changed the title LILYGO T-Deck Support LILYGO® T-Deck Support Nov 5, 2023
@rgrizzell
Copy link
Author

rgrizzell commented Nov 5, 2023

Pin-out based on the schematic provided by LILYGO.

PIN Description
0 Trackball Button Press (BOOT)
1 Trackball Left
2 Trackball Right
3 Trackball Up
4 Battery
5 Speaker LRCLK
6 Speaker DIN
7 Speaker BCLK
8 Keyboard SCL
9 LoRa CS
10 Power On
11 Display DC
12 Display CS
13 LoRa INT (BUSY)
14 Microphone DIN
15 Trackball Down
16 Display INT
17 LoRa RST
18 Keyboard SDA
19 USB DM
20 USB DP
21 Microphone LRCLK
35 Unused
36 Unused
37 Unused
38 MISO
39 SD Card CS
40 CLK
41 MOSI
42 Display Backlight
43 UART TX
44 UART RX
45 LoRa DIO1
46 Keyboard INT
47 Microphone SCLK
48 Microphone MCLK

@rgrizzell
Copy link
Author

CircuitPython 9.x is now running on the device with display output.

CircuitPython on LILYGO T-Deck with display output.

The rest of the hardware needs to be tested to ensure the assigned pins under board are accurate.

@rgrizzell
Copy link
Author

rgrizzell commented Nov 5, 2023

WiFi

WiFi works in the sense that networks can be scanned for, however I am having issues connecting to any access points. The timeout doesn't work either, as it effectively soft-locks until reloaded.

This is likely due to a regression in CircuitPython 9.x. It seems that other ESP32 devices have issues as well: #8552

Example Code

settings.toml

WIFI_SSID = "MyNetwork"
WIFI_PASSWORD = "MyPassword"
WIFI_CHANNEL = 3

code.py

import os
import time
import sys
import ipaddress
import wifi

print("Broadcasted SSIDs")
for network in wifi.radio.start_scanning_networks():
    print(f"{network.ssid} [Ch:{network.channel}]")
wifi.radio.stop_scanning_networks()


ssid = os.getenv("WIFI_SSID")
channel = os.getenv("WIFI_CHANNEL", 0)

try:
    print(f"Connecting to: {ssid} [Ch:{channel}]")
    wifi.radio.connect(
        ssid=ssid,
        password=os.getenv("WIFI_PASSWORD"),
        channel=channel,
        timeout=5
    )
    print("Connected.")
except ConnectionError as e:
    print("Failed to connect, aborting.")
    print(e)
    sys.exit()


print("MAC addr:", [hex(i) for i in wifi.radio.mac_address])
print("IP address:", wifi.radio.ipv4_address)

Bluetooth

It's basic, but it works. I tried using the DeviceInfoService provided by the adafruit_ble library, but that resulted in NotImplementedError when initialized.

Examples

These examples are from the adafruit_ble library, tweaked to work with ESP32.

Broadcasting

code.py

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement

ble = BLERadio()
advertisement = ProvideServicesAdvertisement()

print(f"Advertising as: {ble.name}")
ble.start_advertising(advertisement)
while True:
    pass

REPL

Advertising as: CIRCUITPY

CIRCUITPY device advertisement as seen by mobile phone

Scanning

code.py

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement

ble = BLERadio()
print(f"Device name: {ble.name}")

print("Scanning")
found = set()
scan_responses = set()
for advertisement in ble.start_scan(ProvideServicesAdvertisement):
    addr = advertisement.address
    if advertisement.scan_response and addr not in scan_responses:
        scan_responses.add(addr)
    elif not advertisement.scan_response and addr not in found:
        found.add(addr)
    else:
        continue
    print(addr, advertisement)
    print("\t" + repr(advertisement))
    print()

REPL

Device name: CIRCUITPY
Scanning
<Address [redacted]> <ProvideServicesAdvertisement services=<BoundServiceList: UUID(0xfe78)> >
	Advertisement(data=b"[redacted]")

<Address [redacted]> <ProvideServicesAdvertisement services=<BoundServiceList: UUID(0xfe9f)> >
	Advertisement(data=b"[redacted]")

<Address [redacted]> <ProvideServicesAdvertisement services=<BoundServiceList: UUID(0xfe9f)> >
	Advertisement(data=b"[redacted]")

<Address [redacted]> <ProvideServicesAdvertisement services=<BoundServiceList: UUID(0xfe9f)> >
	Advertisement(data=b"[redacted]")

@dhalbert dhalbert added board New board or update to a single board esp32-s3 labels Nov 5, 2023
@dhalbert dhalbert added this to the 9.0.0 milestone Nov 5, 2023
@rgrizzell
Copy link
Author

Keyboard

The keyboard is an ESP32-C3 chip running its own firmware and communicating via I2C. A keyboard backlight is present, it can be turned on by pressing Alt+B. There is no way to enable that via I2C, but the keyboard can be updated to support it.

There is a separate UART interface (2 of 6 pins) on the main board that can be used to re-flashed the keyboard. It's recommended to solder headers or use probes on this interface, since a standard Dupont connector won't fit. However, the male pins will fit if removed from the plastic, taking care to insulate the pins from one another.

Pin IO46 is labelled as a KEYBOARD_INT, but it's not needed. There isn't a corresponding pin configured in the firmware either. Currently unused, but it appears that functionality can be added.

Example

import board
import busio
import time

i2c = busio.I2C(board.SCL, board.SDA)
keyboard = 0x55

while not i2c.try_lock():
    pass

try:
    while True:
        r = bytearray(1)
        i2c.readfrom_into(keyboard, r)
        if r != b'\x00':
            print(r.decode())
        time.sleep(0.05)

finally: 
    i2c.unlock()

REPL

H
e
l
l
o
 
w
o
r
l
d
!

@rgrizzell
Copy link
Author

I've revised pins.c a few times, but on the final version I settled on using whatever the Arduino project called them. Hopefully making porting a bit easier. I also made sure to add the SPI and I2C interfaces to the board definition.

My plan is to continue testing the hardware and providing examples while the PR (#8563) is reviewed.

@rgrizzell
Copy link
Author

Speaker

When configured through an audiobusio.I2SOut interface, the speaker works as expected. Volume is louder than anticipated.

Example

Took this from the audiobusio example and modified the pins.

import audiobusio
import audiocore
import board
import array
import time
import math

# Generate one period of sine wave.
length = 8000 // 440
sine_wave = array.array("H", [0] * length)
for i in range(length):
    sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15)

sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000)
i2s = audiobusio.I2SOut(board.I2S_BCK, board.I2S_WS, board.I2S_DOUT)
i2s.play(sine_wave, loop=True)
time.sleep(1)
i2s.stop()

Microphone

The T-Deck comes with an ES7210 to handle Microphone input. This device works best with I2S, but no audiobusio.I2SIn interface exists. (#5456)

@rgrizzell
Copy link
Author

Trackball

Functional, but hard to know if it's working as designed. Fine input seems to be an issue with both the Sample Code (that comes with the T-Deck) and CircuitPython. However, when using countio, input resolution is improved.

Examples

Using keypad library, this treats each input as a keypress. The primary issue is that one unit of movement on on the trackball is one event transition. Meaning, it takes two units of movement to equate 1 full (press and release) event.

import board
import keypad

trackball = keypad.Keys(
    [
        board.TRACKBALL_CLICK,
        board.TRACKBALL_UP,
        board.TRACKBALL_DOWN,
        board.TRACKBALL_LEFT,
        board.TRACKBALL_RIGHT
    ],
    value_when_pressed=False
)

while True:
    event = trackball.events.get()
    # event will be None if nothing has happened.
    if event:
        print(event)

When switching the to something like Countio, we can measure the units of movement between each sample. However, the ESP32 is limited to 4 countio.Counters.

import board
import countio

pins = [
    board.TRACKBALL_UP,
    board.TRACKBALL_DOWN,
    board.TRACKBALL_LEFT,
    board.TRACKBALL_RIGHT,
    board.TRACKBALL_CLICK
]

counters = [countio.Counter(p) for p in pins]

while True:
    for i, c in enumerate(counters):
        if c.count > 0:
            print(f"{i}: {c.count}")
            c.reset()

Thankfully, the resolution is not needed for the button click, so that can remain a keypad.Keys object. Combining both examples gets an acceptable input method. It prevents using countio.Counters for anything else, but on the T-Deck there's almost no available GPIO to utilize for other counting purposes.

code.py

import board
import countio
import keypad

trackball = {
    "up": countio.Counter(board.TRACKBALL_UP),
    "down": countio.Counter(board.TRACKBALL_DOWN),
    "left": countio.Counter(board.TRACKBALL_LEFT),
    "right": countio.Counter(board.TRACKBALL_RIGHT)
}
click = keypad.Keys([board.TRACKBALL_CLICK], value_when_pressed=False)

while True:
    event = click.events.get()
    if event:
        print(event)

    for p, c in trackball.items():
        if c.count > 0:
            print(f"{p}: {c.count}")
            c.reset()

REPL

up: 1
<Event: key_number 0 pressed>
right: 1
<Event: key_number 0 released>
left: 1
<Event: key_number 0 pressed>
<Event: key_number 0 released>
down: 2
down: 1
up: 1
left: 1
down: 2
right: 1
down: 1
up: 1
down: 1
<Event: key_number 0 pressed>
right: 1
<Event: key_number 0 released>
right: 2
down: 1

@rgrizzell
Copy link
Author

LoRa

There doesn't seem to be a CircuitPython-specific library for the SX1262 series of LoRa modules, but the one for MicroPython should work. However, the library tries to build the SPI interface rather than accepting a SPI object. Since the CircuitPython build initializes the SPI on start-up, when the library tries to initialize it, the pin in already in use.

Traceback (most recent call last):
  File "code.py", line 6, in <module>
  File "/lib/sx1262.py", line 20, in __init__
  File "/lib/sx126x.py", line 53, in __init__
ValueError: IO40 in use

The micropySX126X will have to be changed to accommodate for SPI ojbects or an alternative library should be used. Thus far, no other library exist for the Semtech SX126x series of LoRa modules.

@tannewt
Copy link
Member

tannewt commented Nov 9, 2023

There doesn't seem to be a CircuitPython-specific library for the SX1262 series of LoRa modules

https://github.com/adafruit/Adafruit_CircuitPython_RFM9x is for a module with a SX1276 inside.

@rgrizzell
Copy link
Author

There doesn't seem to be a CircuitPython-specific library for the SX1262 series of LoRa modules

https://github.com/adafruit/Adafruit_CircuitPython_RFM9x is for a module with a SX1276 inside.

There's a pinout difference between the two chips the SX126x and the SX127x that leads me to believe that the RFM9x library is incompatible. The biggest being the presence of the BUSY pin on the SX126x.

As soon as I can get a receiving device to test with, I'll give it a shot. Thanks!

@rgrizzell
Copy link
Author

I've created a helper library for the LILYGO T-Deck on CircuitPython. Development for the peripherals will continue here. Contributions are welcome!

https://github.com/rgrizzell/LILYGO_T_Deck_CircuitPython

@RetiredWizard
Copy link

@rgrizzell Thanks for your work on this!!!

https://discord.com/channels/327254708534116352/334905778857050125/1173394324348735648

@Freyr86
Copy link

Freyr86 commented Dec 1, 2023

Keyboard

The keyboard is an ESP32-C3 chip running its own firmware and communicating via I2C. A keyboard backlight is present, it can be turned on by pressing Alt+B. There is no way to enable that via I2C, but the keyboard can be updated to support it.

There is a separate UART interface (2 of 6 pins) on the main board that can be used to re-flashed the keyboard. It's recommended to solder headers or use probes on this interface, since a standard Dupont connector won't fit. However, the male pins will fit if removed from the plastic, taking care to insulate the pins from one another.

Pin IO46 is labelled as a KEYBOARD_INT, but it's not needed. There isn't a corresponding pin configured in the firmware either. Currently unused, but it appears that functionality can be added.

Example

import board
import busio
import time

i2c = busio.I2C(board.SCL, board.SDA)
keyboard = 0x55

while not i2c.try_lock():
    pass

try:
    while True:
        r = bytearray(1)
        i2c.readfrom_into(keyboard, r)
        if r != b'\x00':
            print(r.decode())
        time.sleep(0.05)

finally: 
    i2c.unlock()

REPL

H
e
l
l
o
 
w
o
r
l
d
!

Good morning

I have a perhaps stupid question but how to make the keyboard work in REPL because I have the starting file but no possibility of typing the commands directly on the t-deck

@rgrizzell
Copy link
Author

how to make the keyboard work in REPL

Good question! Unfortunately, there isn't a way to use the T-Keyboard to input characters the REPL on the T-Deck.

It might be feasible to add support for that. CircuitPython recently added a repl.py file that, if present, will run before the REPL is loaded. A driver could probably be created to accept input from the T-Keyboard, but more research is required. I haven't tried it.

@RetiredWizard
Copy link

As a general rule you're better off opening a new issue rather than updating closed ones. Closed issues are harder for the Adafruit folks to track. I think this is a great question to open as an issue :D.

I'm thinking a virtual REPL using the python exec() command and a simple I2C keyboard handler wouldn't be too hard to write and might be a decent stop gap in lieu of CircuitPython native support. It could just be placed in code.py.

As a side note, PyDOS supports the T-Deck and will boot up on the device with keyboard support.

@Freyr86
Copy link

Freyr86 commented Dec 1, 2023

Yes it's due to my lack of knowledge of git I will continue here I will leave you the link

#8684

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
board New board or update to a single board enhancement esp32-s3
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants