Current section

Files

Jump to
ili9486_elixir lib ili9486_elixir.ex
Raw

lib/ili9486_elixir.ex

defmodule ILI9486 do
@moduledoc """
ILI9486 Elixir driver
"""
use GenServer
import Bitwise
@enforce_keys [:gpio, :opts, :lcd_spi, :data_bus, :display_mode, :chunk_size]
defstruct [
:gpio,
:opts,
:lcd_spi,
:touch_spi,
:touch_pid,
:pix_fmt,
:rotation,
:mad_mode,
:data_bus,
:display_mode,
:frame_rate,
:diva,
:rtna,
:chunk_size
]
@typedoc "Supported pixel formats"
@type pixel_format :: :rgb565 | :bgr565 | :rgb666 | :bgr666
@typedoc "Screen rotation in degrees"
@type rotation :: 0 | 90 | 180 | 270
@typedoc "MADCTL orientation / RGB mode"
@type mad_mode :: :right_down | :right_up | :rgb_mode
@typedoc "Display mode"
@type display_mode :: :normal | :partial | :idle
@typedoc "Supported frame rates (Hz)"
@type frame_rate ::
28 | 30 | 32 | 34 | 36 | 39 | 42 | 46 | 50 | 56 | 62 | 70 | 81 | 96 | 117
@typedoc "Division ratio for internal clocks (0=focs, 1=focs/2, 2=focs/4, 3=focs/8)"
@type diva :: 0..3
@typedoc "RTNA timing value (0b10000=16 .. 0b11111=31)"
@type rtna :: 0b10000..0b11111
@typedoc "Parallel data bus mode"
@type data_bus :: :parallel_8bit | :parallel_16bit
@typedoc "Supported source color formats"
@type color_format :: :rgb888 | :bgr888
@typedoc "Display struct"
@type t :: %__MODULE__{
gpio: keyword(),
opts: keyword(),
lcd_spi: any(),
touch_spi: any(),
touch_pid: pid() | nil,
pix_fmt: pixel_format(),
rotation: rotation(),
mad_mode: mad_mode(),
data_bus: data_bus(),
display_mode: display_mode(),
frame_rate: pos_integer(),
diva: diva(),
rtna: rtna(),
chunk_size: pos_integer()
}
@typedoc "Image data buffer accepted by display functions"
@type image_data :: binary() | [byte() | [byte()]]
@typedoc "Callback invoked on touch IRQ"
@type touch_callback :: GPIOIRQDevice.irq_callback()
@typedoc """
Options for `start_link/1`.
- `:port` - SPI port number (default: `0`).
- `:lcd_cs` - LCD chip select (default: `0`).
- `:touch_cs` - Touch panel chip select, optional (default: `nil`).
- `:touch_irq` - Touch panel IRQ pin (active low), optional (default: `nil`).
- `:touch_speed_hz` - SPI speed for touch panel (default: `50_000`).
- `:dc` - D/C pin (default: `24`).
- `:rst` - Reset pin for ILI9486, optional (default: `nil`).
- `:width` - Display width in pixels (default: `480`).
- `:height` - Display height in pixels (default: `320`).
- `:offset_top` - Vertical offset (default: `0`).
- `:offset_left` - Horizontal offset (default: `0`).
- `:speed_hz` - SPI speed for LCD (default: `16_000_000`).
- `:pix_fmt` - Pixel format, one of `:bgr565 | :rgb565 | :bgr666 | :rgb666` (default: `:bgr565`).
- `:rotation` - Rotation, one of `0 | 90 | 180 | 270` (default: `90`).
- `:mad_mode` - MAD mode, one of `:right_down | :right_up | :rgb_mode` (default: `:right_down`).
- `:display_mode` - Display mode, one of `:normal | :partial | :idle` (default: `:normal`).
- `:frame_rate` - Frame rate (Hz), one of `28, 30, 32, 34, 36, 39, 42, 46, 50, 56, 62, 70, 81, 96, 117` (default: `70`).
- `:diva` - Clock division (`0` = focs, `1` = focs/2, `2` = focs/4, `3` = focs/8) (default: `0`).
- `:rtna` - Line period `RTNA[4:0]`, `0b10000` (16) to `0b11111` (31), default `0b10001` (17); each step increases clocks by 1.
- `:is_high_speed` - Use high-speed variant (125 MHz SPI) (default: `false`).
- `:chunk_size` - Batch transfer size; default `4096` (lo-speed) or `0x8000` (hi-speed).
- `:spi_lcd` - Pre-opened SPI handle for LCD; overrides `:port`/`:lcd_cs` when set.
- `:spi_touch` - Pre-opened SPI handle for touch; overrides `:port`/`:touch_cs` when set.
- `:gpio_dc` - Pre-opened GPIO for D/C; overrides `:dc` when set.
- `:gpio_rst` - Pre-opened GPIO for reset; overrides `:rst` when set.
- `:name` - Registered name for the GenServer.
"""
@type ili9486_option ::
{:port, non_neg_integer()}
| {:lcd_cs, non_neg_integer()}
| {:touch_cs, non_neg_integer() | nil}
| {:touch_irq, non_neg_integer() | nil}
| {:touch_speed_hz, pos_integer()}
| {:dc, non_neg_integer()}
| {:rst, non_neg_integer() | nil}
| {:width, pos_integer()}
| {:height, pos_integer()}
| {:offset_top, non_neg_integer()}
| {:offset_left, non_neg_integer()}
| {:speed_hz, pos_integer()}
| {:pix_fmt, pixel_format()}
| {:rotation, rotation()}
| {:mad_mode, mad_mode()}
| {:display_mode, display_mode()}
| {:frame_rate, frame_rate()}
| {:diva, diva()}
| {:rtna, rtna()}
| {:is_high_speed, boolean()}
| {:chunk_size, pos_integer()}
| {:spi_lcd, any()}
| {:spi_touch, any()}
| {:gpio_dc, any()}
| {:gpio_rst, any()}
| {:name, GenServer.name()}
@doc """
Start a new connection to an ILI9486.
**return**: `%ILI9486{}`
## Example
```elixir
# default
# assuming LCD device at /dev/spidev0.0
# DC connects to PIN 24
# RST not connected
# SPI speed: 16MHz
# Pixel Format: BGR565
{:ok, disp} = ILI9486.start_link()
```
```elixir
# default with touch panel
# DC connects to PIN 24
# RST connects to PIN 25
# SPI speed: 16MHz
# Pixel Format: RGB666 (for demo only, not necessary)
# Touch panel device at /dev/spidev0.1
# Touch panel IRQ PIN 17
{:ok, disp} = ILI9486.start_link(
speed_hz: 16_000_000,
pix_fmt: :bgr666,
rst: 25,
touch_cs: 1,
touch_irq: 17
)
```
high-speed variant (125MHz SPI)
```elixir
# assuming LCD device at /dev/spidev0.0
# DC connects to PIN 24
# RST connects to PIN 25 (for demo only, not necessary)
# SPI speed: 125MHz
# Pixel Format: BGR666 (for demo only, not necessary)
{:ok, disp} = ILI9486.start_link(
is_high_speed: true,
speed_hz: 125_000_000,
pix_fmt: :bgr666,
rst: 25
)
```
high-speed variant (125MHz SPI) with touch panel
```elixir
# assuming LCD device at /dev/spidev0.0
# DC connects to PIN 24
# RST connects to PIN 25 (for demo only, not necessary)
# SPI speed: 125MHz
# Pixel Format: BGR666 (for demo only, not necessary)
# Touch panel device at /dev/spidev0.1
# Touch panel IRQ PIN 17
{:ok, disp} = ILI9486.start_link(
is_high_speed: true,
speed_hz: 125_000_000,
pix_fmt: :bgr666,
rst: 25,
touch_cs: 1,
touch_irq: 17
)
```
"""
@spec start_link([ili9486_option()]) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: opts[:name])
end
@deprecated "Use start_link/1 instead"
@spec new([ili9486_option()]) :: GenServer.on_start()
def new(opts \\ []) do
GenServer.start_link(__MODULE__, opts)
end
@deprecated "Use start_link/1 instead"
@spec new!([ili9486_option()]) :: pid()
def new!(opts \\ []) do
{:ok, pid} = GenServer.start_link(__MODULE__, opts)
pid
end
@impl true
@spec init(Keyword.t()) :: {:ok, t()}
def init(opts) do
# Make sure terminate/2 is called on shutdown
Process.flag(:trap_exit, true)
port = opts[:port] || 0
lcd_cs = opts[:lcd_cs] || 0
touch_cs = opts[:touch_cs]
touch_irq = opts[:touch_irq]
dc = opts[:dc] || 24
speed_hz = opts[:speed_hz] || 16_000_000
touch_speed_hz = opts[:touch_speed_hz] || 50000
width = opts[:width] || 480
height = opts[:height] || 320
offset_top = opts[:offset_top] || 0
offset_left = opts[:offset_left] || 0
rst = opts[:rst]
pix_fmt = opts[:pix_fmt] || :bgr565
rotation = opts[:rotation] || 90
mad_mode = opts[:mad_mode] || :right_down
display_mode = opts[:display_mode] || :normal
frame_rate = opts[:frame_rate] || 70
diva = opts[:diva] || 0b00
rtna = opts[:rtna] || 0b10001
is_high_speed = opts[:is_high_speed] || false
calc_chunk_size = fn spi_bus ->
from_opts = opts[:chunk_size]
desired_size =
cond do
is_integer(from_opts) and from_opts > 0 -> from_opts
is_high_speed -> 0x8000
true -> 4_096
end
driver_limit =
cond do
function_exported?(Circuits.SPI, :max_transfer_size, 1) ->
Circuits.SPI.max_transfer_size(spi_bus)
function_exported?(Circuits.SPI, :max_transfer_size, 0) ->
Circuits.SPI.max_transfer_size()
true ->
desired_size
end
effective_limit = if driver_limit > 0, do: driver_limit, else: desired_size
min(desired_size, effective_limit)
end
# supported data connection
# 8-bit parallel MCU interface for low speed ones
# - Waveshare RPi 3.5 LCD (A) / Tested
# - Waveshare RPi 3.5 LCD (B)
# 16-bit parallel MCU interface for the high speed one
# - Waveshare RPi 3.5 LCD (C) / Tested
# :parallel_16bit and :parallel_8bit supported colors
# - 65K colors
# - 262K colors
data_bus = if is_high_speed, do: :parallel_16bit, else: :parallel_8bit
lcd_spi =
Keyword.get_lazy(opts, :spi_lcd, fn ->
{:ok, bus} = _init_spi(port, lcd_cs, speed_hz)
bus
end)
touch_spi =
Keyword.get_lazy(opts, :spi_touch, fn ->
if touch_cs do
{:ok, bus} = _init_spi(port, touch_cs, touch_speed_hz)
bus
end
end)
gpio_dc =
Keyword.get_lazy(opts, :gpio_dc, fn ->
{:ok, pin} = Circuits.GPIO.open(dc, :output)
pin
end)
gpio_rst =
Keyword.get_lazy(opts, :gpio_rst, fn ->
if rst, do: _init_reset(rst)
end)
{:ok, touch_pid} = _init_touch_irq(touch_irq)
display =
%ILI9486{
lcd_spi: lcd_spi,
touch_spi: touch_spi,
touch_pid: touch_pid,
gpio: [
dc: gpio_dc,
rst: gpio_rst
],
opts: [
port: port,
lcd_cs: lcd_cs,
touch_cs: touch_cs,
dc: dc,
speed_hz: speed_hz,
touch_speed_hz: touch_speed_hz,
width: width,
height: height,
offset_top: offset_top,
offset_left: offset_left,
rst: rst
],
pix_fmt: pix_fmt,
rotation: rotation,
mad_mode: mad_mode,
data_bus: data_bus,
display_mode: display_mode,
frame_rate: frame_rate,
diva: diva,
rtna: rtna,
chunk_size: calc_chunk_size.(lcd_spi)
}
|> _reset()
|> _init(is_high_speed)
{:ok, display}
end
@doc """
Closes all SPI and GPIO resources on shutdown.
"""
@impl true
@spec terminate(any(), t()) :: :ok
def terminate(_reason, %{lcd_spi: lcd_spi, touch_spi: touch_spi, gpio: gpio}) do
dc_pin = gpio[:dc]
rst_pin = gpio[:rst]
Circuits.SPI.close(lcd_spi)
if touch_spi, do: Circuits.SPI.close(touch_spi)
Circuits.GPIO.close(dc_pin)
if rst_pin, do: Circuits.GPIO.close(rst_pin)
:ok
end
@doc """
Reset the display, if reset pin is connected.
- **display**: `%ILI9486{}`
**return**: `display`
"""
@spec reset(pid()) :: :ok
def reset(self_pid) do
GenServer.call(self_pid, :reset)
end
defp _reset(display = %ILI9486{gpio: gpio}) do
gpio_rst = gpio[:rst]
if gpio_rst != nil do
Circuits.GPIO.write(gpio_rst, 1)
:timer.sleep(500)
Circuits.GPIO.write(gpio_rst, 0)
:timer.sleep(500)
Circuits.GPIO.write(gpio_rst, 1)
:timer.sleep(500)
end
display
end
@doc """
Get screen size
- **display**: `%ILI9486{}`
**return**: `%{height: height, width: width}`
"""
@spec size(pid()) :: %{height: pos_integer(), width: pos_integer()}
def size(self_pid) do
GenServer.call(self_pid, :size)
end
defp _size(%ILI9486{opts: opts}) do
%{height: opts[:height], width: opts[:width]}
end
@doc """
Get display pixel format
- **display**: `%ILI9486{}`
**return**: one of `:bgr565`, `:rgb565`, `:bgr666`, `:rgb666`
"""
@spec pix_fmt(pid()) :: pixel_format()
def pix_fmt(self_pid) do
GenServer.call(self_pid, :pix_fmt)
end
defp _pix_fmt(%ILI9486{pix_fmt: pix_fmt}) do
pix_fmt
end
@doc """
Set display pixel format
- **display**: `%ILI9486{}`
- **pix_fmt**: one of `:bgr565`, `:rgb565`, `:bgr666`, `:rgb666`
**return**: `display`
"""
@spec set_pix_fmt(pid(), pixel_format()) :: :ok
def set_pix_fmt(self_pid, pix_fmt)
when pix_fmt == :bgr565 or pix_fmt == :rgb565 or pix_fmt == :bgr666 or pix_fmt == :rgb666 do
GenServer.call(self_pid, {:set_pix_fmt, pix_fmt})
end
defp _set_pix_fmt(display = %ILI9486{}, pix_fmt)
when pix_fmt == :bgr565 or pix_fmt == :rgb565 or pix_fmt == :bgr666 or pix_fmt == :rgb666 do
%ILI9486{display | pix_fmt: pix_fmt}
|> _command(kMADCTL(), cmd_data: _mad_mode(display))
end
@doc """
Turn on/off display
- **display**: `%ILI9486{}`
- **status**: either `:on` or `:off`
**return**: `display`
"""
@spec set_display(pid(), :on | :off) :: :ok
def set_display(self_pid, status) when status == :on or status == :off do
GenServer.call(self_pid, {:set_display, status})
end
defp _set_display(display = %ILI9486{}, :on) do
_command(display, kDISPON())
end
defp _set_display(display = %ILI9486{}, :off) do
_command(display, kDISPOFF())
end
@doc """
Set display mode
- **display**: `%ILI9486{}`
- **display_mode**: Valid values: `:normal`, `:partial`, `:idle`
**return**: `display`
"""
@spec set_display_mode(pid(), display_mode()) :: :ok
def set_display_mode(self_pid, display_mode) do
GenServer.call(self_pid, {:set_display_mode, display_mode})
end
defp _set_display_mode(display = %ILI9486{}, display_mode = :normal) do
%ILI9486{display | display_mode: display_mode}
|> _command(kNORON())
end
defp _set_display_mode(display = %ILI9486{}, display_mode = :partial) do
%ILI9486{display | display_mode: display_mode}
|> _command(kPTLON())
end
defp _set_display_mode(display = %ILI9486{}, display_mode = :idle) do
%ILI9486{display | display_mode: display_mode}
|> _command(kIDLEON())
end
@doc """
Set frame rate
- **display**: `%ILI9486{}`
- **frame_rate**: Valid value should be one of the following
- 28
- 30
- 32
- 34
- 36
- 39
- 42
- 46
- 50
- 56
- 62
- 70
- 81
- 96
- 117
**return**: `:ok`
"""
@spec set_frame_rate(pid(), frame_rate()) :: :ok
def set_frame_rate(self_pid, frame_rate) do
GenServer.call(self_pid, {:set_frame_rate, frame_rate})
end
defp _set_frame_rate(
display = %ILI9486{display_mode: display_mode, diva: diva, rtna: rtna},
frame_rate
) do
index = Enum.find_index(_valid_frame_rates(display_mode), fn valid -> valid == frame_rate end)
p1 =
index
|> bsl(4)
|> bor(diva)
%ILI9486{display | frame_rate: frame_rate}
|> _command(kFRMCTR1())
|> _data(p1)
|> _data(rtna)
end
defp _valid_frame_rates(:normal) do
[28, 30, 32, 34, 36, 39, 42, 46, 50, 56, 62, 70, 81, 96, 117, 117]
end
@doc """
Write the provided 16bit BGR565/RGB565 image to the hardware.
- **display**: `%ILI9486{}`
- **image_data**: Should be 16bit BGR565/RGB565 format (same channel order as in `display`) and
the same dimensions (width x height x 3) as the display hardware.
**return**: `display`
"""
@spec display_565(pid(), image_data()) :: :ok
def display_565(self_pid, image_data) when is_binary(image_data) or is_list(image_data) do
GenServer.call(self_pid, {:display_565, image_data})
end
defp _display_565(display, image_data) when is_binary(image_data) do
_display_565(display, :binary.bin_to_list(image_data))
end
defp _display_565(display, image_data) when is_list(image_data) do
display
|> _set_window(x0: 0, y0: 0, x1: nil, y2: nil)
|> _send(image_data, true, false)
end
@doc """
Write the provided 18bit BGR666/RGB666 image to the hardware.
- **display**: `%ILI9486{}`
- **image_data**: Should be 18bit BGR666/RGB666 format (same channel order as in `display`) and
the same dimensions (width x height x 3) as the display hardware.
**return**: `display`
"""
@spec display_666(pid(), image_data()) :: :ok
def display_666(self_pid, image_data) when is_binary(image_data) or is_list(image_data) do
GenServer.call(self_pid, {:display_666, image_data})
end
defp _display_666(display, image_data) when is_binary(image_data) do
_display_666(display, :binary.bin_to_list(image_data))
end
defp _display_666(display, image_data) when is_list(image_data) do
display
|> _set_window(x0: 0, y0: 0, x1: nil, y2: nil)
|> _send(image_data, true, false)
end
@doc """
Write the provided 24bit BGR888/RGB888 image to the hardware.
- **display**: `%ILI9486{}`
- **image_data**: Should be 24bit format and the same dimensions (width x height x 3) as the display hardware.
- **pix_fmt**: Either `:rgb888` or `:bgr888`. Indicates the channel order of the provided `image_data`.
**return**: `display`
"""
@spec display(pid(), image_data(), color_format()) :: :ok
def display(self_pid, image_data, source_color)
when is_binary(image_data) and (source_color == :rgb888 or source_color == :bgr888) do
GenServer.call(self_pid, {:display, image_data, source_color})
end
defp _display(display = %ILI9486{pix_fmt: target_color}, image_data, source_color)
when is_binary(image_data) and (source_color == :rgb888 or source_color == :bgr888) and
(target_color == :rgb565 or target_color == :bgr565) do
_display_565(display, _to_565(image_data, source_color, target_color))
end
defp _display(display = %ILI9486{pix_fmt: target_color}, image_data, source_color)
when is_binary(image_data) and (source_color == :rgb888 or source_color == :bgr888) and
(target_color == :rgb666 or target_color == :bgr666) do
_display_666(display, _to_666(image_data, source_color, target_color))
end
defp _display(display, image_data, source_color)
when is_list(image_data) and (source_color == :rgb888 or source_color == :bgr888) do
_display(
display,
image_data_to_binary(image_data),
source_color
)
end
defp image_data_to_binary(image_data) do
image_data
|> Enum.map(fn
row_of_bytes when is_list(row_of_bytes) -> Enum.map(row_of_bytes, &<<&1::8>>)
byte when is_integer(byte) -> <<byte::8>>
end)
|> IO.iodata_to_binary()
end
@doc """
Set touch panel callback function
- **display**: `%ILI9486{}`
- **callback**: callback function. 3 arguments: `pin`, `timestamp`, `status`
"""
@spec set_touch_callback(pid(), touch_callback()) :: :ok
def set_touch_callback(self_pid, callback) when is_function(callback, 3) do
GenServer.call(self_pid, {:set_touch_callback, callback})
end
defp _set_touch_callback(display = %ILI9486{touch_pid: touch_pid}, callback)
when is_function(callback, 3) do
GPIOIRQDevice.set_callback(touch_pid, callback)
display
end
@doc """
Write a byte to the display as command data.
- **display**: `%ILI9486{}`
- **cmd**: command data
- **opts**:
- **cmd_data**: cmd data to be sent.
Default value: `[]`. (no data will be sent)
- **delay**: wait `delay` ms after the cmd data is sent
Default value: `0`. (no wait)
**return**: `display`
"""
@spec command(pid(), byte(), Keyword.t()) :: :ok
def command(self_pid, cmd, opts \\ []) when is_integer(cmd) do
GenServer.call(self_pid, {:command, cmd, opts})
end
defp _command(display, cmd, opts \\ [])
defp _command(display = %ILI9486{data_bus: :parallel_8bit}, cmd, opts) when is_integer(cmd) do
cmd_data = opts[:cmd_data] || []
delay = opts[:delay] || 0
display
|> _send(cmd, false, false)
|> _data(cmd_data)
:timer.sleep(delay)
display
end
defp _command(display = %ILI9486{data_bus: :parallel_16bit}, cmd, opts) when is_integer(cmd) do
cmd_data = opts[:cmd_data] || []
delay = opts[:delay] || 0
display
|> _send(cmd, false, true)
|> _data(cmd_data)
:timer.sleep(delay)
display
end
@doc """
Write a byte or array of bytes to the display as display data.
- **display**: `%ILI9486{}`
- **data**: display data
**return**: `display`
"""
@spec data(pid(), iodata()) :: :ok
def data(_self_pid, []), do: :ok
def data(self_pid, data) do
GenServer.call(self_pid, {:data, data})
end
defp _data(display, []), do: display
defp _data(display = %ILI9486{data_bus: :parallel_8bit}, data) do
_send(display, data, true, false)
end
defp _data(display = %ILI9486{data_bus: :parallel_16bit}, data) do
_send(display, data, true, true)
end
@doc """
Send bytes to the ILI9486
- **display**: `%ILI9486{}`
- **bytes**: The bytes to be sent to `display`
- `when is_integer(bytes)`,
`sent` will take the 8 least-significant bits `[band(bytes, 0xFF)]`
and send it to `display`
- `when is_list(bytes)`, `bytes` will be casting to bitstring and then sent
to `display`
- **is_data**:
- `true`: `bytes` will be sent as data
- `false`: `bytes` will be sent as commands
**return**: `display`
"""
@spec send(pid(), integer() | iodata(), boolean()) :: :ok
def send(self_pid, bytes, is_data)
when (is_integer(bytes) or is_list(bytes)) and is_boolean(is_data) do
GenServer.call(self_pid, {:send, bytes, is_data})
end
defp to_be_u16(u8_bytes) do
u8_bytes
|> Enum.map(fn u8 -> [0x00, u8] end)
|> IO.iodata_to_binary()
end
defp chunk_binary(binary, chunk_size) when is_binary(binary) do
total_bytes = byte_size(binary)
full_chunks = div(total_bytes, chunk_size)
chunks =
if full_chunks > 0 do
for i <- 0..(full_chunks - 1), reduce: [] do
acc -> [:binary.part(binary, chunk_size * i, chunk_size) | acc]
end
else
[]
end
remaining = rem(total_bytes, chunk_size)
chunks =
if remaining > 0 do
[:binary.part(binary, chunk_size * full_chunks, remaining) | chunks]
else
chunks
end
Enum.reverse(chunks)
end
defp _send(display, bytes, is_data, to_be16 \\ false)
defp _send(display = %ILI9486{}, bytes, true, to_be16) do
_send(display, bytes, 1, to_be16)
end
defp _send(display = %ILI9486{}, bytes, false, to_be16) do
_send(display, bytes, 0, to_be16)
end
defp _send(display = %ILI9486{}, bytes, is_data, to_be16)
when (is_data == 0 or is_data == 1) and is_integer(bytes) do
_send(display, <<Bitwise.band(bytes, 0xFF)>>, is_data, to_be16)
end
defp _send(display = %ILI9486{}, bytes, is_data, to_be16)
when (is_data == 0 or is_data == 1) and is_list(bytes) do
_send(display, IO.iodata_to_binary(bytes), is_data, to_be16)
end
defp _send(
display = %ILI9486{gpio: gpio, lcd_spi: spi, chunk_size: chunk_size},
bytes,
is_data,
to_be16
)
when (is_data == 0 or is_data == 1) and is_binary(bytes) do
gpio_dc = gpio[:dc]
bytes = if to_be16, do: to_be_u16(:binary.bin_to_list(bytes)), else: bytes
Circuits.GPIO.write(gpio_dc, is_data)
for xfdata <- chunk_binary(bytes, chunk_size) do
{:ok, _ret} = Circuits.SPI.transfer(spi, xfdata)
end
display
end
@impl true
def handle_call(:reset, _from, display) do
{:reply, :ok, _reset(display)}
end
@impl true
def handle_call(:size, _from, display) do
ret = _size(display)
{:reply, ret, display}
end
@impl true
def handle_call(:pix_fmt, _from, display) do
ret = _pix_fmt(display)
{:reply, ret, display}
end
@impl true
def handle_call({:set_pix_fmt, pix_fmt}, _from, display) do
{:reply, :ok, _set_pix_fmt(display, pix_fmt)}
end
@impl true
def handle_call({:set_display, status}, _from, display) do
{:reply, :ok, _set_display(display, status)}
end
@impl true
def handle_call({:set_display_mode, display_mode}, _from, display) do
{:reply, :ok, _set_display_mode(display, display_mode)}
end
@impl true
def handle_call({:set_frame_rate, frame_rate}, _from, display) do
{:reply, :ok, _set_frame_rate(display, frame_rate)}
end
@impl true
def handle_call({:display_565, image_data}, _from, display) do
{:reply, :ok, _display_565(display, image_data)}
end
@impl true
def handle_call({:display_666, image_data}, _from, display) do
{:reply, :ok, _display_666(display, image_data)}
end
@impl true
def handle_call({:display, image_data, source_color}, _from, display) do
{:reply, :ok, _display(display, image_data, source_color)}
end
@impl true
def handle_call({:set_touch_callback, callback}, _from, display) do
{:reply, :ok, _set_touch_callback(display, callback)}
end
@impl true
def handle_call({:command, cmd, opts}, _from, display) do
{:reply, :ok, _command(display, cmd, opts)}
end
@impl true
def handle_call({:data, data}, _from, display) do
{:reply, :ok, _data(display, data)}
end
@impl true
def handle_call({:send, bytes, is_data}, _from, display) do
{:reply, :ok, _send(display, bytes, is_data)}
end
defp _init_spi(_port, nil, _speed_hz), do: {:ok, nil}
defp _init_spi(port, cs, speed_hz) when cs >= 0 do
Circuits.SPI.open("spidev#{port}.#{cs}", speed_hz: speed_hz)
end
defp _init_spi(_port, _cs, _speed_hz), do: {:error, :invalid_cs}
defp _init_touch_irq(nil), do: {:ok, nil}
defp _init_touch_irq(pin) do
GenServer.start_link(GPIOIRQDevice, pin)
end
defp _init_reset(nil), do: nil
defp _init_reset(rst) when rst >= 0 do
{:ok, gpio} = Circuits.GPIO.open(rst, :output)
gpio
end
defp _init_reset(_), do: nil
defp _get_channel_order(%ILI9486{pix_fmt: :rgb565}), do: kMAD_RGB()
defp _get_channel_order(%ILI9486{pix_fmt: :bgr565}), do: kMAD_BGR()
defp _get_channel_order(%ILI9486{pix_fmt: :rgb666}), do: kMAD_RGB()
defp _get_channel_order(%ILI9486{pix_fmt: :bgr666}), do: kMAD_BGR()
defp _get_pix_fmt(%ILI9486{pix_fmt: :rgb565}), do: k16BIT_PIX()
defp _get_pix_fmt(%ILI9486{pix_fmt: :bgr565}), do: k16BIT_PIX()
defp _get_pix_fmt(%ILI9486{pix_fmt: :rgb666}), do: k18BIT_PIX()
defp _get_pix_fmt(%ILI9486{pix_fmt: :bgr666}), do: k18BIT_PIX()
defp _mad_mode(display = %ILI9486{rotation: 0, mad_mode: :right_down}) do
display
|> _get_channel_order()
|> bor(kMAD_X_RIGHT())
|> bor(kMAD_Y_DOWN())
end
defp _mad_mode(display = %ILI9486{rotation: 90, mad_mode: :right_down}) do
display
|> _get_channel_order()
|> bor(kMAD_X_LEFT())
|> bor(kMAD_Y_DOWN())
|> bor(kMAD_VERTICAL())
end
defp _mad_mode(display = %ILI9486{rotation: 180, mad_mode: :right_down}) do
display
|> _get_channel_order()
|> bor(kMAD_X_LEFT())
|> bor(kMAD_Y_UP())
end
defp _mad_mode(display = %ILI9486{rotation: 270, mad_mode: :right_down}) do
display
|> _get_channel_order()
|> bor(kMAD_X_RIGHT())
|> bor(kMAD_Y_UP())
|> bor(kMAD_VERTICAL())
end
defp _mad_mode(display = %ILI9486{rotation: 0, mad_mode: :right_up}) do
display
|> _get_channel_order()
|> bor(kMAD_X_RIGHT())
|> bor(kMAD_Y_UP())
end
defp _mad_mode(display = %ILI9486{rotation: 90, mad_mode: :right_up}) do
display
|> _get_channel_order()
|> bor(kMAD_X_RIGHT())
|> bor(kMAD_Y_DOWN())
|> bor(kMAD_VERTICAL())
end
defp _mad_mode(display = %ILI9486{rotation: 180, mad_mode: :right_up}) do
display
|> _get_channel_order()
|> bor(kMAD_X_LEFT())
|> bor(kMAD_Y_DOWN())
end
defp _mad_mode(display = %ILI9486{rotation: 270, mad_mode: :right_up}) do
display
|> _get_channel_order()
|> bor(kMAD_X_LEFT())
|> bor(kMAD_Y_UP())
|> bor(kMAD_VERTICAL())
end
defp _mad_mode(display = %ILI9486{rotation: 0, mad_mode: :rgb_mode}) do
display
|> _get_channel_order()
|> bor(kMAD_X_LEFT())
|> bor(kMAD_Y_DOWN())
end
defp _mad_mode(display = %ILI9486{rotation: 90, mad_mode: :rgb_mode}) do
display
|> _get_channel_order()
|> bor(kMAD_X_RIGHT())
|> bor(kMAD_Y_DOWN())
end
defp _mad_mode(display = %ILI9486{rotation: 180, mad_mode: :rgb_mode}) do
display
|> _get_channel_order()
|> bor(kMAD_X_RIGHT())
|> bor(kMAD_Y_UP())
end
defp _mad_mode(display = %ILI9486{rotation: 270, mad_mode: :rgb_mode}) do
display
|> _get_channel_order()
|> bor(kMAD_X_LEFT())
|> bor(kMAD_Y_UP())
end
defp _init(display = %ILI9486{frame_rate: frame_rate}, false) do
display
# software reset
|> _command(kSWRESET(), delay: 120)
# RGB mode off
|> _command(kRGB_INTERFACE(), cmd_data: 0x00)
# turn off sleep mode
|> _command(kSLPOUT(), delay: 200)
# interface format
|> _command(kPIXFMT(), cmd_data: _get_pix_fmt(display))
|> _command(kMADCTL(), cmd_data: _mad_mode(display))
|> _command(kPWCTR3(), cmd_data: 0x44)
|> _command(kVMCTR1())
|> _data(0x00)
|> _data(0x00)
|> _data(0x00)
|> _data(0x00)
|> _command(kGMCTRP1())
|> _data(0x0F)
|> _data(0x1F)
|> _data(0x1C)
|> _data(0x0C)
|> _data(0x0F)
|> _data(0x08)
|> _data(0x48)
|> _data(0x98)
|> _data(0x37)
|> _data(0x0A)
|> _data(0x13)
|> _data(0x04)
|> _data(0x11)
|> _data(0x0D)
|> _data(0x00)
|> _command(kGMCTRN1())
|> _data(0x0F)
|> _data(0x32)
|> _data(0x2E)
|> _data(0x0B)
|> _data(0x0D)
|> _data(0x05)
|> _data(0x47)
|> _data(0x75)
|> _data(0x37)
|> _data(0x06)
|> _data(0x10)
|> _data(0x03)
|> _data(0x24)
|> _data(0x20)
|> _data(0x00)
|> _command(kDGCTR1())
|> _data(0x0F)
|> _data(0x32)
|> _data(0x2E)
|> _data(0x0B)
|> _data(0x0D)
|> _data(0x05)
|> _data(0x47)
|> _data(0x75)
|> _data(0x37)
|> _data(0x06)
|> _data(0x10)
|> _data(0x03)
|> _data(0x24)
|> _data(0x20)
|> _data(0x00)
|> _set_display_mode(:normal)
|> _command(kINVOFF())
|> _command(kSLPOUT(), delay: 200)
|> _command(kDISPON())
|> _set_frame_rate(frame_rate)
end
defp _init(display = %ILI9486{frame_rate: frame_rate}, true) do
display
# software reset
|> _command(kSWRESET(), delay: 120)
# RGB mode off
|> _command(kRGB_INTERFACE(), cmd_data: 0x00)
# turn off sleep mode
|> _command(kSLPOUT(), delay: 250)
# interface format
|> _command(kPIXFMT(), cmd_data: _get_pix_fmt(display))
|> _command(kPWCTR3(), cmd_data: 0x44)
|> _command(kVMCTR1(), cmd_data: [0x00, 0x00, 0x00, 0x00])
|> _command(kGMCTRP1())
|> _data(0x0F)
|> _data(0x1F)
|> _data(0x1C)
|> _data(0x0C)
|> _data(0x0F)
|> _data(0x08)
|> _data(0x48)
|> _data(0x98)
|> _data(0x37)
|> _data(0x0A)
|> _data(0x13)
|> _data(0x04)
|> _data(0x11)
|> _data(0x0D)
|> _data(0x00)
|> _command(kGMCTRN1())
|> _data(0x0F)
|> _data(0x32)
|> _data(0x2E)
|> _data(0x0B)
|> _data(0x0D)
|> _data(0x05)
|> _data(0x47)
|> _data(0x75)
|> _data(0x37)
|> _data(0x06)
|> _data(0x10)
|> _data(0x03)
|> _data(0x24)
|> _data(0x20)
|> _data(0x00)
|> _command(kDGCTR1())
|> _data(0x0F)
|> _data(0x32)
|> _data(0x2E)
|> _data(0x0B)
|> _data(0x0D)
|> _data(0x05)
|> _data(0x47)
|> _data(0x75)
|> _data(0x37)
|> _data(0x06)
|> _data(0x10)
|> _data(0x03)
|> _data(0x24)
|> _data(0x20)
|> _data(0x00)
|> _set_display_mode(:normal)
|> _command(kINVOFF())
|> _command(kDISPON(), delay: 100)
|> _command(kMADCTL(), cmd_data: _mad_mode(display))
|> _set_frame_rate(frame_rate)
end
defp _set_window(display = %ILI9486{opts: board}, opts = [x0: 0, y0: 0, x1: nil, y2: nil]) do
width = board[:width]
height = board[:height]
offset_top = board[:offset_top]
offset_left = board[:offset_left]
x0 = opts[:x0]
x1 = opts[:x1]
x1 = if x1 == nil, do: width - 1
y0 = opts[:y0]
y1 = opts[:y1]
y1 = if y1 == nil, do: height - 1
y0 = y0 + offset_top
y1 = y1 + offset_top
x0 = x0 + offset_left
x1 = x1 + offset_left
display
|> _command(kCASET())
|> _data(bsr(x0, 8))
|> _data(band(x0, 0xFF))
|> _data(bsr(x1, 8))
|> _data(band(x1, 0xFF))
|> _command(kPASET())
|> _data(bsr(y0, 8))
|> _data(band(y0, 0xFF))
|> _data(bsr(y1, 8))
|> _data(band(y1, 0xFF))
|> _command(kRAMWR())
end
defp _to_565(image_data, source_color, target_color)
when is_binary(image_data) do
image_data
|> CvtColor.cvt(source_color, target_color)
|> :binary.bin_to_list()
end
defp _to_666(image_data, :bgr888, :bgr666)
when is_binary(image_data) do
image_data
|> :binary.bin_to_list()
end
defp _to_666(image_data, source_color, target_color)
when is_binary(image_data) do
image_data
|> CvtColor.cvt(source_color, target_color)
|> :binary.bin_to_list()
end
@doc functions: :constants
def kNOP, do: 0x00
@doc functions: :constants
def kSWRESET, do: 0x01
@doc functions: :constants
def kRDDID, do: 0x04
@doc functions: :constants
def kRDDST, do: 0x09
@doc functions: :constants
def kRDMODE, do: 0x0A
@doc functions: :constants
def kRDMADCTL, do: 0x0B
@doc functions: :constants
def kRDPIXFMT, do: 0x0C
@doc functions: :constants
def kRDIMGFMT, do: 0x0D
@doc functions: :constants
def kRDSELFDIAG, do: 0x0F
@doc functions: :constants
def kSLPIN, do: 0x10
@doc functions: :constants
def kSLPOUT, do: 0x11
@doc functions: :constants
def kPTLON, do: 0x12
@doc functions: :constants
def kNORON, do: 0x13
@doc functions: :constants
def kINVOFF, do: 0x20
@doc functions: :constants
def kINVON, do: 0x21
@doc functions: :constants
def kGAMMASET, do: 0x26
@doc functions: :constants
def kDISPOFF, do: 0x28
@doc functions: :constants
def kDISPON, do: 0x29
@doc functions: :constants
def kCASET, do: 0x2A
@doc functions: :constants
def kPASET, do: 0x2B
@doc functions: :constants
def kRAMWR, do: 0x2C
@doc functions: :constants
def kRAMRD, do: 0x2E
@doc functions: :constants
def kPTLAR, do: 0x30
@doc functions: :constants
def kVSCRDEF, do: 0x33
@doc functions: :constants
def kMADCTL, do: 0x36
@doc functions: :constants
# Vertical Scrolling Start Address
def kVSCRSADD, do: 0x37
@doc functions: :constants
def kIDLEOFF, do: 0x38
@doc functions: :constants
def kIDLEON, do: 0x39
@doc functions: :constants
# COLMOD: Pixel Format Set
def kPIXFMT, do: 0x3A
@doc functions: :constants
# RGB Interface Signal Control
def kRGB_INTERFACE, do: 0xB0
@doc functions: :constants
def kFRMCTR1, do: 0xB1
@doc functions: :constants
def kFRMCTR2, do: 0xB2
@doc functions: :constants
def kFRMCTR3, do: 0xB3
@doc functions: :constants
def kINVCTR, do: 0xB4
# Display Function Control
@doc functions: :constants
def kDFUNCTR, do: 0xB6
@doc functions: :constants
def kPWCTR1, do: 0xC0
@doc functions: :constants
def kPWCTR2, do: 0xC1
@doc functions: :constants
def kPWCTR3, do: 0xC2
@doc functions: :constants
def kPWCTR4, do: 0xC3
@doc functions: :constants
def kPWCTR5, do: 0xC4
@doc functions: :constants
def kVMCTR1, do: 0xC5
@doc functions: :constants
def kVMCTR2, do: 0xC7
@doc functions: :constants
def kRDID1, do: 0xDA
@doc functions: :constants
def kRDID2, do: 0xDB
@doc functions: :constants
def kRDID3, do: 0xDC
@doc functions: :constants
def kRDID4, do: 0xDD
@doc functions: :constants
def kGMCTRP1, do: 0xE0
@doc functions: :constants
def kGMCTRN1, do: 0xE1
@doc functions: :constants
def kDGCTR1, do: 0xE2
@doc functions: :constants
def kDGCTR2, do: 0xE3
@doc functions: :constants
def kMAD_RGB, do: 0x08
@doc functions: :constants
def kMAD_BGR, do: 0x00
@doc functions: :constants
def k18BIT_PIX, do: 0x66
@doc functions: :constants
def k16BIT_PIX, do: 0x55
@doc functions: :constants
def kMAD_VERTICAL, do: 0x20
@doc functions: :constants
def kMAD_X_LEFT, do: 0x00
@doc functions: :constants
def kMAD_X_RIGHT, do: 0x40
@doc functions: :constants
def kMAD_Y_UP, do: 0x80
@doc functions: :constants
def kMAD_Y_DOWN, do: 0x00
@doc functions: :constants
def kHISPEEDF1, do: 0xF1
@doc functions: :constants
def kHISPEEDF2, do: 0xF2
@doc functions: :constants
def kHISPEEDF8, do: 0xF8
@doc functions: :constants
def kHISPEEDF9, do: 0xF9
end