From 0d1dbaec4cde32eccd10624b5cef43440f25d62b Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Fri, 31 Oct 2025 21:41:52 +0100 Subject: [PATCH 1/2] Improve docs/primitives.md Add more context and information. Signed-off-by: Davide Bettio --- docs/primitives.md | 55 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/docs/primitives.md b/docs/primitives.md index 0093981..3fdadcb 100644 --- a/docs/primitives.md +++ b/docs/primitives.md @@ -6,8 +6,34 @@ # Primitives +AtomGL primitives are the basic drawing elements that make up a display list. Each primitive is +represented as an Erlang tuple with specific parameters defining its appearance and position. + +## Types + +### Colors +Colors are represented as 24-bit RGB values. For example, `0xFF0000` represents red (equivalent to +HTML color `#FF0000`). Display drivers for monochrome devices may apply dithering, while 16-bit +displays may reduce color depth as needed. + +### Coordinates and Sizes +All numeric values are integers. Coordinates are specified in pixels, as are sizes. Subpixel or +half-pixel values are not allowed. + +### Transparent +The `transparent` atom indicates that no background is drawn for the item's bounding rectangle, +allowing the item to be properly rendered over lower items in the display list. This may have +performance implications. + +### Text +Text can be provided as either an Erlang string (a list) or an Elixir string (a binary). UTF-8 +encoding is supported. + ## image +Displays an image at the specified position. The image dimensions are determined by the image tuple +itself. + ```erlang {image, X, Y, % image position in pixels, width and height are implicit @@ -18,12 +44,14 @@ ## scaled_cropped_image +Displays a portion of an image with scaling applied. Useful for sprite sheets or zoomed views. + ```erlang {scaled_cropped_image, X, Y, Width, Height, % bounding rect in pixels BackgroundColor, % RGB background color, a "hex color" can be used here, or transparent atom SourceX, SourceY, % offset inside the source image from where the image is taken - XScaleFactor, YScaleFactor, % integer scaling factor, 1 is original, 2 is twice, etc... + XScaleFactor, YScaleFactor, % integer scaling factor, 1 is original, 2 is twice, etc. Opts, % option keyword list, always []: right now no additional options are supported Image % image tuple } @@ -31,6 +59,8 @@ ## rect +Draws a filled rectangle with the specified color. + ```erlang {rect, X, Y, Width, Height, % bounding rect in pixels @@ -40,11 +70,13 @@ ## text +Renders text with the specified font and colors. + ```erlang {text, X, Y, % text position in pixels, width and height are implicit Font, % a font name atom, such as default16px - TextColor, % RGB background color, a "hex color" can be used here + TextColor, % RGB text color, a "hex color" can be used here BackgroundColor, % RGB background color, a "hex color" can be used here, or transparent atom Text % simple text string, UTF-8 can be used, rich text and control characters are not supported } @@ -52,9 +84,22 @@ ## Image Tuples -An image tupple contains all the information required for displaying an image. +An image tuple contains all the information required for displaying an image. Images are tagged +tuples with the following structure: -Such as: ```erlang -{rgba8888, Width, Height, RawPixelBinary} +{Format, Width, Height, RawPixelBinary} +``` + +The format tag indicates the pixel format. For example, `rgba8888` means: +- RGBA format with alpha channel +- Byte order: R, G, B, A +- Each component is 8 bits + +**Important:** Width and height must exactly match the dimensions of the image data in the binary. +Incorrect values will result in corrupted image display. + +**Tip:** You can convert images to raw RGBA format using ImageMagick: +```bash +convert -define h:format=rgba -depth 8 image.png image.rgba ``` From 32c27f33763b6bb21a36d8f679bba6cb05315bae Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Fri, 31 Oct 2025 23:01:05 +0100 Subject: [PATCH 2/2] Add docs/display-drivers.md Add additional documentation for displays and their drivers. Signed-off-by: Davide Bettio --- README.Md | 2 + docs/display-drivers.md | 297 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 docs/display-drivers.md diff --git a/README.Md b/README.Md index 6fbead0..5af0cb5 100644 --- a/README.Md +++ b/README.Md @@ -89,6 +89,8 @@ software dithering [SDL Linux display](sdl_display/) is also supported and can be built as an AtomVM plugin. +See also [display drivers](docs/display-drivers.md) documentation page for additional information. + ## Platform Support AtomGL is currently compatible with **ESP32** microcontrollers. Linux with SDL is also supported for diff --git a/docs/display-drivers.md b/docs/display-drivers.md new file mode 100644 index 0000000..3e3e106 --- /dev/null +++ b/docs/display-drivers.md @@ -0,0 +1,297 @@ +# Display Drivers + +This document describes how to configure and use the various display drivers supported by AtomGL. + +## Overview + +To use a display with AtomGL, you need: +1. A communication interface (either SPI or I²C) that must be opened and configured +2. A display driver selected by providing a `compatible` string that matches your display model + +The display driver will handle all the low-level communication and rendering, while you provide +display lists describing what should be shown. + +## Using SPI Displays + +Most displays use SPI for communication. First, configure and open an SPI host, then pass it to the +display driver. + +### Basic SPI Setup + +```elixir +# Configure SPI bus +spi_opts = %{ + bus_config: %{ + sclk: 35, # Serial clock pin + mosi: 34, # Master Out Slave In pin + miso: 33, # Master In Slave Out pin (optional for displays) + peripheral: "spi2" + }, + device_config: %{ + # Device-specific configuration + } +} + +# Open SPI host +spi_host = :spi.open(spi_opts) + +# Configure display +display_opts = [ + spi_host: spi_host, + width: 320, + height: 240, + compatible: "ilitek,ili9341", + cs: 22, # Chip select pin + dc: 21, # Data/Command pin + reset: 18, # Reset pin + # Additional options... +] + +# Open display port +display = :erlang.open_port({:spawn, "display"}, display_opts) +``` + +## Using I²C Displays + +Some displays (like small OLED screens) use I²C communication. + +### Basic I²C Setup + +```elixir +# Configure I²C bus +i2c_opts = [ + sda: 8, # Data pin + scl: 9, # Clock pin + clock_speed_hz: 1_000_000, + peripheral: "i2c0" +] + +# Open I²C host +i2c_host = :i2c.open(i2c_opts) + +# Configure display +display_opts = [ + i2c_host: i2c_host, + width: 128, + height: 64, + compatible: "solomon-systech,ssd1306", + invert: true +] + +# Open display port +display = :erlang.open_port({:spawn, "display"}, display_opts) +``` + +## Common Display Options + +### Backlight Configuration + +Many displays support backlight control: + +```elixir +backlight_opts = [ + backlight: 5, # Backlight GPIO pin + backlight_active: :low, # :low or :high + backlight_enabled: true # Enable on startup +] +``` + +## Supported Displays + +### ILI9341 / ILI9342C (ilitek,ili9341 / ilitek,ili9342c) + +240×320 TFT display with 16-bit colors. Both variants use the same driver. + +**Compatible strings:** `"ilitek,ili9341"` or `"ilitek,ili9342c"` + +| Option | Type | Description | Default | +|--------|------|-------------|---------| +| `spi_host` | term | SPI host reference | Required | +| `width` | integer | Display width in pixels | 320 | +| `height` | integer | Display height in pixels | 240 | +| `cs` | integer | Chip select GPIO pin | Required | +| `dc` | integer | Data/Command GPIO pin | Required | +| `reset` | integer | Reset GPIO pin | Required | +| `rotation` | integer | Display rotation (0-3) | 0 | +| `enable_tft_invon` | boolean | Enable color inversion | false | +| `backlight` | integer | Backlight GPIO pin | Optional | +| `backlight_active` | atom | Backlight active level (:low/:high) | Optional | +| `backlight_enabled` | boolean | Enable backlight on init | Optional | + +**Example:** +```elixir +ili9341_opts = [ + spi_host: spi_host, + compatible: "ilitek,ili9341", + width: 320, + height: 240, + cs: 22, + dc: 21, + reset: 18, + rotation: 1, + backlight: 5, + backlight_active: :low, + backlight_enabled: true, + enable_tft_invon: false +] +``` + +### ST7789 / ST7796 (sitronix,st7789 / sitronix,st7796) + +TFT displays with 16-bit colors. + +**Compatible strings:** `"sitronix,st7789"` or `"sitronix,st7796"` + +| Option | Type | Description | Default | +|--------|------|-------------|---------| +| `spi_host` | term | SPI host reference | Required | +| `width` | integer | Display width in pixels | 320 | +| `height` | integer | Display height in pixels | 240 | +| `cs` | integer | Chip select GPIO pin | Required | +| `dc` | integer | Data/Command GPIO pin | Required | +| `reset` | integer | Reset GPIO pin | Optional | +| `rotation` | integer | Display rotation (0-3) | 0 | +| `x_offset` | integer | X-axis offset in pixels | 0 | +| `y_offset` | integer | Y-axis offset in pixels | 0 | +| `enable_tft_invon` | boolean | Enable color inversion | false | +| `init_list` | list | Custom initialization sequence | Optional | +| `backlight` | integer | Backlight GPIO pin | Optional | +| `backlight_active` | atom | Backlight active level (:low/:high) | Optional | +| `backlight_enabled` | boolean | Enable backlight on init | Optional | + +**Example with custom initialization:** +```elixir +st7796_opts = [ + spi_host: spi_host, + compatible: "sitronix,st7796", + width: 480, + height: 222, + y_offset: 49, + cs: 38, + dc: 37, + init_list: [ + {0x01, <<0x00>>}, # {command, <>} + {:sleep_ms, 120} # wait 120 ms + # ... + ] +] +``` + +### SSD1306 / SH1106 (solomon-systech,ssd1306 / sino-wealth,sh1106) + +128×64 monochrome OLED displays using I²C communication. + +**Compatible strings:** `"solomon-systech,ssd1306"` or `"sino-wealth,sh1106"` + +| Option | Type | Description | Default | +|--------|------|-------------|---------| +| `i2c_host` | term | I²C host reference | Required | +| `width` | integer | Display width in pixels | 128 | +| `height` | integer | Display height in pixels | 64 | +| `reset` | integer | Reset GPIO pin | Optional | +| `invert` | boolean | Invert display colors | false | + +**Example:** +```elixir +ssd1306_opts = [ + i2c_host: i2c_host, + compatible: "solomon-systech,ssd1306", + width: 128, + height: 64, + invert: false, + reset: 16 # Optional +] +``` + +### Sharp Memory LCD (sharp,memory-lcd) + +400×240 monochrome memory LCD with ultra-low power consumption. + +**Compatible string:** `"sharp,memory-lcd"` + +| Option | Type | Description | Default | +|--------|------|-------------|---------| +| `spi_host` | term | SPI host reference | Required | +| `width` | integer | Display width in pixels | 400 | +| `height` | integer | Display height in pixels | 240 | +| `cs` | integer | Chip select GPIO pin | Required | +| `en` | integer | Enable GPIO pin | Optional | + +**Example:** +```elixir +sharp_lcd_opts = [ + spi_host: spi_host, + compatible: "sharp,memory-lcd", + cs: 22, + en: 23 # Optional enable pin +] +``` + +### Waveshare 5.65" ACeP 7-Color (waveshare,5in65-acep-7c) + +600×480 7-color E-Paper display. Driver has software dithering support. + +**Compatible string:** `"waveshare,5in65-acep-7c"` + +| Option | Type | Description | Default | +|--------|------|-------------|---------| +| `spi_host` | term | SPI host reference | Required | +| `width` | integer | Display width in pixels | 600 | +| `height` | integer | Display height in pixels | 480 | +| `cs` | integer | Chip select GPIO pin | Required | +| `dc` | integer | Data/Command GPIO pin | Required | +| `reset` | integer | Reset GPIO pin | Required | +| `busy` | integer | Busy signal GPIO pin | Required | + +**Note:** E-Paper displays have slow refresh rates due to their technology. + +**Example:** +```elixir +epaper_opts = [ + spi_host: spi_host, + compatible: "waveshare,5in65-acep-7c", + cs: 22, + dc: 21, + reset: 18, + busy: 19 +] +``` + +## Custom Initialization Sequences + +Many displays require specific initialization sequences with carefully tuned values for voltages, +timing, gamma curves, and other display-specific parameters. For displays that need custom +initialization, you can provide an `init_list`: + +```elixir +init_list: [ + {0x01, <<0x00>>}, + {:sleep_ms, 120} + # ... +] +``` + +Each entry can be: +- `{command, data}` - Send command byte followed by data bytes +- `{:sleep_ms, milliseconds}` - Delay for specified time + +These sequences are highly specific to each display model and typically come from the manufacturer's datasheet or reference implementation. + +## Updating the Display + +Once configured, update the display using the display port: + +```elixir +# Create display list +items = [ + {:text, 10, 20, :default16px, 0x000000, 0xFFFFFF, "Hello, World!"}, + {:rect, 0, 0, 320, 240, 0xFFFFFF} # White background +] + +# Send update command +:erlang.port_call(display, {:update, items}, 5000) +``` + +**Note:** While direct port calls work, the recommended approach is to use [avm_scene](https://github.com/atomvm/avm_scene) which provides a higher-level interface for managing display updates and handling the display list lifecycle properly. + +For more information about display primitives and the display list concept, see the [primitives documentation](primitives.md).