docs: add LCD protocol documentation (#3)

This commit is contained in:
Markus Zehnder
2025-07-27 22:00:01 +02:00
committed by GitHub
parent fa134da7b2
commit 8275bcd79a
6 changed files with 137 additions and 70 deletions
+11 -70
View File
@@ -20,30 +20,12 @@ Note: Multiple attempts to contact the manufacturer for documentation have recei
With that out of the way, on to the fun stuff! With that out of the way, on to the fun stuff!
## Features **See [Linux shell commands](doc/shell_commands.md) on how to switch off the display with standard Linux commands!**
- Control the AOOSTAR WTR MAX and GEM12+ PRO second screen from Linux.
- Switch the display on or off.
- Display images (with automatic scaling and partial update support).
- Proof-of-concept demo for drawing shapes and text.
- USB device/serial port selection.
## Display
Known information:
- **Screen size:** 2.86" ≈ 68 × 27 mm
- **Resolution:** 960 × 376
- **Manufacturer:** Synwit
- **Connected over USB UART** with a proprietary serial communication protocol:
- **USB device ID:** `416:90A1` (as shown by `lsusb`)
- **Linux device (example on Debian):** `/dev/ttyACM0`
- **1,500,000 baud**, 8N1 (likely ignored; actual USB transfer speed is much higher)
## Reverse Engineering ## Reverse Engineering
Reverse engineered LCD commands: [doc/lcd_protocol.md](doc/lcd_protocol.md)
### Motivation ### Motivation
Developing open client software to use the embedded second screen on various Linux distributions. Developing open client software to use the embedded second screen on various Linux distributions.
@@ -62,9 +44,9 @@ The display remains on continuously (24×7) if the official software is not runn
### Goals ### Goals
- [ ] Reverse engineer the LCD serial protocol to provide open screen software. - [x] Reverse engineer the LCD serial protocol to provide open screen software.
- Utilize the official AOOSTAR-X display software by sniffing USB communication, using `strace`, and decompiling the Python app. - Utilize the official AOOSTAR-X display software by sniffing USB communication, using `strace`, and decompiling the Python app.
- [ ] Document known commands so clients in other programming languages can be written. - [x] Document known commands so clients in other programming languages can be written.
- [ ] Eventually, create a Rust crate for easy integration into other Rust applications. - [ ] Eventually, create a Rust crate for easy integration into other Rust applications.
**Out of scope:** **Out of scope:**
@@ -73,33 +55,13 @@ The display remains on continuously (24×7) if the official software is not runn
That would be an interesting task — potentially uncovering additional display commands — but is outside the project's current scope. That would be an interesting task — potentially uncovering additional display commands — but is outside the project's current scope.
- Reimplementing the full AOOSTAR-X display software, which is overly complex for most use cases. - Reimplementing the full AOOSTAR-X display software, which is overly complex for most use cases.
## Linux Shell Control Commands ### Features
Turning the display on or off is possible directly in a Linux shell! - Control the AOOSTAR WTR MAX and GEM12+ PRO second screen from Linux.
- Switch the display on or off.
Add your user to the `dialout` group for access to `/dev/ttyACM0`: - Display images (with automatic scaling and partial update support).
- Proof-of-concept demo for drawing shapes and text.
```shell - USB device/serial port selection.
sudo usermod -a -G dialout $USER
```
> You may have to log out and back in for group changes to take effect.
> If not using a Debian based Linux, the tty device might have a different name, or not using the `dialout` group.
### Turn display on
```shell
stty -F /dev/ttyACM0 raw
printf "\252U\252U\v\0\0\0" > /dev/ttyACM0
```
### Turn display off
```shell
stty -F /dev/ttyACM0 raw
printf "\252U\252U\12\0\0\0" > /dev/ttyACM0
```
## Setup ## Setup
@@ -180,27 +142,6 @@ asterctl --image img/aybabtu.png
This expects a 960 × 376 image (other sizes are automatically scaled and the aspect ratio is ignored). This expects a 960 × 376 image (other sizes are automatically scaled and the aspect ratio is ignored).
See Rust image crate for [supported image formats](https://github.com/image-rs/image?tab=readme-ov-file#supported-image-formats). See Rust image crate for [supported image formats](https://github.com/image-rs/image?tab=readme-ov-file#supported-image-formats).
## Development
- When sending an image to the screen, the image must be in **RGB565** format (16 bits per pixel).
- All graphic operations are performed on the loaded RGB888 image buffer.
- The image is automatically converted to RGB565 when sending it to the display.
- The 1.5 Mbps baud rate set in the client is ignored, as actual USB bulk transfer achieves much higher throughput.
For reference, at the nominal serial rate (~1,500,000 baud), it would take approximately 6 seconds to transfer a full image of 721,920 bytes (960 × 376 × 2):
- Display protocol: payload per chunk = 47 bytes; header per chunk = 12 bytes
- Number of chunks: 721,920 / 47 ≈ 15,360 chunks
- Total transmitted data: 15,360 chunks × 59 bytes/chunk = 906,240 bytes
- Serial frame format: 1 start bit + 8 data bits + 1 stop bit = 10 bits/byte
- Effective byte rate: 1,500,000 bits/sec / 10 bits/byte = 150,000 bytes/sec
- Transfer time: 906,240 bytes / 150,000 bytes/sec ≈ 6 seconds
- **Performance:**
- Displaying the first fullscreen image takes around 1.3 seconds.
- When switching the display on, the old image is immediately shown.
- Once the new image is fully transferred and the end-header command is sent, the display firmware switches to the new image.
- **Partial Updates:**
- A frame cache is used to send only changed chunks after the initial image is displayed, greatly speeding up partial screen updates.
- The chunk size is 47 bytes, determined from the original app. It is unknown if other chunk sizes are supported.
## Contributing ## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

+99
View File
@@ -0,0 +1,99 @@
# LCD Protocol
This page contains the current state of the reverse engineered AOOSTAR display protocol.
See [Linux shell commands](shell_commands.md) how you can switch the display on and off with standard Linux commands.
- **Resolution:** 960 × 376
- **Manufacturer:** Synwit
- **Connected over USB UART** with a proprietary serial communication protocol:
- **USB device ID:** `416:90A1` (as shown by `lsusb`)
- **Linux device (example on Debian):** `/dev/ttyACM0`
- **1,500,000 baud**, 8N1 (likely ignored; actual USB transfer speed is much higher)
## Display Off
**Request:**
<img src="img/lcd_off.png" alt="LCD off EBNF" width="485" height="70">
<details>
```
@startebnf lcd_off
lcd_off = 0xAA, 0x55, 0xAA, 0x55, 0x0A, 0x00, 0x00, 0x00 ;
@endebnf
```
</details>
**Response:**
- Success: character `A`
- Error: _unknown_
## Display On
**Request:**
<img src="img/lcd_on.png" alt="LCD on EBNF" width="485" height="70">
<details>
```
@startebnf lcd_on
lcd_on = 0xAA, 0x55, 0xAA, 0x55, 0x0B, 0x00, 0x00, 0x00 ;
@endebnf
```
</details>
**Response:**
- Success: character `A`
- Error: _unknown_
Note:
- When switching the display on, the last displayed image is immediately shown.
## Display Image
**Request:**
<img src="img/send_image.png" alt="Send image EBNF" width="868" height="423">
<details>
```
@startebnf send_image
send_image = img_cmd_start, { data_chunk }, img_cmd_end ;
img_cmd_start = 0xAA, 0x55, 0xAA, 0x55, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x2F, 0x00, 0x04, 0x0B, 0x00 ;
data_chunk = chunk_header, chunk_offset, rgb565_chunk ;
chunk_header = 0xAA, 0x55, 0xAA, 0x55, 0x08, 0x00, 0x00, 0x00 ;
chunk_offset = ? 32 bit offset in little-endian format ? ;
rgb565_chunk = 47 * ? byte image data in RGB565 format from given index ?;
img_cmd_end = 0xAA, 0x55, 0xAA, 0x55, 0x06, 0x00, 0x00, 0x00 ;
@endebnf
```
</details>
**Response:**
- Success: character `A`
- Error: _unknown_
Notes:
- When sending an image to the screen, the image must be in **RGB565** format (16 bits per pixel).
- `asterctl` performs all graphic operations on an RGB888 image buffer.
- `asterctl` automatically converts the image to RGB565 when sending it to the display.
- The 1.5 Mbps baud rate set in the client is ignored, as actual USB bulk transfer achieves much higher throughput.
For reference, at the nominal serial rate (~1,500,000 baud), it would take approximately 6 seconds to transfer a full image of 721,920 bytes (960 × 376 × 2):
- Display protocol: payload per chunk = 47 bytes; header per chunk = 12 bytes
- Number of chunks: 721,920 / 47 ≈ 15,360 chunks
- Total transmitted data: 15,360 chunks × 59 bytes/chunk = 906,240 bytes
- Serial frame format: 1 start bit + 8 data bits + 1 stop bit = 10 bits/byte
- Effective byte rate: 1,500,000 bits/sec / 10 bits/byte = 150,000 bytes/sec
- Transfer time: 906,240 bytes / 150,000 bytes/sec ≈ 6 seconds
- **Performance:**
- Displaying the first fullscreen image takes around 1.3 seconds.
- Once the new image is fully transferred and the end-header command is sent, the display firmware switches to the new image.
- **Partial Updates:**
- `asterctl` uses a frame cache to send only changed chunks after the initial image is displayed, greatly speeding up partial screen updates.
- The chunk size is 47 bytes, determined from the original app. It is unknown if other chunk sizes are supported.
- There are no fractional chunks: 960x376 x 2 bytes/pixel / 47 bytes/chunk = 15360 chunks
+27
View File
@@ -0,0 +1,27 @@
# Linux Shell Control Commands
Turning the display on or off is possible directly in a Linux shell!
Add your user to the `dialout` group for access to `/dev/ttyACM0`:
```shell
sudo usermod -a -G dialout $USER
```
> You may have to log out and back in for group changes to take effect.
> If not using a Debian based Linux, the tty device might have a different name, or not using the `dialout` group.
## Turn display on
```shell
stty -F /dev/ttyACM0 raw
printf "\252U\252U\v\0\0\0" > /dev/ttyACM0
```
## Turn display off
```shell
stty -F /dev/ttyACM0 raw
printf "\252U\252U\12\0\0\0" > /dev/ttyACM0
```