Current section
Files
Jump to
Current section
Files
lib/hal/rpiio.ex
defmodule Inky.RpiIO do
@moduledoc """
An `Inky.InkyIO` implementation intended for use with raspberry pis and relies on
Circuits.GPIO and Cirtuits.SPI.
"""
@behaviour Inky.InkyIO
alias Inky.InkyIO
defmodule State do
@moduledoc false
@state_fields [
:gpio_mod,
:spi_mod,
:busy_pid,
:dc_pid,
:reset_pid,
:spi_pid
]
@enforce_keys @state_fields
defstruct @state_fields
end
@busy_pin 17
@cs0_pin 0
@dc_pin 22
@reset_pin 27
@default_pin_mappings %{
busy_pin: @busy_pin,
cs0_pin: @cs0_pin,
dc_pin: @dc_pin,
reset_pin: @reset_pin
}
@spi_speed_hz 488_000
@spi_command 0
@spi_data 1
@spi_chunk_bytes 4096
# API
@impl InkyIO
def init(opts \\ []) do
gpio = opts[:gpio_mod] || Inky.TestGPIO
spi = opts[:spi_mod] || Inky.TestSPI
pin_mappings = opts[:pin_mappings] || @default_pin_mappings
spi_address = "spidev0." <> to_string(pin_mappings[:cs0_pin])
{:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output)
{:ok, reset_pid} = gpio.open(pin_mappings[:reset_pin], :output)
{:ok, busy_pid} = gpio.open(pin_mappings[:busy_pin], :input)
{:ok, spi_pid} = spi.open(spi_address, speed_hz: @spi_speed_hz)
# Use binary pattern matching to pull out the ADC counts (low 10 bits)
# <<_::size(6), counts::size(10)>> = SPI.transfer(spi_pid, <<0x78, 0x00>>)
%State{
gpio_mod: gpio,
spi_mod: spi,
busy_pid: busy_pid,
dc_pid: dc_pid,
reset_pid: reset_pid,
spi_pid: spi_pid
}
end
@impl InkyIO
def handle_sleep(_state, duration_ms) do
:timer.sleep(duration_ms)
end
@impl InkyIO
def handle_read_busy(state), do: gpio_call(state, :read, [state.busy_pid])
@impl InkyIO
def handle_reset(state, value), do: :ok = gpio_call(state, :write, [state.reset_pid, value])
@impl InkyIO
def handle_command(state, command, data) do
write_command(state, command)
write_data(state, data)
end
@impl InkyIO
def handle_command(state, command) do
write_command(state, command)
end
# IO primitives
defp write_command(state, command) do
value = maybe_wrap_integer(command)
spi_write(state, @spi_command, value)
end
require Logger
defp write_data(state, data) do
value = maybe_wrap_integer(data)
spi_write(state, @spi_data, value)
end
defp spi_write(state, data_or_command, values) when is_list(values),
do: spi_write(state, data_or_command, :erlang.list_to_binary(values))
defp spi_write(state, data_or_command, value) when is_binary(value) do
:ok = gpio_call(state, :write, [state.dc_pid, data_or_command])
case spi_call(state, :transfer, [state.spi_pid, value]) do
{:ok, response} -> {:ok, response}
{:error, :transfer_failed} -> spi_call_chunked(state, value)
end
end
defp spi_call_chunked(state, value) do
size = byte_size(value)
parts = div(size - 1, @spi_chunk_bytes)
for x <- 0..parts do
offset = x * @spi_chunk_bytes
# NOTE: grab the smallest of a chunk or the remainder
length = min(@spi_chunk_bytes, size - offset)
{:ok, <<_::binary>>} =
spi_call(state, :transfer, [state.spi_pid, :binary.part(value, offset, length)])
end
end
# internals
defp maybe_wrap_integer(value), do: if(is_integer(value), do: <<value>>, else: value)
defp gpio_call(state, op, args), do: apply(state.gpio_mod, op, args)
defp spi_call(state, op, args), do: apply(state.spi_mod, op, args)
end