Current section
Files
Jump to
Current section
Files
lib/ex_lcd.ex
defmodule ExLCD do
@moduledoc """
**ExLCD** implements a standard API for controlling and
displaying text on character matrix LCD display modules. ExLCD
handles most modes and operations supported by common display modules.
ExLCD controls an assortment of displays by interacting with a
driver module implementing the ExLCD.Driver behaviour.
ExLCD has no direct support for controlling hardware and instead
delegates low-level functions driver modules for the actual device.
## Usage
ExLCD is implemented as a GenServer. Start it by calling
ExLCD.start_link/1 and passing a tuple containing the
name of the driver module in the first element and a map of
configuration parameters in second element. See the driver module
documentation for what configuration parameters it accepts.
Example:
```elixir
alias ExLCD
ExLCD.start_link({ExLCD.HD44780, %{...}})
```
"""
use GenServer
@type feature :: :display | :cursor | :blink | :autoscroll |
:rtl_text | :ltr_text
@type bitmap :: list
defmodule LCDState do
defstruct driver: nil, config: nil, display: nil, callback: nil
end
@doc """
Start the ExLCD GenServer to manage the display.
Pass a tuple containing the name of the driver module in the first element
and a map of configuration parameters in second element. See the driver
module documentation for what configuration parameters it accepts.
Example:
```elixir
alias ExLCD
ExLCD.start_link({ExLCD.HD44780, %{...}})
```
"""
@spec start_link({term, map}) :: {:ok, pid}
def start_link({driver_module, config}) do
state = %LCDState{driver: driver_module, config: config}
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
@doc false
@spec init(term) :: {:ok, term}
def init(state) do
state = %LCDState{state | display: apply(state.driver, :start, [state.config])}
state = %LCDState{state | callback: apply(state.driver, :execute, [])}
{:ok, state}
end
# -------------------------------------------------------------------
# Public API
#
@doc """
Clear the display.
Example:
```elixir
iex> ExLCD.clear
:ok
```
"""
@spec clear() :: :ok
def clear(), do: cast(:clear)
@doc """
Home the cursor position to row 0, col 0
Example:
```elixir
iex> ExLCD.home
:ok
```
"""
@spec home() :: :ok
def home(), do: cast(:home)
@doc """
Position the cursor at a specified row and colum
Example:
```elixir
iex> ExLCD.move_to(2, 12)
:ok
```
"""
@spec move_to(row::integer, col::integer) :: :ok
def move_to(row, col), do: cast({:set_cursor, row, col})
@doc """
Write a string or charlist to the display at the current cursor position.
Example:
```elixir
iex> ExLCD.write("ExLCD!")
:ok
iex> ExLCD.write('ExLCD!')
:ok
iex> ExLCD.write(['E', 'x', 'L', 'C', 'D', '!'])
:ok
```
"""
@spec write(binary | list) :: :ok
def write(content) when is_binary(content) do
cast({:print, content})
end
def write(content), do: cast({:write, content})
@doc """
Scroll the display contents left by 1 or some number of columns.
Example:
```elixir
iex> ExLCD.scroll_right(6)
:ok
```
"""
@spec scroll_left(integer) :: :ok
def scroll_left(cols \\ 1), do: cast({:scroll, -cols})
@doc """
Scroll the display contents right by 1 or some number of columns.
Example:
```elixir
iex> ExLCD.scroll_right(6)
:ok
```
"""
@spec scroll_right(integer) :: :ok
def scroll_right(cols \\ 1), do: cast({:scroll, cols})
@doc """
Move the cursor 1 or some number of columns to the left of its current
position.
Example:
```elixir
iex> ExLCD.move_left(4)
:ok
```
"""
@spec move_left(integer) :: :ok
def move_left(cols \\ 1), do: cast({:left, cols})
@doc """
Move the cursor 1 or some number of columns to the right of its current
position.
Example:
```elixir
iex> ExLCD.move_right(1)
:ok
```
"""
@spec move_right(integer) :: :ok
def move_right(cols \\ 1), do: cast({:right, cols})
@doc """
Program a custom character glyph.
Custom glyphs may not be supported by all displays. Check the driver
to see if it is on yours. Pass a custome character index or slot
number and a list of integers representing the glyph bitmap data. Format
of the bitmap data and the numbering of the character slots is highly
dependent on the display controller. Refer to the driver module for
details.
Example:
```elixir
iex> ExLCD.create_char(0, [0x7F, 0x7F, 0x7F, 0x7F,
...> 0x7F, 0x7F, 0x7F, 0x7F])
:ok
```
"""
@spec create_char(integer, bitmap) :: :ok
def create_char(idx, bits), do: cast({:char, idx, bits})
@doc """
Enable a display feature.
Example:
```elixir
iex> ExLCD.enable(:display)
:ok
```
"""
@spec enable(feature) :: :ok
def enable(:cursor), do: cast({:enable, :cursor})
def enable(:blink), do: cast({:enable, :blink})
def enable(:display), do: cast({:enable, :display})
def enable(:autoscroll), do: cast({:enable, :autoscroll})
def enable(:rtl_text), do: cast({:enable, :rtl_text})
def enable(:ltr_text), do: cast({:enable, :ltr_text})
@doc """
Disable a display feature.
Example:
```elixir
iex> ExLCD.disable(:blink)
:ok
```
"""
@spec disable(feature) :: :ok
def disable(:cursor), do: cast({:disable, :cursor})
def disable(:blink), do: cast({:disable, :blink})
def disable(:display), do: cast({:disable, :display})
def disable(:autoscroll), do: cast({:disable, :autoscroll})
def disable(:rtl_text), do: ExLCD.enable(:ltr_text)
def disable(:ltr_text), do: ExLCD.enable(:rtl_text)
@doc """
Stop the driver and release hardware resources.
Example:
```elixir
iex> ExLCD.stop
:ok
```
"""
@spec stop() :: :ok
def stop(), do: stop(:shutdown)
# -------------------------------------------------------------------
# GenServer Cast callbacks
#
@doc false
def handle_cast(:clear, state), do: execute({:clear, []}, state)
def handle_cast(:home, state), do: execute({:home, []}, state)
def handle_cast({:set_cursor, row, col}, state) do
execute({:set_cursor, {row, col}}, state)
end
def handle_cast({:print, content}, state) do
execute({:print, content}, state)
end
def handle_cast({:write, content}, state) do
execute({:write, content}, state)
end
def handle_cast({:scroll, cols}, state) do
execute({:scroll, cols}, state)
end
def handle_cast({:right, cols}, state) do
execute({:right, cols}, state)
end
def handle_cast({:left, cols}, state) do
execute({:left, cols}, state)
end
def handle_cast({:char, idx, bitmap}, state) when idx in 0..7 and byte_size(bitmap) === 8 do
execute({:char, idx, bitmap}, state)
end
def handle_cast({:enable, feature}, state) do
execute({feature, :on}, state)
end
def handle_cast({:disable, feature}, state) do
execute({feature, :off}, state)
end
defp execute(op, state) do
{_result, display} = state.callback.(state.display, op)
{:noreply, updated_display_state(state, display)}
end
# -------------------------------------------------------------------
# Private Utility Functions
#
defp cast(msg), do: GenServer.cast(__MODULE__, msg)
defp stop(msg), do: GenServer.stop(__MODULE__, msg)
defp updated_display_state(state, display) do
%LCDState{state | display: display}
end
end