From 7d80cce9e1979d31124c59328e6bb355d09ac1ea Mon Sep 17 00:00:00 2001 From: Lars Wikman Date: Sun, 10 Oct 2021 14:44:51 +0200 Subject: [PATCH 01/36] Start work on on Inky Impression --- lib/display/display.ex | 41 +++++- lib/hal/impression/rpihal.ex | 261 +++++++++++++++++++++++++++++++++++ lib/hal/impression/rpiio.ex | 198 ++++++++++++++++++++++++++ lib/inky.ex | 23 +++ 4 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 lib/hal/impression/rpihal.ex create mode 100644 lib/hal/impression/rpiio.ex diff --git a/lib/display/display.ex b/lib/display/display.ex index 2743c5a..7f28e91 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -4,7 +4,7 @@ defmodule Inky.Display do """ alias Inky.LookupTables - + @type t() :: %__MODULE__{} @enforce_keys [:type, :width, :height, :packed_dimensions, :rotation, :accent, :luts] @@ -16,6 +16,18 @@ defmodule Inky.Display do accent: :black, luts: <<>> + @spec spec_for(:impression) :: Inky.Display.t() + def spec_for(type = :impression) do + %__MODULE__{ + type: type, + width: 600, + height: 448, + packed_dimensions: nil, + rotation: 0, + accent: nil, + } + end + @spec spec_for(:phat | :what, :black | :red | :yellow) :: Inky.Display.t() def spec_for(type, accent \\ :black) @@ -83,4 +95,31 @@ defmodule Inky.Display do # Little endian, unsigned short <> end + + # colorsets from pimoroni library + defp get_colorset(:desaturated) do + [ + [0, 0, 0], + [255, 255, 255], + [0, 255, 0], + [0, 0, 255], + [255, 0, 0], + [255, 255, 0], + [255, 140, 0], + [255, 255, 255] + ] + end + + defp get_colorset(:saturated) do + [ + [57, 48, 57], + [255, 255, 255], + [58, 91, 70], + [61, 59, 94], + [156, 72, 75], + [208, 190, 71], + [177, 106, 73], + [255, 255, 255] + ] + end end diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex new file mode 100644 index 0000000..24313c1 --- /dev/null +++ b/lib/hal/impression/rpihal.ex @@ -0,0 +1,261 @@ +defmodule Inky.Impression.RpiHAL do + @default_io_mod Inky.Impression.RpiIO + + @moduledoc """ + An `Inky.HAL` implementation responsible for sending commands to the Inky + screen. It delegates to whatever IO module its user provides at init, but + defaults to #{inspect(@default_io_mod)} + """ + + @behaviour Inky.HAL + + @color_map_black %{black: 0, miss: 1} + @color_map_accent %{red: 1, yellow: 1, accent: 1, miss: 0} + + alias Inky.Display + alias Inky.HAL + alias Inky.PixelUtil + + defmodule State do + @moduledoc false + + @state_fields [:display, :io_mod, :io_state] + + @enforce_keys @state_fields + defstruct @state_fields + end + + # + # API + # + + @impl HAL + def init(args) do + display = args[:display] || raise(ArgumentError, message: ":display missing in args") + io_mod = args[:io_mod] || @default_io_mod + + io_args = args[:io_args] || [] + io_args = if :gpio_mod in io_args, do: io_args, else: [gpio_mod: Circuits.GPIO] ++ io_args + io_args = if :spi_mod in io_args, do: io_args, else: [spi_mod: Circuits.SPI] ++ io_args + + %State{ + display: display, + io_mod: io_mod, + io_state: io_mod.init(io_args) + } + end + + @impl HAL + def handle_update(pixels, border, push_policy, state = %State{}) do + display = %Display{width: w, height: h, rotation: r} = state.display + black_bits = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map_black) + accent_bits = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map_accent) + + reset(state) + soft_reset(state) + + case pre_update(state, push_policy) do + :cont -> do_update(state, display, border, black_bits, accent_bits) + :halt -> {:error, :device_busy} + end + end + + # + # procedures + # + + defp pre_update(state, :await) do + await_device(state) + :cont + end + + defp pre_update(state, :once) do + case read_busy(state) do + 0 -> :cont + 1 -> :halt + end + end + + defp do_update(state, display, border, buf_black, buf_accent) do + d_pd = display.packed_dimensions + + state + |> set_analog_block_control() + |> set_digital_block_control() + |> set_gate(d_pd.height) + |> set_gate_driving_voltage() + |> dummy_line_period() + |> set_gate_line_width() + |> set_data_entry_mode() + |> power_on() + |> vcom_register() + |> set_border_color(border) + |> configure_if_yellow(display.accent) + |> configure_if_red_what(display.accent, display.type) + |> set_luts(display.luts) + |> set_dimensions(d_pd.width, d_pd.height) + |> push_pixel_data_bw(buf_black) + |> push_pixel_data_ry(buf_accent) + |> display_update_sequence() + |> trigger_display_update() + |> sleep(50) + |> await_device() + |> deep_sleep() + + :ok + end + + # + # "routines" and serial commands + # + + defp reset(state) do + state + |> set_reset(0) + |> sleep(100) + |> set_reset(1) + |> sleep(100) + end + + defp soft_reset(state), do: write_command(state, 0x12) + defp set_analog_block_control(state), do: write_command(state, 0x74, 0x54) + defp set_digital_block_control(state), do: write_command(state, 0x7E, 0x3B) + defp set_gate(state, packed_height), do: write_command(state, 0x01, packed_height <> <<0x00>>) + defp set_gate_driving_voltage(state), do: write_command(state, 0x03, [0b10000, 0b0001]) + defp dummy_line_period(state), do: write_command(state, 0x3A, 0x07) + defp set_gate_line_width(state), do: write_command(state, 0x3B, 0x04) + # Data entry mode setting 0x03 = X/Y increment + defp set_data_entry_mode(state), do: write_command(state, 0x11, 0x03) + defp power_on(state), do: write_command(state, 0x04) + + defp vcom_register(state) do + # VCOM Register, 0x3c = -1.5v? + write_command(state, 0x2C, 0x3C) + end + + defp set_border_color(state, border) do + accent = state.display.accent + + border_data = + case border do + # GS Transition Define A + VSS + LUT0 + :black -> + 0b00000000 + + # Fix Level Define A + VSH2 + LUT3 + c when c in [:red, :accent] and accent == :red -> + 0b01110011 + + # GS Transition Define A + VSH2 + LUT3 + c when c in [:yellow, :accent] and accent == :yellow -> + 0b00110011 + + # GS Transition Define A + VSH2 + LUT1 + :white -> + 0b00110001 + + _ -> + raise ArgumentError, + message: "Invalid border #{inspect(border)} provided. Accent was #{inspect(accent)}" + end + + write_command(state, 0x3C, border_data) + end + + # Set voltage of VSH and VSL on Yellow device + defp configure_if_yellow(state, :yellow), do: write_command(state, 0x04, 0x07) + + defp configure_if_yellow(state, _), do: state + + # Set voltage of VSH and VSL on red device + defp configure_if_red_what(state, :red, :what), + do: write_command(state, 0x04, <<0x30, 0xAC, 0x22>>) + + defp configure_if_red_what(state, _, _), do: state + + defp set_luts(state, luts), do: write_command(state, 0x32, luts) + + defp set_dimensions(state, width_data, packed_height) do + height_data = <<0, 0>> <> packed_height + width_data = <<0>> <> width_data + + state + # Set RAM X Start/End + |> write_command(0x44, width_data) + # Set RAM Y Start/End + |> write_command(0x45, height_data) + end + + # 0x24 == RAM B/W + defp push_pixel_data_bw(state, buffer_black), + do: do_push_pixel_data(state, 0x24, buffer_black) + + # 0x26 == RAM Red/Yellow/etc + defp push_pixel_data_ry(state, buffer_accent), + do: do_push_pixel_data(state, 0x26, buffer_accent) + + defp do_push_pixel_data(state, pixel_cmd, pixel_buffer) do + # Set RAM X Pointer start + write_command(state, 0x4E, 0x00) + + # Set RAM Y Pointer start + write_command(state, 0x4F, <<0x00, 0x00>>) + write_command(state, pixel_cmd, pixel_buffer) + end + + defp display_update_sequence(state), do: write_command(state, 0x22, 0xC7) + defp trigger_display_update(state), do: write_command(state, 0x20) + defp deep_sleep(state), do: write_command(state, 0x10, 0x01) + + # + # waiting + # + + defp await_device(state) do + case read_busy(state) do + 1 -> + sleep(state, 10) + await_device(state) + + 0 -> + state + end + end + + # + # pipe-able wrappers + # + + defp sleep(state, sleep_time) do + io_call(state, :handle_sleep, [sleep_time]) + state + end + + defp set_reset(state, value) do + io_call(state, :handle_reset, [value]) + state + end + + defp read_busy(state) do + io_call(state, :handle_read_busy) + end + + defp write_command(state, command) do + io_call(state, :handle_command, [command]) + state + end + + defp write_command(state, command, data) do + io_call(state, :handle_command, [command, data]) + state + end + + # + # Behaviour dispatching + # + + # Dispatch to the IO callback module that's held in state, using the previously obtained state + defp io_call(state, op, args \\ []) do + apply(state.io_mod, op, [state.io_state | args]) + end +end diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex new file mode 100644 index 0000000..96e4a17 --- /dev/null +++ b/lib/hal/impression/rpiio.ex @@ -0,0 +1,198 @@ +defmodule Inky.Impression.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 + + @colors %{ + :black => 0, + :white => 1, + :green => 2, + :blue => 3, + :red => 4, + :yellow => 5, + :orange => 6, + :clean => 7 + } + + @default_palette = [ + [57, 48, 57], + [255, 255, 255], + [58, 91, 70], + [61, 59, 94], + [156, 72, 75], + [208, 190, 71], + [177, 106, 73], + [255, 255, 255] + ] + + @reset_pin 27 + @busy_pin 17 + @dc_pin 22 + @cs0_pin 8 + + @psr 0x00 + @pwr 0x01 + @pof 0x02 + @pfs 0x03 + @pon 0x04 + @btst 0x06 + @dslp 0x07 + @dtm1 0x10 + @dsp 0x11 + @drf 0x12 + @ipc 0x13 + @pll 0x30 + @tsc 0x40 + @tse 0x41 + @tws 0x42 + @tsr 0x43 + @cdi 0x50 + @lpd 0x51 + @tcon 0x60 + @tres 0x61 + @dam 0x65 + @rev 0x70 + @flg 0x71 + @amv 0x80 + @vv 0x81 + @vdcs 0x82 + @pws 0xE3 + @tsset 0xE5 + + @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_size 4096 + + @resolution %{ + {600, 448} => {600, 448, 0, 0, 0} + } + + @border :white + + # 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]) + + # TODO: Resume straight porting here, up to https://github.com/pimoroni/inky/blob/master/library/inky/inky_uc8159.py#L130 + + {: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: <>, 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 diff --git a/lib/inky.ex b/lib/inky.ex index 719f588..0379ef5 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -10,6 +10,7 @@ defmodule Inky do alias Inky.Display alias Inky.RpiHAL + alias Inky.Impression.RpiHAL, as: ImpressionHAL @typedoc "The Inky process name" @type name :: atom | {:global, term} | {:via, module, term} @@ -59,6 +60,11 @@ defmodule Inky do GenServer.start_link(__MODULE__, [type, accent, opts], genserver_opts) end + def start_link(type, opts \\ %{}) do + genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) + GenServer.start_link(__MODULE__, [type, opts], genserver_opts) + end + @doc """ `set_pixels` sets pixels and draws to the display (or not!), with new pixel data or a painter function. @@ -155,6 +161,23 @@ defmodule Inky do }} end + @impl GenServer + def init([:impression = type, opts]) do + border = opts[:border] || @default_border + hal_mod = opts[:hal_mod] || ImpressionHAL + + display = Display.spec_for(type) + hal_state = hal_mod.init(%{display: display}) + + {:ok, + %State{ + border: border, + display: display, + hal_mod: hal_mod, + hal_state: hal_state + }} + end + # GenServer calls @impl GenServer From 8ee2dda94f49719993a0ae2b945e714b2ec07fb2 Mon Sep 17 00:00:00 2001 From: Lars Wikman Date: Wed, 27 Oct 2021 21:53:20 +0200 Subject: [PATCH 02/36] More work on setup and update for Inky Impression --- lib/display/display.ex | 2 +- lib/hal/impression/rpihal.ex | 109 +++++++++++++++++++++++++++++++++-- lib/hal/impression/rpiio.ex | 46 +-------------- 3 files changed, 108 insertions(+), 49 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index 7f28e91..943408a 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -22,7 +22,7 @@ defmodule Inky.Display do type: type, width: 600, height: 448, - packed_dimensions: nil, + packed_resolution: <<2, 88, 1, 192>>, # I used the struct.pack in Python to generate this rotation: 0, accent: nil, } diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 24313c1..9c1d622 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -9,8 +9,45 @@ defmodule Inky.Impression.RpiHAL do @behaviour Inky.HAL - @color_map_black %{black: 0, miss: 1} - @color_map_accent %{red: 1, yellow: 1, accent: 1, miss: 0} + @colors %{ + :black => 0, + :white => 1, + :green => 2, + :blue => 3, + :red => 4, + :yellow => 5, + :orange => 6, + :clean => 7 + } + + @psr 0x00 + @pwr 0x01 + @pof 0x02 + @pfs 0x03 + @pon 0x04 + @btst 0x06 + @dslp 0x07 + @dtm1 0x10 + @dsp 0x11 + @drf 0x12 + @ipc 0x13 + @pll 0x30 + @tsc 0x40 + @tse 0x41 + @tws 0x42 + @tsr 0x43 + @cdi 0x50 + @lpd 0x51 + @tcon 0x60 + @tres 0x61 + @dam 0x65 + @rev 0x70 + @flg 0x71 + @amv 0x80 + @vv 0x81 + @vdcs 0x82 + @pws 0xE3 + @tsset 0xE5 alias Inky.Display alias Inky.HAL @@ -52,7 +89,6 @@ defmodule Inky.Impression.RpiHAL do accent_bits = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map_accent) reset(state) - soft_reset(state) case pre_update(state, push_policy) do :cont -> do_update(state, display, border, black_bits, accent_bits) @@ -77,9 +113,18 @@ defmodule Inky.Impression.RpiHAL do end defp do_update(state, display, border, buf_black, buf_accent) do - d_pd = display.packed_dimensions - state + |> set_resolution(display.packed_resolution) + |> set_panel() + |> set_power() + |> set_pll_clock_frequency() + |> set_tse_register() + |> set_vcom_data_interval_setting(border) + |> set_gate_source_data_interval_setting() + |> disable_external_flash() + |> set_pws_whatever_that_means() + # TODO: Continue with update here from https://github.com/pimoroni/inky/blob/master/library/inky/inky_uc8159.py#L311 + |> set_analog_block_control() |> set_digital_block_control() |> set_gate(d_pd.height) @@ -118,6 +163,60 @@ defmodule Inky.Impression.RpiHAL do end defp soft_reset(state), do: write_command(state, 0x12) + + # >HH struct.pack, so big-endian, unsigned-sort * 2 + defp set_resolution(state, packed_resolution), do: write_command(state, @tres, packed_resolution) + + # Panel Setting + # 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448 + # 0b00100000 = LUT selection, 0 = ext flash, 1 = registers, we use ext flash + # 0b00010000 = Ignore + # 0b00001000 = Gate scan direction, 0 = down, 1 = up (default) + # 0b00000100 = Source shift direction, 0 = left, 1 = right (default) + # 0b00000010 = DC-DC converter, 0 = off, 1 = on + # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) + defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08] + + defp set_power(state), do: write_command(state, @pwr, [ + Bitwise.bor( + Bitwise.bor( + Bitwise.bor( + # ??? - not documented in UC8159 datasheet + Bitwise.<<<(0x06, 3), + # SOURCE_INTERNAL_DC_DC + Bitwise.<<<(0x01, 2) + ), + # GATE_INTERNAL_DC_DC + Bitwise.<<<(0x01, 1) + ), + # LV_SOURCE_INTERNAL_DC_DC + 0x01 + ), + # VGx_20V + 0x00, + # UC8159_7C + 0x23, + # UC8159_7C + 0x23 + ]) + + # Set the PLL clock frequency to 50Hz + # 0b11000000 = Ignore + # 0b00111000 = M + # 0b00000111 = N + # PLL = 2MHz * (M / N) + # PLL = 2MHz * (7 / 4) + # PLL = 2,800,000 ??? + defp set_pll_clock_frequency(state), do: write_command(state, 0x3C) + + defp set_tse_register(state), do: write_command(state, 0x00) + + defp set_vcom_data_interval_setting(state, border), do: write_command(state, @cdi, [Bitwise.bor(Bitwise.<<<(@colors[border], 5), 0x17)]) + defp set_gate_source_non_overlap_period(state), do: write_command(state, @tcon, 0x22) + defp disable_external_flash(state), do: write_command(state, @dam, 0x00) + defp set_pws_whatever_that_means(state), do: write_command(state, @pws, 0xAA) + defp power_off_sequence(state), do: write_command(state, @pfs, 0x00) + defp set_analog_block_control(state), do: write_command(state, 0x74, 0x54) defp set_digital_block_control(state), do: write_command(state, 0x7E, 0x3B) defp set_gate(state, packed_height), do: write_command(state, 0x01, packed_height <> <<0x00>>) diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index 96e4a17..3b9d769 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -24,16 +24,6 @@ defmodule Inky.Impression.RpiIO do defstruct @state_fields end - @colors %{ - :black => 0, - :white => 1, - :green => 2, - :blue => 3, - :red => 4, - :yellow => 5, - :orange => 6, - :clean => 7 - } @default_palette = [ [57, 48, 57], @@ -51,34 +41,6 @@ defmodule Inky.Impression.RpiIO do @dc_pin 22 @cs0_pin 8 - @psr 0x00 - @pwr 0x01 - @pof 0x02 - @pfs 0x03 - @pon 0x04 - @btst 0x06 - @dslp 0x07 - @dtm1 0x10 - @dsp 0x11 - @drf 0x12 - @ipc 0x13 - @pll 0x30 - @tsc 0x40 - @tse 0x41 - @tws 0x42 - @tsr 0x43 - @cdi 0x50 - @lpd 0x51 - @tcon 0x60 - @tres 0x61 - @dam 0x65 - @rev 0x70 - @flg 0x71 - @amv 0x80 - @vv 0x81 - @vdcs 0x82 - @pws 0xE3 - @tsset 0xE5 @default_pin_mappings %{ busy_pin: @busy_pin, @@ -87,7 +49,7 @@ defmodule Inky.Impression.RpiIO do reset_pin: @reset_pin } - @spi_speed_hz 488_000 + @spi_speed_hz 3000000 @spi_command 0 @spi_data 1 @spi_chunk_size 4096 @@ -108,10 +70,8 @@ defmodule Inky.Impression.RpiIO do spi_address = "spidev0." <> to_string(pin_mappings[:cs0_pin]) - # TODO: Resume straight porting here, up to https://github.com/pimoroni/inky/blob/master/library/inky/inky_uc8159.py#L130 - - {:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output) - {:ok, reset_pid} = gpio.open(pin_mappings[:reset_pin], :output) + {:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output, initial_value: 0) + {:ok, reset_pid} = gpio.open(pin_mappings[:reset_pin], :output, initial_value: 1) {:ok, busy_pid} = gpio.open(pin_mappings[:busy_pin], :input) {:ok, spi_pid} = spi.open(spi_address, speed_hz: @spi_speed_hz) From 6e11741a30fae65b31a097a7d9e7530c880434b5 Mon Sep 17 00:00:00 2001 From: Lars Wikman Date: Thu, 28 Oct 2021 00:38:44 +0200 Subject: [PATCH 03/36] Add all of the Impression code but not working, added cs0 but that won't open --- lib/display/display.ex | 3 +- lib/hal/impression/rpihal.ex | 173 +++++++++++------------------------ lib/hal/impression/rpiio.ex | 26 ++++-- lib/inky.ex | 16 +++- 4 files changed, 85 insertions(+), 133 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index 943408a..6921fa2 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -7,11 +7,12 @@ defmodule Inky.Display do @type t() :: %__MODULE__{} - @enforce_keys [:type, :width, :height, :packed_dimensions, :rotation, :accent, :luts] + @enforce_keys [:type, :width, :height, :rotation, :accent] defstruct type: nil, width: 0, height: 0, packed_dimensions: %{}, + packed_resolution: nil, rotation: 0, accent: :black, luts: <<>> diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 9c1d622..fafb15f 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -85,13 +85,19 @@ defmodule Inky.Impression.RpiHAL do @impl HAL def handle_update(pixels, border, push_policy, state = %State{}) do display = %Display{width: w, height: h, rotation: r} = state.display - black_bits = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map_black) - accent_bits = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map_accent) + # All black buffer? + IO.puts("Generating buffer...") + buffer = for x <- 0..w, y <- 0..h, into: <<>> do + color = floor((0/w)*7) + <> + end + IO.puts("Generated buffer: #{byte_size(buffer)}") + IO.puts("Resetting device") reset(state) case pre_update(state, push_policy) do - :cont -> do_update(state, display, border, black_bits, accent_bits) + :cont -> do_update(state, display, border, buffer) :halt -> {:error, :device_busy} end end @@ -107,45 +113,55 @@ defmodule Inky.Impression.RpiHAL do defp pre_update(state, :once) do case read_busy(state) do - 0 -> :cont - 1 -> :halt + 1 -> :cont + 0 -> :halt end end - defp do_update(state, display, border, buf_black, buf_accent) do + defp log(state, msg) do + IO.puts(msg) state + end + + defp do_update(state, display, border, buffer) do + state + |> log("setting resolution") |> set_resolution(display.packed_resolution) + |> log("setting panel") |> set_panel() + |> log("setting power") |> set_power() + |> log("setting pll") |> set_pll_clock_frequency() + |> log("set tse register") |> set_tse_register() + |> log("set vcom") |> set_vcom_data_interval_setting(border) - |> set_gate_source_data_interval_setting() + |> log("set gate") + |> set_gate_source_non_overlap_period() + |> log("disable external flash") |> disable_external_flash() + |> log("set pws") |> set_pws_whatever_that_means() - # TODO: Continue with update here from https://github.com/pimoroni/inky/blob/master/library/inky/inky_uc8159.py#L311 - - |> set_analog_block_control() - |> set_digital_block_control() - |> set_gate(d_pd.height) - |> set_gate_driving_voltage() - |> dummy_line_period() - |> set_gate_line_width() - |> set_data_entry_mode() - |> power_on() - |> vcom_register() - |> set_border_color(border) - |> configure_if_yellow(display.accent) - |> configure_if_red_what(display.accent, display.type) - |> set_luts(display.luts) - |> set_dimensions(d_pd.width, d_pd.height) - |> push_pixel_data_bw(buf_black) - |> push_pixel_data_ry(buf_accent) - |> display_update_sequence() - |> trigger_display_update() - |> sleep(50) + |> log("power off seq") + |> power_off_sequence() + |> log("push pixels") + |> push_pixel_buffer(buffer) + |> log("await") + |> await_device() + |> log("pon") + |> pon() + |> log("await") |> await_device() - |> deep_sleep() + |> log("drf") + |> drf() + |> log("await") + |> await_device() + |> log("pof") + |> pof() + |> log("await") + |> await_device() + |> log("done") :ok end @@ -175,7 +191,7 @@ defmodule Inky.Impression.RpiHAL do # 0b00000100 = Source shift direction, 0 = left, 1 = right (default) # 0b00000010 = DC-DC converter, 0 = off, 1 = on # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) - defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08] + defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08]) defp set_power(state), do: write_command(state, @pwr, [ Bitwise.bor( @@ -216,95 +232,10 @@ defmodule Inky.Impression.RpiHAL do defp disable_external_flash(state), do: write_command(state, @dam, 0x00) defp set_pws_whatever_that_means(state), do: write_command(state, @pws, 0xAA) defp power_off_sequence(state), do: write_command(state, @pfs, 0x00) - - defp set_analog_block_control(state), do: write_command(state, 0x74, 0x54) - defp set_digital_block_control(state), do: write_command(state, 0x7E, 0x3B) - defp set_gate(state, packed_height), do: write_command(state, 0x01, packed_height <> <<0x00>>) - defp set_gate_driving_voltage(state), do: write_command(state, 0x03, [0b10000, 0b0001]) - defp dummy_line_period(state), do: write_command(state, 0x3A, 0x07) - defp set_gate_line_width(state), do: write_command(state, 0x3B, 0x04) - # Data entry mode setting 0x03 = X/Y increment - defp set_data_entry_mode(state), do: write_command(state, 0x11, 0x03) - defp power_on(state), do: write_command(state, 0x04) - - defp vcom_register(state) do - # VCOM Register, 0x3c = -1.5v? - write_command(state, 0x2C, 0x3C) - end - - defp set_border_color(state, border) do - accent = state.display.accent - - border_data = - case border do - # GS Transition Define A + VSS + LUT0 - :black -> - 0b00000000 - - # Fix Level Define A + VSH2 + LUT3 - c when c in [:red, :accent] and accent == :red -> - 0b01110011 - - # GS Transition Define A + VSH2 + LUT3 - c when c in [:yellow, :accent] and accent == :yellow -> - 0b00110011 - - # GS Transition Define A + VSH2 + LUT1 - :white -> - 0b00110001 - - _ -> - raise ArgumentError, - message: "Invalid border #{inspect(border)} provided. Accent was #{inspect(accent)}" - end - - write_command(state, 0x3C, border_data) - end - - # Set voltage of VSH and VSL on Yellow device - defp configure_if_yellow(state, :yellow), do: write_command(state, 0x04, 0x07) - - defp configure_if_yellow(state, _), do: state - - # Set voltage of VSH and VSL on red device - defp configure_if_red_what(state, :red, :what), - do: write_command(state, 0x04, <<0x30, 0xAC, 0x22>>) - - defp configure_if_red_what(state, _, _), do: state - - defp set_luts(state, luts), do: write_command(state, 0x32, luts) - - defp set_dimensions(state, width_data, packed_height) do - height_data = <<0, 0>> <> packed_height - width_data = <<0>> <> width_data - - state - # Set RAM X Start/End - |> write_command(0x44, width_data) - # Set RAM Y Start/End - |> write_command(0x45, height_data) - end - - # 0x24 == RAM B/W - defp push_pixel_data_bw(state, buffer_black), - do: do_push_pixel_data(state, 0x24, buffer_black) - - # 0x26 == RAM Red/Yellow/etc - defp push_pixel_data_ry(state, buffer_accent), - do: do_push_pixel_data(state, 0x26, buffer_accent) - - defp do_push_pixel_data(state, pixel_cmd, pixel_buffer) do - # Set RAM X Pointer start - write_command(state, 0x4E, 0x00) - - # Set RAM Y Pointer start - write_command(state, 0x4F, <<0x00, 0x00>>) - write_command(state, pixel_cmd, pixel_buffer) - end - - defp display_update_sequence(state), do: write_command(state, 0x22, 0xC7) - defp trigger_display_update(state), do: write_command(state, 0x20) - defp deep_sleep(state), do: write_command(state, 0x10, 0x01) + defp push_pixel_buffer(state, buffer), do: write_command(state, @dtm1, buffer) + defp pon(state), do: write_command(state, @pon) + defp drf(state), do: write_command(state, @drf) + defp pof(state), do: write_command(state, @pof) # # waiting @@ -312,11 +243,11 @@ defmodule Inky.Impression.RpiHAL do defp await_device(state) do case read_busy(state) do - 1 -> + 0 -> sleep(state, 10) await_device(state) - 0 -> + 1 -> state end end diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index 3b9d769..368a0ef 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -17,7 +17,8 @@ defmodule Inky.Impression.RpiIO do :busy_pid, :dc_pid, :reset_pid, - :spi_pid + :spi_pid, + :cs_pid ] @enforce_keys @state_fields @@ -25,7 +26,7 @@ defmodule Inky.Impression.RpiIO do end - @default_palette = [ + @default_palette [ [57, 48, 57], [255, 255, 255], [58, 91, 70], @@ -41,10 +42,10 @@ defmodule Inky.Impression.RpiIO do @dc_pin 22 @cs0_pin 8 - @default_pin_mappings %{ busy_pin: @busy_pin, cs0_pin: @cs0_pin, + spi: 0, dc_pin: @dc_pin, reset_pin: @reset_pin } @@ -52,7 +53,7 @@ defmodule Inky.Impression.RpiIO do @spi_speed_hz 3000000 @spi_command 0 @spi_data 1 - @spi_chunk_size 4096 + @spi_chunk_bytes 4096 @resolution %{ {600, 448} => {600, 448, 0, 0, 0} @@ -68,11 +69,18 @@ defmodule Inky.Impression.RpiIO do spi = opts[:spi_mod] || Inky.TestSPI pin_mappings = opts[:pin_mappings] || @default_pin_mappings - spi_address = "spidev0." <> to_string(pin_mappings[:cs0_pin]) + spi_address = "spidev0." <> to_string(pin_mappings[:spi]) + IO.inspect(pin_mappings) + IO.puts("opening CS pin") + {:ok, cs_pid} = gpio.open(pin_mappings[:cs0_pin], :output, initial_value: 1) + IO.puts("opening DC pin") {:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output, initial_value: 0) + IO.puts("opening reset pin") {:ok, reset_pid} = gpio.open(pin_mappings[:reset_pin], :output, initial_value: 1) + IO.puts("opening busy pin") {:ok, busy_pid} = gpio.open(pin_mappings[:busy_pin], :input) + IO.puts("opening SPI device") {: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) @@ -83,8 +91,10 @@ defmodule Inky.Impression.RpiIO do busy_pid: busy_pid, dc_pid: dc_pid, reset_pid: reset_pid, - spi_pid: spi_pid + spi_pid: spi_pid, + cs_pid: cs_pid } + |> IO.inspect(label: "init complete") end @impl InkyIO @@ -127,12 +137,14 @@ defmodule Inky.Impression.RpiIO do 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.cs_pid, 0]) :ok = gpio_call(state, :write, [state.dc_pid, data_or_command]) - case spi_call(state, :transfer, [state.spi_pid, value]) do + result = case spi_call(state, :transfer, [state.spi_pid, value]) do {:ok, response} -> {:ok, response} {:error, :transfer_failed} -> spi_call_chunked(state, value) end + :ok = gpio_call(state, :write, [state.cs_pid, 1]) end defp spi_call_chunked(state, value) do diff --git a/lib/inky.ex b/lib/inky.ex index 0379ef5..1c0efb0 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -34,6 +34,18 @@ defmodule Inky do # # API # + def impress do + {:ok, pid} = start_link(:impression, %{}, name: Inky.Foo) + IO.puts("Started...") + + IO.puts("Seting pixels...") + set_pixels(Inky.Foo, %{}) + end + + def start_link(type, opts) when is_map(opts) do + genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) + GenServer.start_link(__MODULE__, [type, opts], genserver_opts) + end @doc """ Start an Inky GenServer for a display of type `type`, with the color `accent` @@ -60,10 +72,6 @@ defmodule Inky do GenServer.start_link(__MODULE__, [type, accent, opts], genserver_opts) end - def start_link(type, opts \\ %{}) do - genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) - GenServer.start_link(__MODULE__, [type, opts], genserver_opts) - end @doc """ `set_pixels` sets pixels and draws to the display (or not!), with new pixel From c56c9be5c8ee75d61008df675079e16a10508d5c Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sun, 7 Nov 2021 21:09:37 -1000 Subject: [PATCH 04/36] Kinda working! --- lib/display/display.ex | 1 + lib/hal/hal.ex | 2 +- lib/hal/impression/rpihal.ex | 90 +++++++++++++++++++++++------------- lib/hal/impression/rpiio.ex | 21 ++++++--- lib/inky.ex | 38 ++++++++++++--- mix.exs | 3 +- mix.lock | 6 +-- 7 files changed, 111 insertions(+), 50 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index 6921fa2..24a1cb1 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -98,6 +98,7 @@ defmodule Inky.Display do end # colorsets from pimoroni library + # https://github.com/pimoroni/inky/blob/54684464b2f35bfd52208cdfb922c09685644181/library/inky/inky_uc8159.py#L26-L46 defp get_colorset(:desaturated) do [ [0, 0, 0], diff --git a/lib/hal/hal.ex b/lib/hal/hal.ex index 44081ff..9787df6 100644 --- a/lib/hal/hal.ex +++ b/lib/hal/hal.ex @@ -12,5 +12,5 @@ defmodule Inky.HAL do policy :: :await | :once, state :: Inky.IOCommands.State.t() ) :: - :ok | {:error, :device_busy} + io_state() | :ok | {:error, :device_busy} end diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index fafb15f..13ae2f5 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -17,6 +17,7 @@ defmodule Inky.Impression.RpiHAL do :red => 4, :yellow => 5, :orange => 6, + # Not a color, but is used to clear the display :clean => 7 } @@ -49,6 +50,8 @@ defmodule Inky.Impression.RpiHAL do @pws 0xE3 @tsset 0xE5 + require Logger + alias Inky.Display alias Inky.HAL alias Inky.PixelUtil @@ -56,7 +59,7 @@ defmodule Inky.Impression.RpiHAL do defmodule State do @moduledoc false - @state_fields [:display, :io_mod, :io_state] + @state_fields [:display, :io_mod, :io_state, :setup?] @enforce_keys @state_fields defstruct @state_fields @@ -78,7 +81,8 @@ defmodule Inky.Impression.RpiHAL do %State{ display: display, io_mod: io_mod, - io_state: io_mod.init(io_args) + io_state: io_mod.init(io_args), + setup?: false } end @@ -87,13 +91,18 @@ defmodule Inky.Impression.RpiHAL do display = %Display{width: w, height: h, rotation: r} = state.display # All black buffer? IO.puts("Generating buffer...") - buffer = for x <- 0..w, y <- 0..h, into: <<>> do - color = floor((0/w)*7) - <> - end - IO.puts("Generated buffer: #{byte_size(buffer)}") - IO.puts("Resetting device") + buffer = + for _x <- 0..w, _y <- 0..h, into: <<>> do + # color = floor(0 / w * 7) + # This generates a vertical bar type pattern. + <<1>> + end + + log("Generated buffer: #{byte_size(buffer)}") + Logger.info("buffer: #{inspect(buffer)}") + + log("Resetting device") reset(state) case pre_update(state, push_policy) do @@ -118,12 +127,24 @@ defmodule Inky.Impression.RpiHAL do end end - defp log(state, msg) do + defp log(msg) when is_binary(msg) do + IO.puts(msg) + Logger.info(msg) + end + + defp log(state, msg) when is_binary(msg) do IO.puts(msg) + Logger.info(msg) state end + # defp do_update(%State{setup?: true}, _, _, _) do + # Logger.info("Already setup") + # end + defp do_update(state, display, border, buffer) do + IO.inspect(buffer, label: "buffer (rpihal.ex:138)") + state |> log("setting resolution") |> set_resolution(display.packed_resolution) @@ -163,7 +184,7 @@ defmodule Inky.Impression.RpiHAL do |> await_device() |> log("done") - :ok + {:ok, %State{state | setup?: true}} end # @@ -181,7 +202,8 @@ defmodule Inky.Impression.RpiHAL do defp soft_reset(state), do: write_command(state, 0x12) # >HH struct.pack, so big-endian, unsigned-sort * 2 - defp set_resolution(state, packed_resolution), do: write_command(state, @tres, packed_resolution) + defp set_resolution(state, packed_resolution), + do: write_command(state, @tres, packed_resolution) # Panel Setting # 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448 @@ -193,28 +215,30 @@ defmodule Inky.Impression.RpiHAL do # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08]) - defp set_power(state), do: write_command(state, @pwr, [ - Bitwise.bor( - Bitwise.bor( + defp set_power(state), + do: + write_command(state, @pwr, [ Bitwise.bor( - # ??? - not documented in UC8159 datasheet - Bitwise.<<<(0x06, 3), - # SOURCE_INTERNAL_DC_DC - Bitwise.<<<(0x01, 2) + Bitwise.bor( + Bitwise.bor( + # ??? - not documented in UC8159 datasheet + Bitwise.<<<(0x06, 3), + # SOURCE_INTERNAL_DC_DC + Bitwise.<<<(0x01, 2) + ), + # GATE_INTERNAL_DC_DC + Bitwise.<<<(0x01, 1) + ), + # LV_SOURCE_INTERNAL_DC_DC + 0x01 ), - # GATE_INTERNAL_DC_DC - Bitwise.<<<(0x01, 1) - ), - # LV_SOURCE_INTERNAL_DC_DC - 0x01 - ), - # VGx_20V - 0x00, - # UC8159_7C - 0x23, - # UC8159_7C - 0x23 - ]) + # VGx_20V + 0x00, + # UC8159_7C + 0x23, + # UC8159_7C + 0x23 + ]) # Set the PLL clock frequency to 50Hz # 0b11000000 = Ignore @@ -227,7 +251,9 @@ defmodule Inky.Impression.RpiHAL do defp set_tse_register(state), do: write_command(state, 0x00) - defp set_vcom_data_interval_setting(state, border), do: write_command(state, @cdi, [Bitwise.bor(Bitwise.<<<(@colors[border], 5), 0x17)]) + defp set_vcom_data_interval_setting(state, border), + do: write_command(state, @cdi, [Bitwise.bor(Bitwise.<<<(@colors[border], 5), 0x17)]) + defp set_gate_source_non_overlap_period(state), do: write_command(state, @tcon, 0x22) defp disable_external_flash(state), do: write_command(state, @dam, 0x00) defp set_pws_whatever_that_means(state), do: write_command(state, @pws, 0xAA) diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index 368a0ef..acf279f 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -21,7 +21,7 @@ defmodule Inky.Impression.RpiIO do :cs_pid ] - @enforce_keys @state_fields + @enforce_keys @state_fields -- [:cs_pid] defstruct @state_fields end @@ -73,8 +73,9 @@ defmodule Inky.Impression.RpiIO do IO.inspect(pin_mappings) IO.puts("opening CS pin") - {:ok, cs_pid} = gpio.open(pin_mappings[:cs0_pin], :output, initial_value: 1) - IO.puts("opening DC pin") + # TODO: This isn't working! Shouldn't this be done via SPI? + # {:ok, cs_pid} = gpio.open(pin_mappings[:cs0_pin], :output, initial_value: 1) + # IO.puts("opening DC pin") {:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output, initial_value: 0) IO.puts("opening reset pin") {:ok, reset_pid} = gpio.open(pin_mappings[:reset_pin], :output, initial_value: 1) @@ -92,11 +93,19 @@ defmodule Inky.Impression.RpiIO do dc_pid: dc_pid, reset_pid: reset_pid, spi_pid: spi_pid, - cs_pid: cs_pid + # cs_pid: cs_pid } |> IO.inspect(label: "init complete") end + def setup do + # Python library is using BCM numbers for GPIO numbering + # https://raspberrypi.stackexchange.com/a/12967 + # But maybe this is the default for Circuits.GPIO? + + # self._spi_bus.max_speed_hz = 3000000 + end + @impl InkyIO def handle_sleep(_state, duration_ms) do :timer.sleep(duration_ms) @@ -137,14 +146,14 @@ defmodule Inky.Impression.RpiIO do 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.cs_pid, 0]) + # :ok = gpio_call(state, :write, [state.cs_pid, 0]) :ok = gpio_call(state, :write, [state.dc_pid, data_or_command]) result = case spi_call(state, :transfer, [state.spi_pid, value]) do {:ok, response} -> {:ok, response} {:error, :transfer_failed} -> spi_call_chunked(state, value) end - :ok = gpio_call(state, :write, [state.cs_pid, 1]) + # :ok = gpio_call(state, :write, [state.cs_pid, 1]) end defp spi_call_chunked(state, value) do diff --git a/lib/inky.ex b/lib/inky.ex index 1c0efb0..3fb35a6 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -34,16 +34,22 @@ defmodule Inky do # # API # + def go, do: 45 + def impress do - {:ok, pid} = start_link(:impression, %{}, name: Inky.Foo) - IO.puts("Started...") + IO.puts("Starting impression2") + {:ok, pid} = Inky.start_link(:impression, name: Inky.Foo) + IO.puts("Started... #{inspect(pid)}") IO.puts("Seting pixels...") set_pixels(Inky.Foo, %{}) end - def start_link(type, opts) when is_map(opts) do + def start_link(type, opts) when is_list(opts) do + IO.inspect(type, label: "type (inky.ex:46)") + IO.inspect(opts, label: "opts (inky.ex:47)") genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) + IO.inspect(genserver_opts, label: "genserver_opts (inky.ex:49)") GenServer.start_link(__MODULE__, [type, opts], genserver_opts) end @@ -72,7 +78,6 @@ defmodule Inky do GenServer.start_link(__MODULE__, [type, accent, opts], genserver_opts) end - @doc """ `set_pixels` sets pixels and draws to the display (or not!), with new pixel data or a painter function. @@ -153,7 +158,11 @@ defmodule Inky do # @impl GenServer + # TODO: Figure out why we're here instead of down below! What start_link is being called? def init([type, accent, opts]) do + IO.inspect(type, label: "type (inky.ex:162)") + IO.inspect(accent, label: "accent (inky.ex:163)") + IO.inspect(opts, label: "opts (inky.ex:164)") border = opts[:border] || @default_border hal_mod = opts[:hal_mod] || RpiHAL @@ -204,7 +213,16 @@ defmodule Inky do end def handle_call(:push, _from, state) do - {:reply, push(:await, state), state} + result = push(:await, state) + + state = + case result do + :ok -> state + {:error, _} -> state + {:ok, state} -> state + end + + {:reply, result, state} end def handle_call(request, from, state) do @@ -216,7 +234,13 @@ defmodule Inky do @impl GenServer def handle_cast(:push, state) do - push(:await, state) + state = + case push(:await, state) do + :ok -> state + {:error, _} -> state + {:ok, state} -> state + end + {:noreply, state} end @@ -311,7 +335,7 @@ defmodule Inky do # Internals - defp push(push_policy, state) when not (push_policy in [:await, :once]), do: push(:await, state) + defp push(push_policy, state) when push_policy not in [:await, :once], do: push(:await, state) defp push(push_policy, state) do hm = state.hal_mod diff --git a/mix.exs b/mix.exs index 62b8168..9f055d7 100644 --- a/mix.exs +++ b/mix.exs @@ -24,8 +24,9 @@ defmodule Inky.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + # {:circuits_gpio, "~> 1.0"}, {:circuits_gpio, "~> 0.4"}, - {:circuits_spi, "~> 0.1"}, + {:circuits_spi, "~> 1.0"}, {:credo, "~> 1.0.0", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev} ] diff --git a/mix.lock b/mix.lock index b9f884a..b34afb1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,10 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "circuits_gpio": {:hex, :circuits_gpio, "0.4.1", "344dd34f2517687fd28723e5552571babff5469db05b697181cab860fe7eff23", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "8031c53c1813f52ccd2926c5e806d192ec5625cb12c3a2bf6b63fecd34999010"}, - "circuits_spi": {:hex, :circuits_spi, "0.1.3", "a94889abc874e9976f397c649152776d9c0863e5fd3377203c7f0cf992d9609c", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "09569214ccdff871149dc5d738a20206647b60769a919b5bb1a6a7f843a10f66"}, + "circuits_gpio": {:hex, :circuits_gpio, "0.4.8", "75a62b07131f119c0ffa1dd49f79fbe29c46fd39b20ef0acc036d449fb780b89", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "05e341a5de7e9181a0ee6b3281bd2dc278ae4651825e95c9e83c9eabc421448e"}, + "circuits_spi": {:hex, :circuits_spi, "1.0.0", "ecc0db489048799f7a49fcf3fd1ff5fddc3c1d9faaea5bc01dfe3189d66b7fc3", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b119f4f41c8b1ab51ff1bc7f2ffe556ebc14619c32fd39ab64efdf5f25d8a1d8"}, "credo": {:hex, :credo, "1.0.5", "fdea745579f8845315fe6a3b43e2f9f8866839cfbc8562bb72778e9fdaa94214", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "16105fac37c5c4b3f6e1f70ba0784511fec4275cd8bb979386e3c739cf4e6455"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm", "e3be2bc3ae67781db529b80aa7e7c49904a988596e2dbff897425b48b3581161"}, - "elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm", "382eeea8e02dfe6c468f6729b6cf20fe5b14390671d38c7363e59621c7ab4efc"}, + "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "8e24fc8ff9a50b9f557ff020d6c91a03cded7e59ac3e0eec8a27e771430c7d27"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5fbc8e549aa9afeea2847c0769e3970537ed302f93a23ac612602e805d9d1e7f"}, From e415e0e7eaffea1bdb1f1622b7e8b9b7fa3acb47 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sun, 7 Nov 2021 21:45:22 -1000 Subject: [PATCH 05/36] Kind of like a flag Fixes a off by one error --- lib/hal/impression/rpihal.ex | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 13ae2f5..235c262 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -89,14 +89,18 @@ defmodule Inky.Impression.RpiHAL do @impl HAL def handle_update(pixels, border, push_policy, state = %State{}) do display = %Display{width: w, height: h, rotation: r} = state.display + Logger.info("display: #{inspect(display)}") # All black buffer? IO.puts("Generating buffer...") buffer = - for _x <- 0..w, _y <- 0..h, into: <<>> do + for y <- 0..(h - 1), x <- 0..(w - 1), into: <<>> do + cond do + y > 100 -> <<3>> + x > 100 -> <<4>> + true -> <> + end # color = floor(0 / w * 7) - # This generates a vertical bar type pattern. - <<1>> end log("Generated buffer: #{byte_size(buffer)}") From fc37b1f285d9139c924e8e6b7aeec85acab4043c Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Mon, 8 Nov 2021 20:24:59 -1000 Subject: [PATCH 06/36] Cleaning --- lib/hal/impression/rpihal.ex | 18 ++++++++---------- lib/inky.ex | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 235c262..2c9b031 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -90,21 +90,21 @@ defmodule Inky.Impression.RpiHAL do def handle_update(pixels, border, push_policy, state = %State{}) do display = %Display{width: w, height: h, rotation: r} = state.display Logger.info("display: #{inspect(display)}") - # All black buffer? IO.puts("Generating buffer...") buffer = for y <- 0..(h - 1), x <- 0..(w - 1), into: <<>> do cond do - y > 100 -> <<3>> - x > 100 -> <<4>> - true -> <> + x > 150 && y > 200 -> <<@colors[:orange]>> + x > 100 -> <<@colors[:blue]>> + y > 100 -> <<@colors[:green]>> + true -> <> end # color = floor(0 / w * 7) end - log("Generated buffer: #{byte_size(buffer)}") - Logger.info("buffer: #{inspect(buffer)}") + log("Generated buffer of size: #{byte_size(buffer)}") + log("buffer: #{inspect(buffer)}") log("Resetting device") reset(state) @@ -142,12 +142,10 @@ defmodule Inky.Impression.RpiHAL do state end - # defp do_update(%State{setup?: true}, _, _, _) do - # Logger.info("Already setup") - # end - defp do_update(state, display, border, buffer) do IO.inspect(buffer, label: "buffer (rpihal.ex:138)") + Logger.info("border: #{inspect(border)}") + border = :red state |> log("setting resolution") diff --git a/lib/inky.ex b/lib/inky.ex index 3fb35a6..a806c9b 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -42,7 +42,7 @@ defmodule Inky do IO.puts("Started... #{inspect(pid)}") IO.puts("Seting pixels...") - set_pixels(Inky.Foo, %{}) + Inky.set_pixels(Inky.Foo, %{}) end def start_link(type, opts) when is_list(opts) do From cc82eda0667792a01264579fa76d2c61273830e7 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Mon, 8 Nov 2021 20:28:34 -1000 Subject: [PATCH 07/36] Clean up cs pin calls Just put a comment instead of an actual call in case we want to restore the calls later. --- lib/hal/impression/rpihal.ex | 2 +- lib/hal/impression/rpiio.ex | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 2c9b031..0d1ca4d 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -136,7 +136,7 @@ defmodule Inky.Impression.RpiHAL do Logger.info(msg) end - defp log(state, msg) when is_binary(msg) do + defp log(_state, msg) when is_binary(msg) do IO.puts(msg) Logger.info(msg) state diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index acf279f..cc8ee14 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -18,10 +18,11 @@ defmodule Inky.Impression.RpiIO do :dc_pid, :reset_pid, :spi_pid, - :cs_pid + # The python library uses a CS pin but we haven't been able to use pin 8 as a CS pin + # :cs_pid ] - @enforce_keys @state_fields -- [:cs_pid] + @enforce_keys @state_fields defstruct @state_fields end @@ -72,10 +73,8 @@ defmodule Inky.Impression.RpiIO do spi_address = "spidev0." <> to_string(pin_mappings[:spi]) IO.inspect(pin_mappings) - IO.puts("opening CS pin") - # TODO: This isn't working! Shouldn't this be done via SPI? - # {:ok, cs_pid} = gpio.open(pin_mappings[:cs0_pin], :output, initial_value: 1) - # IO.puts("opening DC pin") + # MAYBE_DO: Open CS pin + IO.puts("opening DC pin") {:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output, initial_value: 0) IO.puts("opening reset pin") {:ok, reset_pid} = gpio.open(pin_mappings[:reset_pin], :output, initial_value: 1) @@ -92,8 +91,7 @@ defmodule Inky.Impression.RpiIO do busy_pid: busy_pid, dc_pid: dc_pid, reset_pid: reset_pid, - spi_pid: spi_pid, - # cs_pid: cs_pid + spi_pid: spi_pid } |> IO.inspect(label: "init complete") end @@ -146,14 +144,14 @@ defmodule Inky.Impression.RpiIO do 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.cs_pid, 0]) + # MAYBE_DO: Write a 0 to CS pin :ok = gpio_call(state, :write, [state.dc_pid, data_or_command]) result = case spi_call(state, :transfer, [state.spi_pid, value]) do {:ok, response} -> {:ok, response} {:error, :transfer_failed} -> spi_call_chunked(state, value) end - # :ok = gpio_call(state, :write, [state.cs_pid, 1]) + # MAYBE_DO: Write a 1 to CS pin end defp spi_call_chunked(state, value) do From 99d2cd55b3fc8760bea1f10c0349d50d264d571a Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Mon, 8 Nov 2021 20:29:48 -1000 Subject: [PATCH 08/36] clean --- lib/hal/impression/rpiio.ex | 8 -------- lib/inky.ex | 9 +-------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index cc8ee14..5d96a32 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -96,14 +96,6 @@ defmodule Inky.Impression.RpiIO do |> IO.inspect(label: "init complete") end - def setup do - # Python library is using BCM numbers for GPIO numbering - # https://raspberrypi.stackexchange.com/a/12967 - # But maybe this is the default for Circuits.GPIO? - - # self._spi_bus.max_speed_hz = 3000000 - end - @impl InkyIO def handle_sleep(_state, duration_ms) do :timer.sleep(duration_ms) diff --git a/lib/inky.ex b/lib/inky.ex index a806c9b..6e62ec9 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -37,7 +37,7 @@ defmodule Inky do def go, do: 45 def impress do - IO.puts("Starting impression2") + IO.puts("Starting impression") {:ok, pid} = Inky.start_link(:impression, name: Inky.Foo) IO.puts("Started... #{inspect(pid)}") @@ -46,10 +46,7 @@ defmodule Inky do end def start_link(type, opts) when is_list(opts) do - IO.inspect(type, label: "type (inky.ex:46)") - IO.inspect(opts, label: "opts (inky.ex:47)") genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) - IO.inspect(genserver_opts, label: "genserver_opts (inky.ex:49)") GenServer.start_link(__MODULE__, [type, opts], genserver_opts) end @@ -158,11 +155,7 @@ defmodule Inky do # @impl GenServer - # TODO: Figure out why we're here instead of down below! What start_link is being called? def init([type, accent, opts]) do - IO.inspect(type, label: "type (inky.ex:162)") - IO.inspect(accent, label: "accent (inky.ex:163)") - IO.inspect(opts, label: "opts (inky.ex:164)") border = opts[:border] || @default_border hal_mod = opts[:hal_mod] || RpiHAL From 753d5a8bd0cbf71e4c6ff47a719653d3179714da Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 12:48:22 -0400 Subject: [PATCH 09/36] Fix log typo --- lib/inky.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/inky.ex b/lib/inky.ex index 6e62ec9..1f959f3 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -41,7 +41,7 @@ defmodule Inky do {:ok, pid} = Inky.start_link(:impression, name: Inky.Foo) IO.puts("Started... #{inspect(pid)}") - IO.puts("Seting pixels...") + IO.puts("Setting pixels...") Inky.set_pixels(Inky.Foo, %{}) end From 3f46d9a9dd557aff197b3016e76529556d5d1700 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 13:35:57 -0400 Subject: [PATCH 10/36] Adds comments to byte commands defined for Impression in hal --- lib/hal/impression/rpihal.ex | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 0d1ca4d..16947af 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -21,32 +21,72 @@ defmodule Inky.Impression.RpiHAL do :clean => 7 } + # PANEL SETTING @psr 0x00 + # POWER SETTING @pwr 0x01 + # POWER OFF @pof 0x02 + # POWER OFF SEQUENCE SETTING @pfs 0x03 + # POWER ON @pon 0x04 @btst 0x06 @dslp 0x07 + # DATA START TRANSMISSION 1 @dtm1 0x10 + # TODO: Why are we not using the data stop command? + # DATA STOP @dsp 0x11 + # DISPLAY REFRESH @drf 0x12 @ipc 0x13 + # PLL (Phased Lock Loop) CONTROL + # https://en.wikipedia.org/wiki/Phase-locked_loop @pll 0x30 + # TEMPERATURE SENSOR CALIBRATION + # This command reads the temperature sensed by the temperature sensor. @tsc 0x40 + # TEMPERATURE SENSOR CALIBRATION + # This command selects Internal or External temperature sensor. @tse 0x41 @tws 0x42 @tsr 0x43 + # VCOM AND DATA INTERVAL SETTING + # This command indicates the interval of Vcom and data output. When setting + # the vertical back porch, the total blanking will be kept (20 Hsync). @cdi 0x50 + # LOW POWER DETECTION + # This command indicates the input power condition. Host can read this flag to learn the battery condition. @lpd 0x51 + # TCON SETTING + # This command defines non-overlap period of Gate and Source. @tcon 0x60 + # RESOLUTION SETTING (TRES) + # This command defines alternative resolution and this setting is of higher priority than the RES[1:0] in R00H (PSR). @tres 0x61 + # SPI FLASH CONTROL + # This command defines MCU host direct access external memory mode. + # This might allow us to specify our own lookup tables! Which might mean our own colors! @dam 0x65 + # REVISION + # The REV is read from OTP address = 0x001 @rev 0x70 + # GET STATUS + # This command reads the IC status. + # I2C? @flg 0x71 + # AUTO MEASURE VCOM + # This command reads the IC status. @amv 0x80 + # VCOM VALUE + # This command gets the Vcom value. @vv 0x81 + # VCM_DC SETTING + # This command sets VCOM_DC value. @vdcs 0x82 + # WARN: Not found in datasheet + # python driver calls it "UC8159_7C" @pws 0xE3 @tsset 0xE5 From 314279872d975a80d4f5a85bdaf778be04b86b5b Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 13:38:57 -0400 Subject: [PATCH 11/36] Fix typo in comment --- lib/hal/impression/rpihal.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 16947af..5427dbe 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -243,7 +243,7 @@ defmodule Inky.Impression.RpiHAL do defp soft_reset(state), do: write_command(state, 0x12) - # >HH struct.pack, so big-endian, unsigned-sort * 2 + # >HH struct.pack, so big-endian, unsigned-short * 2 defp set_resolution(state, packed_resolution), do: write_command(state, @tres, packed_resolution) From ae2e48f5b75be21b8dfca7e9becb2c8fcf1c6233 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 14:13:30 -0400 Subject: [PATCH 12/36] Update mix deps for development on jasonmj --- mix.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 9f055d7..fc30239 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Inky.MixProject do version: "1.0.2", elixir: "~> 1.8", start_permanent: Mix.env() == :prod, - source_url: "https://github.com/pappersverk/inky/", + source_url: "https://github.com/jasonmj/inky/", deps: deps(), docs: docs(), package: package() @@ -26,7 +26,7 @@ defmodule Inky.MixProject do [ # {:circuits_gpio, "~> 1.0"}, {:circuits_gpio, "~> 0.4"}, - {:circuits_spi, "~> 1.0"}, + {:circuits_spi, "~> 1.0", override: true}, {:credo, "~> 1.0.0", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev} ] @@ -49,7 +49,7 @@ defmodule Inky.MixProject do description: "A library to drive the Inky series of eInk displays from Pimoroni. Ported from the original Python project, all Elixir.", licenses: ["Apache-2.0"], - links: %{"GitHub" => "https://github.com/pappersverk/inky"} + links: %{"GitHub" => "https://github.com/jasonmj/inky"} ] end end From 07f75e618c99c8c49ef9c2cd069c813d5f0ff392 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:03:33 -0400 Subject: [PATCH 13/36] Update display definition for impression --- lib/display/display.ex | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index 24a1cb1..011bca8 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -19,13 +19,18 @@ defmodule Inky.Display do @spec spec_for(:impression) :: Inky.Display.t() def spec_for(type = :impression) do + width = 600 + height = 448 + %__MODULE__{ type: type, - width: 600, - height: 448, - packed_resolution: <<2, 88, 1, 192>>, # I used the struct.pack in Python to generate this + width: width, + height: height, + packed_resolution: + <> <> <>, + packed_dimensions: packed_dimensions(type, width, height), rotation: 0, - accent: nil, + accent: nil } end From 63cdb4f964e9b9c1a5cb09135afdf0ec259acc11 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:04:02 -0400 Subject: [PATCH 14/36] Add handling for impression in packed_width and packed_height --- lib/display/display.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/display/display.ex b/lib/display/display.ex index 011bca8..e6ec9ae 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -84,6 +84,7 @@ defmodule Inky.Display do case type do :what -> width :phat -> height + :impression -> width :test_small -> height end @@ -94,6 +95,7 @@ defmodule Inky.Display do rows = case type do :what -> height + :impression -> height :phat -> width :test_small -> width end From 13793cd0349e6e06e68189e521e454d0c0415330 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:04:53 -0400 Subject: [PATCH 15/36] Fix usage of state in log function --- lib/hal/impression/rpihal.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 5427dbe..899fa80 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -176,7 +176,7 @@ defmodule Inky.Impression.RpiHAL do Logger.info(msg) end - defp log(_state, msg) when is_binary(msg) do + defp log(state, msg) when is_binary(msg) do IO.puts(msg) Logger.info(msg) state From 853021bc09e44b1cd83ea61f30209f5bce944b98 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:05:53 -0400 Subject: [PATCH 16/36] Add set_dimensions to do_update for impression --- lib/hal/impression/rpihal.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 899fa80..acb186a 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -186,10 +186,13 @@ defmodule Inky.Impression.RpiHAL do IO.inspect(buffer, label: "buffer (rpihal.ex:138)") Logger.info("border: #{inspect(border)}") border = :red + d_pd = display.packed_dimensions state |> log("setting resolution") |> set_resolution(display.packed_resolution) + |> log("setting dimensions") + |> set_dimensions(d_pd.width, d_pd.height) |> log("setting panel") |> set_panel() |> log("setting power") @@ -229,6 +232,17 @@ defmodule Inky.Impression.RpiHAL do {:ok, %State{state | setup?: true}} end + defp set_dimensions(state, width_data, packed_height) do + height_data = <<0, 0>> <> packed_height + width_data = <<0>> <> width_data + + state + # Set RAM X Start/End + |> write_command(0x44, width_data) + # Set RAM Y Start/End + |> write_command(0x45, height_data) + end + # # "routines" and serial commands # From 46a3b40ba3001d34c88765907923e40f7638e48c Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:06:40 -0400 Subject: [PATCH 17/36] Replace buffer generation with PixelUtil.pixels_to_bits --- lib/hal/impression/rpihal.ex | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index acb186a..7b86a4c 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -132,20 +132,7 @@ defmodule Inky.Impression.RpiHAL do Logger.info("display: #{inspect(display)}") IO.puts("Generating buffer...") - buffer = - for y <- 0..(h - 1), x <- 0..(w - 1), into: <<>> do - cond do - x > 150 && y > 200 -> <<@colors[:orange]>> - x > 100 -> <<@colors[:blue]>> - y > 100 -> <<@colors[:green]>> - true -> <> - end - # color = floor(0 / w * 7) - end - - log("Generated buffer of size: #{byte_size(buffer)}") - log("buffer: #{inspect(buffer)}") - + buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map) log("Resetting device") reset(state) From 9dfa18139919d452343be86702268cc465d4b713 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:07:07 -0400 Subject: [PATCH 18/36] Add a color_map for the impression --- lib/hal/impression/rpihal.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 7b86a4c..8260a0c 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -9,6 +9,7 @@ defmodule Inky.Impression.RpiHAL do @behaviour Inky.HAL + @color_map %{black: 0, white: 1, green: 2, blue: 3, red: 4, yellow: 5, orange: 6, miss: 1} @colors %{ :black => 0, :white => 1, @@ -172,7 +173,7 @@ defmodule Inky.Impression.RpiHAL do defp do_update(state, display, border, buffer) do IO.inspect(buffer, label: "buffer (rpihal.ex:138)") Logger.info("border: #{inspect(border)}") - border = :red + border = :black d_pd = display.packed_dimensions state From 5bfb57fe3d874759afc62d632f4eab8aa73c9a2d Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:07:53 -0400 Subject: [PATCH 19/36] Add notes about WIP toward impression support --- lib/hal/impression/rpihal.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 8260a0c..2474e91 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -132,10 +132,11 @@ defmodule Inky.Impression.RpiHAL do display = %Display{width: w, height: h, rotation: r} = state.display Logger.info("display: #{inspect(display)}") IO.puts("Generating buffer...") - buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map) log("Resetting device") reset(state) + # Note: soft_reset doesn't perform any differently than reset + # soft_reset(state) case pre_update(state, push_policy) do :cont -> do_update(state, display, border, buffer) @@ -171,7 +172,6 @@ defmodule Inky.Impression.RpiHAL do end defp do_update(state, display, border, buffer) do - IO.inspect(buffer, label: "buffer (rpihal.ex:138)") Logger.info("border: #{inspect(border)}") border = :black d_pd = display.packed_dimensions @@ -258,6 +258,10 @@ defmodule Inky.Impression.RpiHAL do # 0b00000010 = DC-DC converter, 0 = off, 1 = on # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08]) + # NOTE: Tried several alternatives below + # defp set_panel(state), do: write_command(state, @psr, [Bitwise.<<<(0b11, 6), 0x08]) + # defp set_panel(state), do: write_command(state, @psr, [0b11000000, 0x08]) + # TODO: Figure out how panel workks and whether it needs adjustment. defp set_power(state), do: From 4689834dbededf519df758a6ab55b3ec4568b0d3 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jul 2022 15:19:04 -0400 Subject: [PATCH 20/36] Reverting dev changes to mix.exs --- mix.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index fc30239..9f055d7 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Inky.MixProject do version: "1.0.2", elixir: "~> 1.8", start_permanent: Mix.env() == :prod, - source_url: "https://github.com/jasonmj/inky/", + source_url: "https://github.com/pappersverk/inky/", deps: deps(), docs: docs(), package: package() @@ -26,7 +26,7 @@ defmodule Inky.MixProject do [ # {:circuits_gpio, "~> 1.0"}, {:circuits_gpio, "~> 0.4"}, - {:circuits_spi, "~> 1.0", override: true}, + {:circuits_spi, "~> 1.0"}, {:credo, "~> 1.0.0", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev} ] @@ -49,7 +49,7 @@ defmodule Inky.MixProject do description: "A library to drive the Inky series of eInk displays from Pimoroni. Ported from the original Python project, all Elixir.", licenses: ["Apache-2.0"], - links: %{"GitHub" => "https://github.com/jasonmj/inky"} + links: %{"GitHub" => "https://github.com/pappersverk/inky"} ] end end From 3bcf4f1eca52556dced135da4d14da2a72fd0ffa Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 8 Jul 2022 17:12:51 -0400 Subject: [PATCH 21/36] Cleaning up unused code --- lib/display/display.ex | 3 --- lib/hal/impression/rpihal.ex | 23 ----------------------- 2 files changed, 26 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index e6ec9ae..3b08833 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -28,7 +28,6 @@ defmodule Inky.Display do height: height, packed_resolution: <> <> <>, - packed_dimensions: packed_dimensions(type, width, height), rotation: 0, accent: nil } @@ -84,7 +83,6 @@ defmodule Inky.Display do case type do :what -> width :phat -> height - :impression -> width :test_small -> height end @@ -95,7 +93,6 @@ defmodule Inky.Display do rows = case type do :what -> height - :impression -> height :phat -> width :test_small -> width end diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index 2474e91..eb0b719 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -135,8 +135,6 @@ defmodule Inky.Impression.RpiHAL do buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map) log("Resetting device") reset(state) - # Note: soft_reset doesn't perform any differently than reset - # soft_reset(state) case pre_update(state, push_policy) do :cont -> do_update(state, display, border, buffer) @@ -172,15 +170,9 @@ defmodule Inky.Impression.RpiHAL do end defp do_update(state, display, border, buffer) do - Logger.info("border: #{inspect(border)}") - border = :black - d_pd = display.packed_dimensions - state |> log("setting resolution") |> set_resolution(display.packed_resolution) - |> log("setting dimensions") - |> set_dimensions(d_pd.width, d_pd.height) |> log("setting panel") |> set_panel() |> log("setting power") @@ -220,17 +212,6 @@ defmodule Inky.Impression.RpiHAL do {:ok, %State{state | setup?: true}} end - defp set_dimensions(state, width_data, packed_height) do - height_data = <<0, 0>> <> packed_height - width_data = <<0>> <> width_data - - state - # Set RAM X Start/End - |> write_command(0x44, width_data) - # Set RAM Y Start/End - |> write_command(0x45, height_data) - end - # # "routines" and serial commands # @@ -258,10 +239,6 @@ defmodule Inky.Impression.RpiHAL do # 0b00000010 = DC-DC converter, 0 = off, 1 = on # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08]) - # NOTE: Tried several alternatives below - # defp set_panel(state), do: write_command(state, @psr, [Bitwise.<<<(0b11, 6), 0x08]) - # defp set_panel(state), do: write_command(state, @psr, [0b11000000, 0x08]) - # TODO: Figure out how panel workks and whether it needs adjustment. defp set_power(state), do: From 590c25fdb962fc1b5214e1a57bf03fc4538c13eb Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 8 Jul 2022 17:13:17 -0400 Subject: [PATCH 22/36] Adding bit_size option to PixelUtil.pixels_to_bits --- lib/display/pixelutil.ex | 9 +++++---- lib/hal/impression/rpihal.ex | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/display/pixelutil.ex b/lib/display/pixelutil.ex index 05b1646..8185603 100644 --- a/lib/display/pixelutil.ex +++ b/lib/display/pixelutil.ex @@ -3,7 +3,7 @@ defmodule Inky.PixelUtil do PixelUtil maps pixels to bitstrings to be sent to an Inky screen """ - def pixels_to_bits(pixels, width, height, rotation_degrees, color_map) do + def pixels_to_bits(pixels, width, height, rotation_degrees, color_map, bit_size \\ 1) do {outer_axis, dimension_vectors} = rotation_degrees |> normalised_rotation() @@ -13,7 +13,8 @@ defmodule Inky.PixelUtil do |> rotated_ranges(width, height) |> do_pixels_to_bits( &pixels[pixel_key(outer_axis, &1, &2)], - &(color_map[&1] || color_map.miss) + &(color_map[&1] || color_map.miss), + bit_size ) end @@ -61,10 +62,10 @@ defmodule Inky.PixelUtil do defp rotated_dimension(_width, height, {:y, 1}), do: 0..(height - 1) defp rotated_dimension(_width, height, {:y, -1}), do: (height - 1)..0 - defp do_pixels_to_bits({i_range, j_range}, pixel_picker, cmap) do + defp do_pixels_to_bits({i_range, j_range}, pixel_picker, cmap, bit_size) do for i <- i_range, j <- j_range, - do: <>, + do: <>, into: <<>> end diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index eb0b719..d8d888b 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -132,7 +132,7 @@ defmodule Inky.Impression.RpiHAL do display = %Display{width: w, height: h, rotation: r} = state.display Logger.info("display: #{inspect(display)}") IO.puts("Generating buffer...") - buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map) + buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map, 4) log("Resetting device") reset(state) From 9abc79f72edd1503428202431756f84825d05b6d Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 12 Jul 2022 13:47:08 -0400 Subject: [PATCH 23/36] Cleans up code for PR --- lib/display/display.ex | 34 ++---------- lib/hal/hal.ex | 2 +- lib/hal/impression/rpihal.ex | 102 +++++------------------------------ lib/hal/impression/rpiio.ex | 18 +++---- lib/inky.ex | 41 +++----------- 5 files changed, 33 insertions(+), 164 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index 3b08833..f9afa55 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -7,7 +7,7 @@ defmodule Inky.Display do @type t() :: %__MODULE__{} - @enforce_keys [:type, :width, :height, :rotation, :accent] + @enforce_keys [:type, :width, :height, :packed_dimensions, :rotation, :accent, :luts] defstruct type: nil, width: 0, height: 0, @@ -26,10 +26,12 @@ defmodule Inky.Display do type: type, width: width, height: height, + packed_dimensions: %{}, packed_resolution: <> <> <>, rotation: 0, - accent: nil + accent: nil, + luts: <<>> } end @@ -100,32 +102,4 @@ defmodule Inky.Display do # Little endian, unsigned short <> end - - # colorsets from pimoroni library - # https://github.com/pimoroni/inky/blob/54684464b2f35bfd52208cdfb922c09685644181/library/inky/inky_uc8159.py#L26-L46 - defp get_colorset(:desaturated) do - [ - [0, 0, 0], - [255, 255, 255], - [0, 255, 0], - [0, 0, 255], - [255, 0, 0], - [255, 255, 0], - [255, 140, 0], - [255, 255, 255] - ] - end - - defp get_colorset(:saturated) do - [ - [57, 48, 57], - [255, 255, 255], - [58, 91, 70], - [61, 59, 94], - [156, 72, 75], - [208, 190, 71], - [177, 106, 73], - [255, 255, 255] - ] - end end diff --git a/lib/hal/hal.ex b/lib/hal/hal.ex index 9787df6..44081ff 100644 --- a/lib/hal/hal.ex +++ b/lib/hal/hal.ex @@ -12,5 +12,5 @@ defmodule Inky.HAL do policy :: :await | :once, state :: Inky.IOCommands.State.t() ) :: - io_state() | :ok | {:error, :device_busy} + :ok | {:error, :device_busy} end diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index d8d888b..e4d2419 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -1,4 +1,6 @@ defmodule Inky.Impression.RpiHAL do + use Bitwise + @default_io_mod Inky.Impression.RpiIO @moduledoc """ @@ -19,7 +21,7 @@ defmodule Inky.Impression.RpiHAL do :yellow => 5, :orange => 6, # Not a color, but is used to clear the display - :clean => 7 + :clear => 7 } # PANEL SETTING @@ -32,34 +34,14 @@ defmodule Inky.Impression.RpiHAL do @pfs 0x03 # POWER ON @pon 0x04 - @btst 0x06 - @dslp 0x07 # DATA START TRANSMISSION 1 @dtm1 0x10 - # TODO: Why are we not using the data stop command? - # DATA STOP - @dsp 0x11 # DISPLAY REFRESH @drf 0x12 - @ipc 0x13 - # PLL (Phased Lock Loop) CONTROL - # https://en.wikipedia.org/wiki/Phase-locked_loop - @pll 0x30 - # TEMPERATURE SENSOR CALIBRATION - # This command reads the temperature sensed by the temperature sensor. - @tsc 0x40 - # TEMPERATURE SENSOR CALIBRATION - # This command selects Internal or External temperature sensor. - @tse 0x41 - @tws 0x42 - @tsr 0x43 # VCOM AND DATA INTERVAL SETTING # This command indicates the interval of Vcom and data output. When setting # the vertical back porch, the total blanking will be kept (20 Hsync). @cdi 0x50 - # LOW POWER DETECTION - # This command indicates the input power condition. Host can read this flag to learn the battery condition. - @lpd 0x51 # TCON SETTING # This command defines non-overlap period of Gate and Source. @tcon 0x60 @@ -70,26 +52,9 @@ defmodule Inky.Impression.RpiHAL do # This command defines MCU host direct access external memory mode. # This might allow us to specify our own lookup tables! Which might mean our own colors! @dam 0x65 - # REVISION - # The REV is read from OTP address = 0x001 - @rev 0x70 - # GET STATUS - # This command reads the IC status. - # I2C? - @flg 0x71 - # AUTO MEASURE VCOM - # This command reads the IC status. - @amv 0x80 - # VCOM VALUE - # This command gets the Vcom value. - @vv 0x81 - # VCM_DC SETTING - # This command sets VCOM_DC value. - @vdcs 0x82 # WARN: Not found in datasheet # python driver calls it "UC8159_7C" @pws 0xE3 - @tsset 0xE5 require Logger @@ -130,10 +95,7 @@ defmodule Inky.Impression.RpiHAL do @impl HAL def handle_update(pixels, border, push_policy, state = %State{}) do display = %Display{width: w, height: h, rotation: r} = state.display - Logger.info("display: #{inspect(display)}") - IO.puts("Generating buffer...") buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map, 4) - log("Resetting device") reset(state) case pre_update(state, push_policy) do @@ -158,56 +120,26 @@ defmodule Inky.Impression.RpiHAL do end end - defp log(msg) when is_binary(msg) do - IO.puts(msg) - Logger.info(msg) - end - - defp log(state, msg) when is_binary(msg) do - IO.puts(msg) - Logger.info(msg) - state - end - defp do_update(state, display, border, buffer) do state - |> log("setting resolution") |> set_resolution(display.packed_resolution) - |> log("setting panel") |> set_panel() - |> log("setting power") |> set_power() - |> log("setting pll") |> set_pll_clock_frequency() - |> log("set tse register") |> set_tse_register() - |> log("set vcom") |> set_vcom_data_interval_setting(border) - |> log("set gate") |> set_gate_source_non_overlap_period() - |> log("disable external flash") |> disable_external_flash() - |> log("set pws") |> set_pws_whatever_that_means() - |> log("power off seq") |> power_off_sequence() - |> log("push pixels") |> push_pixel_buffer(buffer) - |> log("await") |> await_device() - |> log("pon") |> pon() - |> log("await") |> await_device() - |> log("drf") |> drf() - |> log("await") |> await_device() - |> log("pof") |> pof() - |> log("await") |> await_device() - |> log("done") {:ok, %State{state | setup?: true}} end @@ -224,8 +156,6 @@ defmodule Inky.Impression.RpiHAL do |> sleep(100) end - defp soft_reset(state), do: write_command(state, 0x12) - # >HH struct.pack, so big-endian, unsigned-short * 2 defp set_resolution(state, packed_resolution), do: write_command(state, @tres, packed_resolution) @@ -243,25 +173,17 @@ defmodule Inky.Impression.RpiHAL do defp set_power(state), do: write_command(state, @pwr, [ - Bitwise.bor( - Bitwise.bor( - Bitwise.bor( - # ??? - not documented in UC8159 datasheet - Bitwise.<<<(0x06, 3), - # SOURCE_INTERNAL_DC_DC - Bitwise.<<<(0x01, 2) - ), - # GATE_INTERNAL_DC_DC - Bitwise.<<<(0x01, 1) - ), - # LV_SOURCE_INTERNAL_DC_DC - 0x01 - ), + # ??? - not documented in UC8159 datasheet + 0x06 <<< 3 + # SOURCE_INTERNAL_DC_DC + |> bor(0x01 <<< 2) + # GATE_INTERNAL_DC_DC + |> bor(0x01 <<< 1) + # LV_SOURCE_INTERNAL_DC_DC + |> bor(0x01), # VGx_20V 0x00, # UC8159_7C - 0x23, - # UC8159_7C 0x23 ]) @@ -277,7 +199,7 @@ defmodule Inky.Impression.RpiHAL do defp set_tse_register(state), do: write_command(state, 0x00) defp set_vcom_data_interval_setting(state, border), - do: write_command(state, @cdi, [Bitwise.bor(Bitwise.<<<(@colors[border], 5), 0x17)]) + do: write_command(state, @cdi, [bor(@colors[border] <<< 5, 0x17)]) defp set_gate_source_non_overlap_period(state), do: write_command(state, @tcon, 0x22) defp disable_external_flash(state), do: write_command(state, @dam, 0x00) diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index 5d96a32..c38ce19 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -17,7 +17,7 @@ defmodule Inky.Impression.RpiIO do :busy_pid, :dc_pid, :reset_pid, - :spi_pid, + :spi_pid # The python library uses a CS pin but we haven't been able to use pin 8 as a CS pin # :cs_pid ] @@ -26,7 +26,6 @@ defmodule Inky.Impression.RpiIO do defstruct @state_fields end - @default_palette [ [57, 48, 57], [255, 255, 255], @@ -51,7 +50,7 @@ defmodule Inky.Impression.RpiIO do reset_pin: @reset_pin } - @spi_speed_hz 3000000 + @spi_speed_hz 3_000_000 @spi_command 0 @spi_data 1 @spi_chunk_bytes 4096 @@ -72,8 +71,6 @@ defmodule Inky.Impression.RpiIO do spi_address = "spidev0." <> to_string(pin_mappings[:spi]) - IO.inspect(pin_mappings) - # MAYBE_DO: Open CS pin IO.puts("opening DC pin") {:ok, dc_pid} = gpio.open(pin_mappings[:dc_pin], :output, initial_value: 0) IO.puts("opening reset pin") @@ -93,7 +90,6 @@ defmodule Inky.Impression.RpiIO do reset_pid: reset_pid, spi_pid: spi_pid } - |> IO.inspect(label: "init complete") end @impl InkyIO @@ -139,10 +135,12 @@ defmodule Inky.Impression.RpiIO do # MAYBE_DO: Write a 0 to CS pin :ok = gpio_call(state, :write, [state.dc_pid, data_or_command]) - result = case spi_call(state, :transfer, [state.spi_pid, value]) do - {:ok, response} -> {:ok, response} - {:error, :transfer_failed} -> spi_call_chunked(state, value) - end + result = + case spi_call(state, :transfer, [state.spi_pid, value]) do + {:ok, response} -> {:ok, response} + {:error, :transfer_failed} -> spi_call_chunked(state, value) + end + # MAYBE_DO: Write a 1 to CS pin end diff --git a/lib/inky.ex b/lib/inky.ex index 1f959f3..16df2e8 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -34,25 +34,10 @@ defmodule Inky do # # API # - def go, do: 45 - - def impress do - IO.puts("Starting impression") - {:ok, pid} = Inky.start_link(:impression, name: Inky.Foo) - IO.puts("Started... #{inspect(pid)}") - - IO.puts("Setting pixels...") - Inky.set_pixels(Inky.Foo, %{}) - end - - def start_link(type, opts) when is_list(opts) do - genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) - GenServer.start_link(__MODULE__, [type, opts], genserver_opts) - end @doc """ Start an Inky GenServer for a display of type `type`, with the color `accent` - using the optionally provided options `opts`. + (not needed for Inky Impression) using the optionally provided options `opts`. The GenServer deals with the HAL state and pushing pixels to the physical display. @@ -70,6 +55,11 @@ defmodule Inky do See `GenServer.start_link/3` for return values. """ + def start_link(type, opts) when is_list(opts) do + genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) + GenServer.start_link(__MODULE__, [type, opts], genserver_opts) + end + def start_link(type, accent, opts \\ %{}) do genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) GenServer.start_link(__MODULE__, [type, accent, opts], genserver_opts) @@ -206,16 +196,7 @@ defmodule Inky do end def handle_call(:push, _from, state) do - result = push(:await, state) - - state = - case result do - :ok -> state - {:error, _} -> state - {:ok, state} -> state - end - - {:reply, result, state} + {:reply, push(:await, state), state} end def handle_call(request, from, state) do @@ -227,13 +208,7 @@ defmodule Inky do @impl GenServer def handle_cast(:push, state) do - state = - case push(:await, state) do - :ok -> state - {:error, _} -> state - {:ok, state} -> state - end - + push(:await, state) {:noreply, state} end From 80bdee86e92d8dc0fcad99049165fc1f1dfea0e0 Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 12 Jul 2022 13:47:18 -0400 Subject: [PATCH 24/36] Adds painter test --- test/inky_test.exs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/inky_test.exs b/test/inky_test.exs index ba8a256..ca4c6a0 100644 --- a/test/inky_test.exs +++ b/test/inky_test.exs @@ -52,7 +52,46 @@ defmodule Inky.InkyTest do assert state.pixels == %{{1, 2} => :white, {0, 0} => :black, {2, 3} => :red} end - # TODO: painter tests + test "assert that painter works", %{inited_state: is} do + TestHAL.on_update(:ok) + + painter = fn x, y, w, h, _pixels_so_far -> + wh = w / 2 + hh = h / 2 + + case {x >= wh, y >= hh} do + {true, true} -> :red + {false, true} -> if(rem(x, 2) == 0, do: :black, else: :white) + {true, false} -> :black + {false, false} -> :white + end + end + + {:reply, :ok, state} = + Inky.handle_call({:set_pixels, painter, %{border: :white}}, :from, is) + + # Flip a few pixels + pixels = %{{0, 0} => :black, {3, 49} => :red, {23, 4} => :white} + {:reply, :ok, state} = Inky.handle_call({:set_pixels, pixels, %{}}, :from, state) + + expected_pixels = %{ + {0, 0} => :black, + {3, 49} => :red, + {23, 4} => :white, + {0, 1} => :white, + {0, 2} => :black, + {0, 3} => :black, + {1, 0} => :white, + {1, 1} => :white, + {1, 2} => :white, + {1, 3} => :white, + {2, 0} => :black, + {2, 1} => :black, + {2, 2} => :red + } + + assert state.pixels == expected_pixels + end end describe "Inky timeout" do From f86d1b843360140e3f665a8396925b9bb0578809 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Tue, 12 Jul 2022 20:12:40 -1000 Subject: [PATCH 25/36] Support circuits_gpio 1.0 There's no code changes, only the version number has been updated: https://github.com/elixir-circuits/circuits_gpio/blob/cc7d77479a4d2ffba1a24962ad067f4680003de4/CHANGELOG.md#v100---10-20-2021 --- mix.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 9f055d7..1e41120 100644 --- a/mix.exs +++ b/mix.exs @@ -24,8 +24,7 @@ defmodule Inky.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:circuits_gpio, "~> 1.0"}, - {:circuits_gpio, "~> 0.4"}, + {:circuits_gpio, "~> 0.4 or ~> 1.0"}, {:circuits_spi, "~> 1.0"}, {:credo, "~> 1.0.0", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev} From a68e31ad99c9266f08094fae1bfe2db18e6a53c5 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 23 Jul 2022 17:47:54 -0400 Subject: [PATCH 26/36] Add support for buttons on Inky Impression --- lib/buttons.ex | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lib/buttons.ex diff --git a/lib/buttons.ex b/lib/buttons.ex new file mode 100644 index 0000000..43c76ed --- /dev/null +++ b/lib/buttons.ex @@ -0,0 +1,136 @@ +defmodule Inky.Buttons do + @moduledoc """ + The Inky Impression has 4 buttons that are monitored independently from the display + and can be started in supervison. + + Supply the `:handler` option as either a pid, or `{module, function, args}` tuple + specifying when to send events to. If no handler is supplied, events are simply logged + + ```elixir + Inky.Buttons.start_link(handler: self()) + ``` + + You can also query the current value of a button at any time + + ```elixir + Inky.Buttons.get_value(:a) + ``` + """ + + use GenServer + + alias Circuits.GPIO + + require Logger + + @typedoc """ + Button name for Inky Impression button + + These are labelled A, B, X, and Y on the board. + """ + @type name() :: :a | :b | :x | :y + + defmodule Event do + defstruct [:action, :name, :value, :timestamp] + + @type t :: %Event{ + action: :pressed | :released, + name: Buttons.name(), + value: 1 | 0, + timestamp: non_neg_integer() + } + end + + @pin_a 5 + @pin_b 6 + @pin_x 16 + @pin_y 24 + + @doc """ + Start a GenServer to watch the buttons on the Inky Impression + + Options: + + * `:handler` - pass a pid or an MFA to receive button events + """ + @spec start_link(keyword) :: GenServer.on_start() + def start_link(opts \\ []) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + @doc """ + Return the current state of the button + + `0` - released + `1` - pressed + """ + @spec get_value(name()) :: 0 | 1 + def get_value(button) do + GenServer.call(__MODULE__, {:get_value, button}) + end + + @impl GenServer + def init(opts) do + {:ok, %{button_to_ref: %{}, pin_to_button: %{}, handler: opts[:handler]}, {:continue, :init}} + end + + @impl GenServer + def handle_continue(:init, state) do + {:ok, a} = GPIO.open(@pin_a, :input, pull_mode: :pullup) + {:ok, b} = GPIO.open(@pin_b, :input, pull_mode: :pullup) + {:ok, x} = GPIO.open(@pin_x, :input, pull_mode: :pullup) + {:ok, y} = GPIO.open(@pin_y, :input, pull_mode: :pullup) + :ok = GPIO.set_interrupts(a, :both) + :ok = GPIO.set_interrupts(b, :both) + :ok = GPIO.set_interrupts(x, :both) + :ok = GPIO.set_interrupts(y, :both) + + button_to_ref = %{a: a, b: b, x: x, y: y} + + pin_to_button = %{ + @pin_a => :a, + @pin_b => :b, + @pin_x => :x, + @pin_y => :y + } + + {:noreply, %{state | button_to_ref: button_to_ref, pin_to_button: pin_to_button}} + end + + @impl GenServer + def handle_call({:get_value, name}, _from, state) do + inverted_value = GPIO.read(state.button_to_ref[name]) + value = 1 - inverted_value + + {:reply, value, state} + end + + @impl GenServer + def handle_info({:circuits_gpio, pin, timestamp, inverted_value}, state) do + value = 1 - inverted_value + action = if value != 0, do: :pressed, else: :released + + event = %Event{ + action: action, + name: state.pin_to_button[pin], + value: value, + timestamp: timestamp + } + + _ = send_event(state.handler, event) + + {:noreply, state} + end + + def handle_info(_other, state), do: {:noreply, state} + + defp send_event(handler, event) when is_pid(handler), do: send(handler, event) + + defp send_event({m, f, a}, event) when is_atom(m) and is_atom(f) and is_list(a) do + apply(m, f, [event | a]) + end + + defp send_event(_, event) do + Logger.info("[Inky] unhandled button event - #{inspect(event)}") + end +end From 40cda8453026ba9822f60912ec9172f477c526f3 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 1 Aug 2022 10:36:06 -0400 Subject: [PATCH 27/36] Support sending button messages to a process by registered atom name --- lib/buttons.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/buttons.ex b/lib/buttons.ex index 43c76ed..48608ae 100644 --- a/lib/buttons.ex +++ b/lib/buttons.ex @@ -3,8 +3,8 @@ defmodule Inky.Buttons do The Inky Impression has 4 buttons that are monitored independently from the display and can be started in supervison. - Supply the `:handler` option as either a pid, or `{module, function, args}` tuple - specifying when to send events to. If no handler is supplied, events are simply logged + Supply the `:handler` option as an atom, a pid, or `{module, function, args}` tuple + specifying where to send events to. If no handler is supplied, events are simply logged ```elixir Inky.Buttons.start_link(handler: self()) @@ -51,7 +51,7 @@ defmodule Inky.Buttons do Options: - * `:handler` - pass a pid or an MFA to receive button events + * `:handler` - pass an atom, a pid, or an MFA to receive button events """ @spec start_link(keyword) :: GenServer.on_start() def start_link(opts \\ []) do @@ -124,6 +124,8 @@ defmodule Inky.Buttons do def handle_info(_other, state), do: {:noreply, state} + defp send_event(handler, event) when is_atom(handler), do: send(handler, event) + defp send_event(handler, event) when is_pid(handler), do: send(handler, event) defp send_event({m, f, a}, event) when is_atom(m) and is_atom(f) and is_list(a) do From 35eb6cdcd8081906473299077fa81838397ec7e8 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sun, 30 Jul 2023 09:38:49 -1000 Subject: [PATCH 28/36] WIP --- lib/display/display.ex | 18 +++++ lib/hal/impression/rpihal.ex | 141 +++++++++++++++++++++++------------ lib/hal/impression/rpiio.ex | 4 - lib/inky.ex | 16 ++++ 4 files changed, 128 insertions(+), 51 deletions(-) diff --git a/lib/display/display.ex b/lib/display/display.ex index f9afa55..cd3211b 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -17,6 +17,24 @@ defmodule Inky.Display do accent: :black, luts: <<>> + @spec spec_for(:impression_7_3) :: Inky.Display.t() + def spec_for(type = :impression_7_3) do + width = 800 + height = 480 + + %__MODULE__{ + type: type, + width: width, + height: height, + packed_dimensions: %{}, + packed_resolution: + <> <> <>, + rotation: 0, + accent: nil, + luts: <<>> + } + end + @spec spec_for(:impression) :: Inky.Display.t() def spec_for(type = :impression) do width = 600 diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/impression/rpihal.ex index e4d2419..55d8755 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/impression/rpihal.ex @@ -31,13 +31,27 @@ defmodule Inky.Impression.RpiHAL do # POWER OFF @pof 0x02 # POWER OFF SEQUENCE SETTING - @pfs 0x03 + @pofs 0x03 # POWER ON @pon 0x04 + + # BTST1 + @btst1 0x05 + @btst2 0x06 + # @dslp 0x07 + @btst3 0x08 + # DATA START TRANSMISSION 1 - @dtm1 0x10 + @dtm 0x10 # DISPLAY REFRESH @drf 0x12 + + #? + @ipc 0x13 + + #? + @tse 0x41 + # VCOM AND DATA INTERVAL SETTING # This command indicates the interval of Vcom and data output. When setting # the vertical back porch, the total blanking will be kept (20 Hsync). @@ -52,9 +66,17 @@ defmodule Inky.Impression.RpiHAL do # This command defines MCU host direct access external memory mode. # This might allow us to specify our own lookup tables! Which might mean our own colors! @dam 0x65 - # WARN: Not found in datasheet - # python driver calls it "UC8159_7C" + + # New in impression_7_3 + @vdcs 0x82 + @t_vdcs 0x84 + @agid 0x86 + @cmdh 0xAA + # @ccset 0xE0 + + @ccset 0xE0 @pws 0xE3 + @tsset 0xE6 require Logger @@ -94,6 +116,7 @@ defmodule Inky.Impression.RpiHAL do @impl HAL def handle_update(pixels, border, push_policy, state = %State{}) do + Logger.info("push_policy: #{inspect(push_policy, pretty: true)}") display = %Display{width: w, height: h, rotation: r} = state.display buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map, 4) reset(state) @@ -120,20 +143,38 @@ defmodule Inky.Impression.RpiHAL do end end - defp do_update(state, display, border, buffer) do + defp do_update(state, _display, _border, buffer) do state - |> set_resolution(display.packed_resolution) - |> set_panel() - |> set_power() + |> set_cmdh() + |> set_power_pwr() + |> set_panel_psr() + |> power_off_sequence_pofs() + |> write_command(@btst1, [0x40, 0x1F, 0x1F, 0x2C]) + |> write_command(@btst2, [0x6F, 0x1F, 0x16, 0x25]) + |> write_command(@btst3, [0x6F, 0x1F, 0x1F, 0x22]) + |> write_command(@ipc, [0x00, 0x04]) |> set_pll_clock_frequency() - |> set_tse_register() - |> set_vcom_data_interval_setting(border) - |> set_gate_source_non_overlap_period() - |> disable_external_flash() - |> set_pws_whatever_that_means() - |> power_off_sequence() + |> write_command(@tse, [0x00]) + # border? + # TODO: Combine with `set_vcom_data_interval_setting` + |> write_command(@cdi, [0x3F]) + |> write_command(@tcon, [0x02, 0x00]) + # resolution? + |> write_command(@tres, [0x03, 0x20, 0x01, 0xE0]) + # |> set_resolution(display.packed_resolution) + # cont + |> write_command(@vdcs, [0x1E]) + |> write_command(@t_vdcs, [0x00]) + |> write_command(@agid, [0x00]) + |> write_command(@pws, [0x2F]) + |> write_command(@ccset, [0x00]) + |> write_command(@tsset, [0x00]) + + # End of setup + # TODO: Need to force white somehow? + # The python driver is doing some bit manipulation before this call + # https://github.com/pimoroni/inky/blob/98383c5d47928b90ee3951ed72576b7064e573e7/library/inky/inky_ac073tc1a.py#L300 |> push_pixel_buffer(buffer) - |> await_device() |> pon() |> await_device() |> drf() @@ -141,6 +182,21 @@ defmodule Inky.Impression.RpiHAL do |> pof() |> await_device() + + # |> set_vcom_data_interval_setting(border) + # |> set_gate_source_non_overlap_period() + # |> disable_external_flash() + # |> set_pws_whatever_that_means() + # |> power_off_sequence_pofs() + # |> push_pixel_buffer(buffer) + # |> await_device() + # |> pon() + # |> await_device() + # |> drf() + # |> await_device() + # |> pof() + # |> await_device() + {:ok, %State{state | setup?: true}} end @@ -154,11 +210,16 @@ defmodule Inky.Impression.RpiHAL do |> sleep(100) |> set_reset(1) |> sleep(100) + |> set_reset(0) + |> sleep(100) + |> set_reset(1) + |> sleep(100) + # busy wait? end # >HH struct.pack, so big-endian, unsigned-short * 2 - defp set_resolution(state, packed_resolution), - do: write_command(state, @tres, packed_resolution) + # defp set_resolution(state, packed_resolution), + # do: write_command(state, @tres, packed_resolution) # Panel Setting # 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448 @@ -168,24 +229,11 @@ defmodule Inky.Impression.RpiHAL do # 0b00000100 = Source shift direction, 0 = left, 1 = right (default) # 0b00000010 = DC-DC converter, 0 = off, 1 = on # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) - defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08]) - - defp set_power(state), - do: - write_command(state, @pwr, [ - # ??? - not documented in UC8159 datasheet - 0x06 <<< 3 - # SOURCE_INTERNAL_DC_DC - |> bor(0x01 <<< 2) - # GATE_INTERNAL_DC_DC - |> bor(0x01 <<< 1) - # LV_SOURCE_INTERNAL_DC_DC - |> bor(0x01), - # VGx_20V - 0x00, - # UC8159_7C - 0x23 - ]) + defp set_panel_psr(state), do: write_command(state, @psr, [0x5F, 0x69]) + + defp set_cmdh(state), do: write_command(state, @cmdh, [0x49, 0x55, 0x20, 0x08, 0x09, 0x18]) + + defp set_power_pwr(state), do: write_command(state, @pwr, [0x3F, 0x00, 0x32, 0x2A, 0x0E, 0x2A]) # Set the PLL clock frequency to 50Hz # 0b11000000 = Ignore @@ -194,26 +242,25 @@ defmodule Inky.Impression.RpiHAL do # PLL = 2MHz * (M / N) # PLL = 2MHz * (7 / 4) # PLL = 2,800,000 ??? - defp set_pll_clock_frequency(state), do: write_command(state, 0x3C) - - defp set_tse_register(state), do: write_command(state, 0x00) + defp set_pll_clock_frequency(state), do: write_command(state, [0x02]) - defp set_vcom_data_interval_setting(state, border), - do: write_command(state, @cdi, [bor(@colors[border] <<< 5, 0x17)]) + # defp set_vcom_data_interval_setting(state, border), + # do: write_command(state, @cdi, [bor(@colors[border] <<< 5, 0x17)]) - defp set_gate_source_non_overlap_period(state), do: write_command(state, @tcon, 0x22) - defp disable_external_flash(state), do: write_command(state, @dam, 0x00) - defp set_pws_whatever_that_means(state), do: write_command(state, @pws, 0xAA) - defp power_off_sequence(state), do: write_command(state, @pfs, 0x00) - defp push_pixel_buffer(state, buffer), do: write_command(state, @dtm1, buffer) + # defp set_gate_source_non_overlap_period(state), do: write_command(state, @tcon, 0x22) + # defp disable_external_flash(state), do: write_command(state, @dam, 0x00) + # defp set_pws_whatever_that_means(state), do: write_command(state, @pws, 0xAA) + defp power_off_sequence_pofs(state), do: write_command(state, @pofs, [0x00, 0x54, 0x00, 0x44]) + defp push_pixel_buffer(state, buffer), do: write_command(state, @dtm, buffer) defp pon(state), do: write_command(state, @pon) - defp drf(state), do: write_command(state, @drf) - defp pof(state), do: write_command(state, @pof) + defp drf(state), do: write_command(state, @drf, [0x00]) + defp pof(state), do: write_command(state, @pof, [0x00]) # # waiting # + # busy_wait defp await_device(state) do case read_busy(state) do 0 -> diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/impression/rpiio.ex index c38ce19..2e8c108 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/impression/rpiio.ex @@ -55,10 +55,6 @@ defmodule Inky.Impression.RpiIO do @spi_data 1 @spi_chunk_bytes 4096 - @resolution %{ - {600, 448} => {600, 448, 0, 0, 0} - } - @border :white # API diff --git a/lib/inky.ex b/lib/inky.ex index 16df2e8..b7a49cc 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -178,6 +178,22 @@ defmodule Inky do }} end + def init([:impression_7_3 = type, opts]) do + border = opts[:border] || @default_border + hal_mod = opts[:hal_mod] || ImpressionHAL + + display = Display.spec_for(type) + hal_state = hal_mod.init(%{display: display}) + + {:ok, + %State{ + border: border, + display: display, + hal_mod: hal_mod, + hal_state: hal_state + }} + end + # GenServer calls @impl GenServer From 2fa18a3283d52b86c5dfb7014b7e3559424280db Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sun, 6 Aug 2023 14:45:33 -1000 Subject: [PATCH 29/36] Refactor! Rename the phat to phat_original Segregate HAL for phat and impression based on chip type Compile test/support when MIX_TARGET=test --- README.md | 2 +- config/config.exs | 2 +- lib/display/display.ex | 36 ++- .../rpihal.ex => hal_impression_ac073tc1a.ex} | 30 +- lib/hal/hal_impression_uc8159.ex | 264 ++++++++++++++++++ lib/hal/{rpihal.ex => hal_phat_original.ex} | 4 +- .../{hal_ssd1608.ex => hal_phat_ssd1608.ex} | 4 +- .../{impression/rpiio.ex => io_impression.ex} | 24 +- lib/hal/{rpiio.ex => io_phat.ex} | 2 +- lib/inky.ex | 102 ++++--- mix.exs | 7 +- ...al_test.exs => hal_phat_original_test.exs} | 65 +++-- test/inky_test.exs | 4 +- test/inky_timer_test.exs | 7 +- test/{rpiio_test.exs => io_phat_test.exs} | 6 +- test/support/{testhal.exs => testhal.ex} | 0 16 files changed, 418 insertions(+), 141 deletions(-) rename lib/hal/{impression/rpihal.ex => hal_impression_ac073tc1a.ex} (96%) create mode 100644 lib/hal/hal_impression_uc8159.ex rename lib/hal/{rpihal.ex => hal_phat_original.ex} (99%) rename lib/hal/{hal_ssd1608.ex => hal_phat_ssd1608.ex} (98%) rename lib/hal/{impression/rpiio.ex => io_impression.ex} (89%) rename lib/hal/{rpiio.ex => io_phat.ex} (99%) rename test/{rpihal_test.exs => hal_phat_original_test.exs} (67%) rename test/{rpiio_test.exs => io_phat_test.exs} (82%) rename test/support/{testhal.exs => testhal.ex} (100%) diff --git a/README.md b/README.md index 98528ef..12f7933 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ config in init, adjust accordingly): ```elixir # Start your Inky process ... -{:ok, pid} = Inky.start_link(:phat, :red, %{name: InkySample}) +{:ok, pid} = Inky.start_link(:phat_original, accent: :red, name: InkySample) painter = fn x, y, w, h, _pixels_so_far -> wh = w / 2 diff --git a/config/config.exs b/config/config.exs index 6ba0b54..66c2fc2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -3,4 +3,4 @@ # # This configuration file is loaded before any dependency and # is restricted to this project. -use Mix.Config +import Config diff --git a/lib/display/display.ex b/lib/display/display.ex index dc6a3bb..bd05632 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -17,8 +17,8 @@ defmodule Inky.Display do accent: :black, luts: <<>> - @spec spec_for(:impression_7_3) :: Inky.Display.t() - def spec_for(type = :impression_7_3) do + @spec spec_for(:impression_7_3, :none) :: Inky.Display.t() + def spec_for(type = :impression_7_3, :none) do width = 800 height = 480 @@ -35,8 +35,8 @@ defmodule Inky.Display do } end - @spec spec_for(:impression) :: Inky.Display.t() - def spec_for(type = :impression) do + @spec spec_for(:impression_5_7, :none) :: Inky.Display.t() + def spec_for(type = :impression_5_7, :none) do width = 600 height = 448 @@ -53,9 +53,27 @@ defmodule Inky.Display do } end - @spec spec_for(:phat_ssd1608 | :phat | :what, :black | :red | :yellow) :: Inky.Display.t() - def spec_for(type, accent \\ :black) + # WARNING: This is untested on actual hardware + @spec spec_for(:impression_4, :none) :: Inky.Display.t() + def spec_for(type = :impression_4, :none) do + width = 640 + height = 400 + %__MODULE__{ + type: type, + width: width, + height: height, + packed_dimensions: %{}, + packed_resolution: + <> <> <>, + rotation: 0, + accent: nil, + luts: <<>> + } + end + + @spec spec_for(:phat_original | :phat_ssd1608 | :what, :black | :red | :yellow) :: + Inky.Display.t() def spec_for(type = :phat_ssd1608, accent) do # Keep it minimal. Details are specified in `Inky.HAL.PhatSSD1608`. %__MODULE__{ @@ -69,7 +87,7 @@ defmodule Inky.Display do } end - def spec_for(type = :phat, accent) do + def spec_for(type = :phat_original, accent) do %__MODULE__{ type: type, width: 212, @@ -115,7 +133,7 @@ defmodule Inky.Display do columns = case type do :what -> width - :phat -> height + :phat_original -> height :test_small -> height end @@ -126,7 +144,7 @@ defmodule Inky.Display do rows = case type do :what -> height - :phat -> width + :phat_original -> width :test_small -> width end diff --git a/lib/hal/impression/rpihal.ex b/lib/hal/hal_impression_ac073tc1a.ex similarity index 96% rename from lib/hal/impression/rpihal.ex rename to lib/hal/hal_impression_ac073tc1a.ex index 55d8755..e085ea7 100644 --- a/lib/hal/impression/rpihal.ex +++ b/lib/hal/hal_impression_ac073tc1a.ex @@ -1,7 +1,5 @@ -defmodule Inky.Impression.RpiHAL do - use Bitwise - - @default_io_mod Inky.Impression.RpiIO +defmodule Inky.HAL.ImpressionAC073TC1A do + @default_io_mod Inky.IO.Impression @moduledoc """ An `Inky.HAL` implementation responsible for sending commands to the Inky @@ -12,17 +10,17 @@ defmodule Inky.Impression.RpiHAL do @behaviour Inky.HAL @color_map %{black: 0, white: 1, green: 2, blue: 3, red: 4, yellow: 5, orange: 6, miss: 1} - @colors %{ - :black => 0, - :white => 1, - :green => 2, - :blue => 3, - :red => 4, - :yellow => 5, - :orange => 6, - # Not a color, but is used to clear the display - :clear => 7 - } + # @colors %{ + # :black => 0, + # :white => 1, + # :green => 2, + # :blue => 3, + # :red => 4, + # :yellow => 5, + # :orange => 6, + # # Not a color, but is used to clear the display + # :clear => 7 + # } # PANEL SETTING @psr 0x00 @@ -65,7 +63,7 @@ defmodule Inky.Impression.RpiHAL do # SPI FLASH CONTROL # This command defines MCU host direct access external memory mode. # This might allow us to specify our own lookup tables! Which might mean our own colors! - @dam 0x65 + # @dam 0x65 # New in impression_7_3 @vdcs 0x82 diff --git a/lib/hal/hal_impression_uc8159.ex b/lib/hal/hal_impression_uc8159.ex new file mode 100644 index 0000000..4410f89 --- /dev/null +++ b/lib/hal/hal_impression_uc8159.ex @@ -0,0 +1,264 @@ +defmodule Inky.HAL.ImpressionUC8159 do + import Bitwise + + @default_io_mod Inky.IO.Impression + + @moduledoc """ + An `Inky.HAL` implementation responsible for sending commands to the Inky + screen. It delegates to whatever IO module its user provides at init, but + defaults to #{inspect(@default_io_mod)} + """ + + @behaviour Inky.HAL + + @color_map %{black: 0, white: 1, green: 2, blue: 3, red: 4, yellow: 5, orange: 6, miss: 1} + @colors %{ + :black => 0, + :white => 1, + :green => 2, + :blue => 3, + :red => 4, + :yellow => 5, + :orange => 6, + # Not a color, but is used to clear the display + :clear => 7 + } + + # PANEL SETTING + @psr 0x00 + # POWER SETTING + @pwr 0x01 + # POWER OFF + @pof 0x02 + # POWER OFF SEQUENCE SETTING + @pfs 0x03 + # POWER ON + @pon 0x04 + # DATA START TRANSMISSION 1 + @dtm1 0x10 + # DISPLAY REFRESH + @drf 0x12 + # VCOM AND DATA INTERVAL SETTING + # This command indicates the interval of Vcom and data output. When setting + # the vertical back porch, the total blanking will be kept (20 Hsync). + @cdi 0x50 + # TCON SETTING + # This command defines non-overlap period of Gate and Source. + @tcon 0x60 + # RESOLUTION SETTING (TRES) + # This command defines alternative resolution and this setting is of higher priority than the RES[1:0] in R00H (PSR). + @tres 0x61 + # SPI FLASH CONTROL + # This command defines MCU host direct access external memory mode. + # This might allow us to specify our own lookup tables! Which might mean our own colors! + @dam 0x65 + # WARN: Not found in datasheet + # python driver calls it "UC8159_7C" + @pws 0xE3 + + require Logger + + alias Inky.Display + alias Inky.HAL + alias Inky.PixelUtil + + defmodule State do + @moduledoc false + + @state_fields [:display, :io_mod, :io_state, :setup?] + + @enforce_keys @state_fields + defstruct @state_fields + end + + # + # API + # + + @impl HAL + def init(args) do + display = args[:display] || raise(ArgumentError, message: ":display missing in args") + io_mod = args[:io_mod] || @default_io_mod + + io_args = args[:io_args] || [] + io_args = if :gpio_mod in io_args, do: io_args, else: [gpio_mod: Circuits.GPIO] ++ io_args + io_args = if :spi_mod in io_args, do: io_args, else: [spi_mod: Circuits.SPI] ++ io_args + + %State{ + display: display, + io_mod: io_mod, + io_state: io_mod.init(io_args), + setup?: false + } + end + + @impl HAL + def handle_update(pixels, border, push_policy, state = %State{}) do + display = %Display{width: w, height: h, rotation: r} = state.display + buffer = PixelUtil.pixels_to_bits(pixels, w, h, r, @color_map, 4) + reset(state) + + case pre_update(state, push_policy) do + :cont -> do_update(state, display, border, buffer) + :halt -> {:error, :device_busy} + end + end + + # + # procedures + # + + defp pre_update(state, :await) do + await_device(state) + :cont + end + + defp pre_update(state, :once) do + case read_busy(state) do + 1 -> :cont + 0 -> :halt + end + end + + defp do_update(state, display, border, buffer) do + state + |> set_resolution(display.packed_resolution) + |> set_panel() + |> set_power() + |> set_pll_clock_frequency() + |> set_tse_register() + |> set_vcom_data_interval_setting(border) + |> set_gate_source_non_overlap_period() + |> disable_external_flash() + |> set_pws_whatever_that_means() + |> power_off_sequence() + |> push_pixel_buffer(buffer) + |> await_device() + |> pon() + |> await_device() + |> drf() + |> await_device() + |> pof() + |> await_device() + + {:ok, %State{state | setup?: true}} + end + + # + # "routines" and serial commands + # + + defp reset(state) do + state + |> set_reset(0) + |> sleep(100) + |> set_reset(1) + |> sleep(100) + end + + # >HH struct.pack, so big-endian, unsigned-short * 2 + defp set_resolution(state, packed_resolution), + do: write_command(state, @tres, packed_resolution) + + # Panel Setting + # 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448 + # 0b00100000 = LUT selection, 0 = ext flash, 1 = registers, we use ext flash + # 0b00010000 = Ignore + # 0b00001000 = Gate scan direction, 0 = down, 1 = up (default) + # 0b00000100 = Source shift direction, 0 = left, 1 = right (default) + # 0b00000010 = DC-DC converter, 0 = off, 1 = on + # 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) + defp set_panel(state), do: write_command(state, @psr, [0b11101111, 0x08]) + + defp set_power(state), + do: + write_command(state, @pwr, [ + # ??? - not documented in UC8159 datasheet + 0x06 <<< 3 + # SOURCE_INTERNAL_DC_DC + |> bor(0x01 <<< 2) + # GATE_INTERNAL_DC_DC + |> bor(0x01 <<< 1) + # LV_SOURCE_INTERNAL_DC_DC + |> bor(0x01), + # VGx_20V + 0x00, + # UC8159_7C + 0x23 + ]) + + # Set the PLL clock frequency to 50Hz + # 0b11000000 = Ignore + # 0b00111000 = M + # 0b00000111 = N + # PLL = 2MHz * (M / N) + # PLL = 2MHz * (7 / 4) + # PLL = 2,800,000 ??? + defp set_pll_clock_frequency(state), do: write_command(state, 0x3C) + + defp set_tse_register(state), do: write_command(state, 0x00) + + defp set_vcom_data_interval_setting(state, border), + do: write_command(state, @cdi, [bor(@colors[border] <<< 5, 0x17)]) + + defp set_gate_source_non_overlap_period(state), do: write_command(state, @tcon, 0x22) + defp disable_external_flash(state), do: write_command(state, @dam, 0x00) + defp set_pws_whatever_that_means(state), do: write_command(state, @pws, 0xAA) + defp power_off_sequence(state), do: write_command(state, @pfs, 0x00) + defp push_pixel_buffer(state, buffer), do: write_command(state, @dtm1, buffer) + defp pon(state), do: write_command(state, @pon) + defp drf(state), do: write_command(state, @drf) + defp pof(state), do: write_command(state, @pof) + + # + # waiting + # + + defp await_device(state) do + case read_busy(state) do + 0 -> + sleep(state, 10) + await_device(state) + + 1 -> + state + end + end + + # + # pipe-able wrappers + # + + defp sleep(state, sleep_time) do + io_call(state, :handle_sleep, [sleep_time]) + state + end + + defp set_reset(state, value) do + io_call(state, :handle_reset, [value]) + state + end + + defp read_busy(state) do + io_call(state, :handle_read_busy) + end + + defp write_command(state, command) do + io_call(state, :handle_command, [command]) + state + end + + defp write_command(state, command, data) do + io_call(state, :handle_command, [command, data]) + state + end + + # + # Behaviour dispatching + # + + # Dispatch to the IO callback module that's held in state, using the previously obtained state + defp io_call(state, op, args \\ []) do + apply(state.io_mod, op, [state.io_state | args]) + end +end diff --git a/lib/hal/rpihal.ex b/lib/hal/hal_phat_original.ex similarity index 99% rename from lib/hal/rpihal.ex rename to lib/hal/hal_phat_original.ex index f0e304e..440f407 100644 --- a/lib/hal/rpihal.ex +++ b/lib/hal/hal_phat_original.ex @@ -1,5 +1,5 @@ -defmodule Inky.RpiHAL do - @default_io_mod Inky.RpiIO +defmodule Inky.HAL.PhatOriginal do + @default_io_mod Inky.IO.Phat @moduledoc """ An `Inky.HAL` implementation responsible for sending commands to the Inky diff --git a/lib/hal/hal_ssd1608.ex b/lib/hal/hal_phat_ssd1608.ex similarity index 98% rename from lib/hal/hal_ssd1608.ex rename to lib/hal/hal_phat_ssd1608.ex index dc9b794..dcf67ad 100644 --- a/lib/hal/hal_ssd1608.ex +++ b/lib/hal/hal_phat_ssd1608.ex @@ -1,5 +1,5 @@ defmodule Inky.HAL.PhatSSD1608 do - @default_io_mod Inky.RpiIO + @default_io_mod Inky.IO.Phat @moduledoc """ An `Inky.HAL` implementation responsible for sending commands to the Inky @@ -10,7 +10,7 @@ defmodule Inky.HAL.PhatSSD1608 do @behaviour Inky.HAL alias Inky.PixelUtil - use Bitwise, only_operators: true + import Bitwise @color_map_black %{black: 0, miss: 1} @color_map_accent %{red: 1, yellow: 1, accent: 1, miss: 0} diff --git a/lib/hal/impression/rpiio.ex b/lib/hal/io_impression.ex similarity index 89% rename from lib/hal/impression/rpiio.ex rename to lib/hal/io_impression.ex index 2e8c108..d6ee0a0 100644 --- a/lib/hal/impression/rpiio.ex +++ b/lib/hal/io_impression.ex @@ -1,4 +1,4 @@ -defmodule Inky.Impression.RpiIO do +defmodule Inky.IO.Impression do @moduledoc """ An `Inky.InkyIO` implementation intended for use with raspberry pis and relies on Circuits.GPIO and Cirtuits.SPI. @@ -26,17 +26,6 @@ defmodule Inky.Impression.RpiIO do defstruct @state_fields end - @default_palette [ - [57, 48, 57], - [255, 255, 255], - [58, 91, 70], - [61, 59, 94], - [156, 72, 75], - [208, 190, 71], - [177, 106, 73], - [255, 255, 255] - ] - @reset_pin 27 @busy_pin 17 @dc_pin 22 @@ -55,8 +44,6 @@ defmodule Inky.Impression.RpiIO do @spi_data 1 @spi_chunk_bytes 4096 - @border :white - # API @impl InkyIO @@ -131,11 +118,10 @@ defmodule Inky.Impression.RpiIO do # MAYBE_DO: Write a 0 to CS pin :ok = gpio_call(state, :write, [state.dc_pid, data_or_command]) - result = - case spi_call(state, :transfer, [state.spi_pid, value]) do - {:ok, response} -> {:ok, response} - {:error, :transfer_failed} -> spi_call_chunked(state, value) - end + case spi_call(state, :transfer, [state.spi_pid, value]) do + {:ok, response} -> {:ok, response} + {:error, :transfer_failed} -> spi_call_chunked(state, value) + end # MAYBE_DO: Write a 1 to CS pin end diff --git a/lib/hal/rpiio.ex b/lib/hal/io_phat.ex similarity index 99% rename from lib/hal/rpiio.ex rename to lib/hal/io_phat.ex index 44dd68f..2590d47 100644 --- a/lib/hal/rpiio.ex +++ b/lib/hal/io_phat.ex @@ -1,4 +1,4 @@ -defmodule Inky.RpiIO do +defmodule Inky.IO.Phat do @moduledoc """ An `Inky.InkyIO` implementation intended for use with raspberry pis and relies on Circuits.GPIO and Cirtuits.SPI. diff --git a/lib/inky.ex b/lib/inky.ex index f0dcd74..ca6b6f0 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -9,14 +9,20 @@ defmodule Inky do require Logger alias Inky.Display - alias Inky.RpiHAL - alias Inky.Impression.RpiHAL, as: ImpressionHAL @typedoc "The Inky process name" @type name :: atom | {:global, term} | {:via, module, term} @default_border :black @push_timeout 5000 + @valid_accents [:black, :red, :yellow] + @valid_types [ + :phat_original, + :phat_ssd1608, + :impression_4, + :impression_5_7, + :impression_7_3 + ] defmodule State do @moduledoc false @@ -24,7 +30,7 @@ defmodule Inky do @enforce_keys [:display, :hal_state] defstruct border: :black, display: nil, - hal_mod: RpiHAL, + hal_mod: Inky.HAL.PhatOriginal, hal_state: nil, pixels: %{}, type: nil, @@ -44,9 +50,9 @@ defmodule Inky do ## Parameters - - `type` - An atom, representing the display type, either `:phat` or `:what` + - `type` - An atom, representing the display type, one of `#{inspect(@valid_types)}` - `accent` - An atom, representing the display's third color, one of - `:black`, `:red` or `:yellow`. + `#{inspect(@valid_accents)}`. ## Options @@ -57,12 +63,42 @@ defmodule Inky do """ def start_link(type, opts) when is_list(opts) do genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) - GenServer.start_link(__MODULE__, [type, opts], genserver_opts) + accent = verify_required_accent!(type, opts[:accent]) + opts = Keyword.put(opts, :accent, accent) + GenServer.start_link(__MODULE__, {type, opts}, genserver_opts) end - def start_link(type, accent, opts \\ %{}) do + # For backwards compatibility + def start_link(type, opts) when is_map(opts) do + opts = Enum.map(opts, fn {key, val} -> {key, val} end) + start_link(type, opts) + end + + def start_link(type, accent, opts) when is_list(opts) do genserver_opts = if(opts[:name], do: [name: opts[:name]], else: []) - GenServer.start_link(__MODULE__, [type, accent, opts], genserver_opts) + accent = verify_required_accent!(type, accent) + opts = Keyword.put(opts, :accent, accent) + GenServer.start_link(__MODULE__, {type, opts}, genserver_opts) + end + + # For backwards compatibility + def start_link(type, accent, opts) when is_map(opts) do + opts = Enum.map(opts, fn {key, val} -> {key, val} end) + start_link(type, accent, opts) + end + + defp verify_required_accent!(type, accent) + when type in [:phat_original, :phat_ssd1608, :what] do + if accent in @valid_accents do + accent + else + raise ":accent is required for the #{type}. And must be one of #{inspect(@valid_accents)}. Received #{inspect(accent)}" + end + end + + defp verify_required_accent!(type, _accent) + when type in [:impression_4, :impression_5_7, :impression_7_3] do + :none end @doc """ @@ -145,13 +181,22 @@ defmodule Inky do # @impl GenServer - def init([type, accent, opts]) do + def init({type, opts}) do border = opts[:border] || @default_border + accent = Access.get(opts, :accent, :none) + hal_mod = - case type do - :phat_ssd1608 -> opts[:hal_mod] || Inky.HAL.PhatSSD1608 - _ -> opts[:hal_mod] || RpiHAL + if opts[:hal_mod] do + opts[:hal_mod] + else + case type do + :phat_original -> Inky.HAL.PhatOriginal + :phat_ssd1608 -> Inky.HAL.PhatSSD1608 + :impression_4 -> Inky.HAL.ImpressionUC8159 + :impression_5_7 -> Inky.HAL.ImpressionUC8159 + :impression_7_3 -> Inky.HAL.ImpressionAC073TC1A + end end display = Display.spec_for(type, accent) @@ -166,39 +211,6 @@ defmodule Inky do }} end - @impl GenServer - def init([:impression = type, opts]) do - border = opts[:border] || @default_border - hal_mod = opts[:hal_mod] || ImpressionHAL - - display = Display.spec_for(type) - hal_state = hal_mod.init(%{display: display}) - - {:ok, - %State{ - border: border, - display: display, - hal_mod: hal_mod, - hal_state: hal_state - }} - end - - def init([:impression_7_3 = type, opts]) do - border = opts[:border] || @default_border - hal_mod = opts[:hal_mod] || ImpressionHAL - - display = Display.spec_for(type) - hal_state = hal_mod.init(%{display: display}) - - {:ok, - %State{ - border: border, - display: display, - hal_mod: hal_mod, - hal_state: hal_state - }} - end - # GenServer calls @impl GenServer diff --git a/mix.exs b/mix.exs index 6b3739b..6d34a33 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,8 @@ defmodule Inky.MixProject do [ app: :inky, version: "1.0.2", - elixir: "~> 1.8", + elixir: "~> 1.9", + elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, source_url: "https://github.com/pappersverk/inky/", deps: deps(), @@ -21,6 +22,10 @@ defmodule Inky.MixProject do ] end + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + # Run "mix help deps" to learn about dependencies. defp deps do [ diff --git a/test/rpihal_test.exs b/test/hal_phat_original_test.exs similarity index 67% rename from test/rpihal_test.exs rename to test/hal_phat_original_test.exs index d777ec7..7f59480 100644 --- a/test/rpihal_test.exs +++ b/test/hal_phat_original_test.exs @@ -1,10 +1,10 @@ -defmodule Inky.RpiHALTest do +defmodule Inky.HAL.PhatOriginalTest do @moduledoc false use ExUnit.Case alias Inky.Display - alias Inky.RpiHAL + alias Inky.HAL.PhatOriginal alias Inky.TestIO import Inky.TestUtil, only: [gather_messages: 0, pos2col: 2] @@ -19,8 +19,7 @@ defmodule Inky.RpiHALTest do setup_all do pixels = - :phat - |> Display.spec_for() + Display.spec_for(:phat_original, :black) |> init_pixels() %{pixels: pixels} @@ -28,9 +27,9 @@ defmodule Inky.RpiHALTest do describe "happy paths" do test "that init dispatches properly" do - display = Display.spec_for(:phat) + display = Display.spec_for(:phat_original, :black) # act - RpiHAL.init(%{ + PhatOriginal.init(%{ display: display, io_args: [], io_mod: TestIO @@ -43,7 +42,7 @@ defmodule Inky.RpiHALTest do test "that update dispatches properly when the device is never busy", ctx do # arrange, read_busy always returns 0 - display = Display.spec_for(:phat) + display = Display.spec_for(:phat_original, :black) init_args = %{ display: display, @@ -53,13 +52,13 @@ defmodule Inky.RpiHALTest do io_mod: TestIO } - state = RpiHAL.init(init_args) + state = PhatOriginal.init(init_args) # act - :ok = RpiHAL.handle_update(ctx.pixels, display.accent, :await, state) + :ok = PhatOriginal.handle_update(ctx.pixels, display.accent, :await, state) # assert - assert_received {:init, init_args} + assert_received {:init, _} assert TestIO.assert_expectations() == :ok spec = load_spec("data/success1.dat", __DIR__) mailbox = gather_messages() @@ -68,7 +67,7 @@ defmodule Inky.RpiHALTest do test "that update dispatches properly when the device is a little busy", ctx do # arrange, read_busy is a little busy each time, we expect two wait-loops. - display = Display.spec_for(:phat) + display = Display.spec_for(:phat_original, :black) init_args = %{ display: display, @@ -78,13 +77,13 @@ defmodule Inky.RpiHALTest do io_mod: TestIO } - state = RpiHAL.init(init_args) + state = PhatOriginal.init(init_args) # act - :ok = RpiHAL.handle_update(ctx.pixels, display.accent, :await, state) + :ok = PhatOriginal.handle_update(ctx.pixels, display.accent, :await, state) # assert - assert_received {:init, init_args} + assert_received {:init, _} assert TestIO.assert_expectations() == :ok spec = load_spec("data/success2.dat", __DIR__) mailbox = gather_messages() @@ -106,46 +105,46 @@ defmodule Inky.RpiHALTest do end test "test border, black accent", ctx do - display = Display.spec_for(:phat) + display = Display.spec_for(:phat_original, :black) init_black = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - black_state = RpiHAL.init(init_black) + black_state = PhatOriginal.init(init_black) # black accent, black border - :ok = RpiHAL.handle_update(ctx.pixels, :black, :await, black_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :black, :await, black_state) assert get_border_command() == [send_command: {60, 0}] # black accent, white border - :ok = RpiHAL.handle_update(ctx.pixels, :white, :await, black_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :white, :await, black_state) assert get_border_command() == [send_command: {60, 49}] end test "red accent, red border", ctx do # arrange - display = Display.spec_for(:phat, :red) + display = Display.spec_for(:phat_original, :red) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = RpiHAL.init(init_red) + red_state = PhatOriginal.init(init_red) # act, explicit border - :ok = RpiHAL.handle_update(ctx.pixels, :red, :await, red_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :red, :await, red_state) assert get_border_command() == [send_command: {60, 115}] # act, implicit border - :ok = RpiHAL.handle_update(ctx.pixels, :accent, :await, red_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, red_state) assert get_border_command() == [send_command: {60, 115}] end test "yellow accent, yellow border", ctx do # arrange - display = Display.spec_for(:phat, :yellow) + display = Display.spec_for(:phat_original, :yellow) init_yellow = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - yellow_state = RpiHAL.init(init_yellow) + yellow_state = PhatOriginal.init(init_yellow) # act, explicit border - :ok = RpiHAL.handle_update(ctx.pixels, :yellow, :await, yellow_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :yellow, :await, yellow_state) assert get_border_command() == [send_command: {60, 51}] # act, implicit border - :ok = RpiHAL.handle_update(ctx.pixels, :accent, :await, yellow_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, yellow_state) assert get_border_command() == [send_command: {60, 51}] end @@ -153,19 +152,19 @@ defmodule Inky.RpiHALTest do # arrange display = Display.spec_for(:what, :yellow) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = RpiHAL.init(init_red) + red_state = PhatOriginal.init(init_red) - :ok = RpiHAL.handle_update(ctx.pixels, :accent, :await, red_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, red_state) assert get_vhs_and_vhl_voltage_command() == [send_command: {0x04, 0x07}] end test "yellow display, phat", ctx do # arrange - display = Display.spec_for(:phat, :yellow) + display = Display.spec_for(:phat_original, :yellow) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = RpiHAL.init(init_red) + red_state = PhatOriginal.init(init_red) - :ok = RpiHAL.handle_update(ctx.pixels, :accent, :await, red_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, red_state) assert get_vhs_and_vhl_voltage_command() == [send_command: {0x04, 0x07}] end @@ -173,9 +172,9 @@ defmodule Inky.RpiHALTest do # arrange display = Display.spec_for(:what, :red) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = RpiHAL.init(init_red) + red_state = PhatOriginal.init(init_red) - :ok = RpiHAL.handle_update(ctx.pixels, :red, :await, red_state) + :ok = PhatOriginal.handle_update(ctx.pixels, :red, :await, red_state) assert get_vhs_and_vhl_voltage_command() == [send_command: {0x04, <<0x30, 0xAC, 0x22>>}] end end diff --git a/test/inky_test.exs b/test/inky_test.exs index ca4c6a0..0bd9b20 100644 --- a/test/inky_test.exs +++ b/test/inky_test.exs @@ -1,5 +1,3 @@ -Code.require_file("test/support/testhal.exs") - defmodule Inky.InkyTest do @moduledoc false @@ -13,7 +11,7 @@ defmodule Inky.InkyTest do doctest Inky setup_all do - init_args = [:test_small, :red, [hal_mod: TestHAL]] + init_args = {:test_small, [accent: :red, hal_mod: TestHAL]} {:ok, inited_state} = Inky.init(init_args) receive do diff --git a/test/inky_timer_test.exs b/test/inky_timer_test.exs index e235cec..5d7c2aa 100644 --- a/test/inky_timer_test.exs +++ b/test/inky_timer_test.exs @@ -1,5 +1,3 @@ -Code.require_file("test/support/testhal.exs") - defmodule Inky.InkyTimerTest do @moduledoc false @@ -13,14 +11,13 @@ defmodule Inky.InkyTimerTest do doctest Inky setup_all do - init_args = [:test_small, :red, [hal_mod: TestHAL]] - {:ok, inited_state} = Inky.init(init_args) + {:ok, inited_state} = Inky.init({:test_small, accent: :red, hal_mod: TestHAL}) receive do {TestHAL, :init} -> :ok end - %{inited_state: inited_state, init_args: init_args} + %{inited_state: inited_state} end # AWAIT, timer cleared diff --git a/test/rpiio_test.exs b/test/io_phat_test.exs similarity index 82% rename from test/rpiio_test.exs rename to test/io_phat_test.exs index c2aa80b..58a62cf 100644 --- a/test/rpiio_test.exs +++ b/test/io_phat_test.exs @@ -1,4 +1,4 @@ -defmodule Inky.RpiIOTest do +defmodule Inky.IO.PhatTest do @moduledoc false use ExUnit.Case @@ -8,10 +8,10 @@ defmodule Inky.RpiIOTest do import Inky.TestUtil, only: [gather_messages: 0] test "spi_write only sets the dc pin once" do - state = Inky.RpiIO.init() + state = Inky.IO.Phat.init() # NOTE: discard init messages gather_messages() - Inky.RpiIO.handle_command(state, 0x42, [0x1, 0x2, 0x4]) + Inky.IO.Phat.handle_command(state, 0x42, [0x1, 0x2, 0x4]) assert gather_messages() == [ {TestGPIO, {{:gpio, :write}, {{:pid, 22}, 0}}}, diff --git a/test/support/testhal.exs b/test/support/testhal.ex similarity index 100% rename from test/support/testhal.exs rename to test/support/testhal.ex From 5d65f0969893738176d5aa14daff4dbba2318162 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sun, 6 Aug 2023 17:43:48 -1000 Subject: [PATCH 30/36] Fix credo issues --- lib/buttons.ex | 3 +++ lib/hal/hal_impression_ac073tc1a.ex | 4 ++-- mix.exs | 2 +- mix.lock | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/buttons.ex b/lib/buttons.ex index 48608ae..998879f 100644 --- a/lib/buttons.ex +++ b/lib/buttons.ex @@ -31,6 +31,9 @@ defmodule Inky.Buttons do @type name() :: :a | :b | :x | :y defmodule Event do + @moduledoc """ + Represents an event from the buttons + """ defstruct [:action, :name, :value, :timestamp] @type t :: %Event{ diff --git a/lib/hal/hal_impression_ac073tc1a.ex b/lib/hal/hal_impression_ac073tc1a.ex index e085ea7..55e8167 100644 --- a/lib/hal/hal_impression_ac073tc1a.ex +++ b/lib/hal/hal_impression_ac073tc1a.ex @@ -154,7 +154,7 @@ defmodule Inky.HAL.ImpressionAC073TC1A do |> set_pll_clock_frequency() |> write_command(@tse, [0x00]) # border? - # TODO: Combine with `set_vcom_data_interval_setting` + # Could be combined with `set_vcom_data_interval_setting`? |> write_command(@cdi, [0x3F]) |> write_command(@tcon, [0x02, 0x00]) # resolution? @@ -169,7 +169,7 @@ defmodule Inky.HAL.ImpressionAC073TC1A do |> write_command(@tsset, [0x00]) # End of setup - # TODO: Need to force white somehow? + # Need to force white somehow? # The python driver is doing some bit manipulation before this call # https://github.com/pimoroni/inky/blob/98383c5d47928b90ee3951ed72576b7064e573e7/library/inky/inky_ac073tc1a.py#L300 |> push_pixel_buffer(buffer) diff --git a/mix.exs b/mix.exs index 6d34a33..2ab96b4 100644 --- a/mix.exs +++ b/mix.exs @@ -32,7 +32,7 @@ defmodule Inky.MixProject do {:circuits_gpio, "~> 0.4"}, {:circuits_spi, "~> 0.1"}, {:circuits_i2c, "~> 0.3"}, - {:credo, "~> 1.0.0", only: [:dev, :test], runtime: false}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev} ] end diff --git a/mix.lock b/mix.lock index 8915255..7ce169e 100644 --- a/mix.lock +++ b/mix.lock @@ -3,10 +3,11 @@ "circuits_gpio": {:hex, :circuits_gpio, "0.4.8", "75a62b07131f119c0ffa1dd49f79fbe29c46fd39b20ef0acc036d449fb780b89", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "05e341a5de7e9181a0ee6b3281bd2dc278ae4651825e95c9e83c9eabc421448e"}, "circuits_i2c": {:hex, :circuits_i2c, "0.3.9", "746a599ac06f8d31572143a8c51e1bc787246c173669940dc23e078907fb13e1", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c6d75387637a4fbae77cf37af45000fe23763eb8104cc50e7aae532bec46e91d"}, "circuits_spi": {:hex, :circuits_spi, "0.1.6", "30cefc0169fccb9204ac500c6614409d20a4b1fd06124772e1155bf7b05148f1", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0d185813e15c9dd1aa04f63647fa95748979e0e1589f2672f7cdff44b696ff14"}, - "credo": {:hex, :credo, "1.0.5", "fdea745579f8845315fe6a3b43e2f9f8866839cfbc8562bb72778e9fdaa94214", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "16105fac37c5c4b3f6e1f70ba0784511fec4275cd8bb979386e3c739cf4e6455"}, + "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "ex_doc": {:hex, :ex_doc, "0.30.4", "e8395c8e3c007321abb30a334f9f7c0858d80949af298302daf77553468c0c39", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "9a19f0c50ffaa02435668f5242f2b2a61d46b541ebf326884505dfd3dd7af5e4"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, From f6e11c789cf73c429d99d4f1b1bc27f3d51fbfb7 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sun, 6 Aug 2023 18:23:00 -1000 Subject: [PATCH 31/36] Allow newer versions of circuits libraries There's no breaking changes except for a small one in circuits spi 1.4.0 that says "Remove Erlang convenience functions since no one used them" --- mix.exs | 6 +++--- mix.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 2ab96b4..db07293 100644 --- a/mix.exs +++ b/mix.exs @@ -29,9 +29,9 @@ defmodule Inky.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:circuits_gpio, "~> 0.4"}, - {:circuits_spi, "~> 0.1"}, - {:circuits_i2c, "~> 0.3"}, + {:circuits_gpio, "~> 0.4 or ~> 1.0"}, + {:circuits_spi, "~> 0.1 or ~> 1.0"}, + {:circuits_i2c, "~> 0.3 or ~> 1.0"}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev} ] diff --git a/mix.lock b/mix.lock index 7ce169e..02d746a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,8 @@ %{ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "circuits_gpio": {:hex, :circuits_gpio, "0.4.8", "75a62b07131f119c0ffa1dd49f79fbe29c46fd39b20ef0acc036d449fb780b89", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "05e341a5de7e9181a0ee6b3281bd2dc278ae4651825e95c9e83c9eabc421448e"}, - "circuits_i2c": {:hex, :circuits_i2c, "0.3.9", "746a599ac06f8d31572143a8c51e1bc787246c173669940dc23e078907fb13e1", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c6d75387637a4fbae77cf37af45000fe23763eb8104cc50e7aae532bec46e91d"}, - "circuits_spi": {:hex, :circuits_spi, "0.1.6", "30cefc0169fccb9204ac500c6614409d20a4b1fd06124772e1155bf7b05148f1", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0d185813e15c9dd1aa04f63647fa95748979e0e1589f2672f7cdff44b696ff14"}, + "circuits_gpio": {:hex, :circuits_gpio, "1.1.0", "cda895fd0a12fdf50e27f6d61cc349587dff29755fca640b93233a661925d97a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "11dab3c7b39cbe08588e9527c9fd98117be485f70b61641874abdda50340e991"}, + "circuits_i2c": {:hex, :circuits_i2c, "1.2.2", "1666bf1763fe60ab835462d13f04ca75b0de000a7d5b89e24d8f35a06ef6d097", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a7fffcb1bf128e4112b0734796f26b0b4f2359e4c60e86d9718c31834e10a343"}, + "circuits_spi": {:hex, :circuits_spi, "1.4.0", "ca1674c4c955bbd3e6e8d4390c63514da6b6d976b098ed99d375d09b94259a5b", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "bf37c24a720c30a5201b7d098276ef074c1b5a79105e11dec11ddd54d6fb793f"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, From 975d63b331c595ea563930bc2e33e5d711a09b72 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sat, 12 Aug 2023 19:20:54 -1000 Subject: [PATCH 32/36] Rename phat_original to phat_il91874 --- README.md | 2 +- lib/display/display.ex | 8 +-- ...l_phat_original.ex => hal_phat_il91874.ex} | 5 +- lib/inky.ex | 8 +-- ...nal_test.exs => hal_phat_il91874_test.exs} | 60 +++++++++---------- 5 files changed, 43 insertions(+), 40 deletions(-) rename lib/hal/{hal_phat_original.ex => hal_phat_il91874.ex} (96%) rename test/{hal_phat_original_test.exs => hal_phat_il91874_test.exs} (68%) diff --git a/README.md b/README.md index 12f7933..2a84991 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ config in init, adjust accordingly): ```elixir # Start your Inky process ... -{:ok, pid} = Inky.start_link(:phat_original, accent: :red, name: InkySample) +{:ok, pid} = Inky.start_link(:phat_il91874, accent: :red, name: InkySample) painter = fn x, y, w, h, _pixels_so_far -> wh = w / 2 diff --git a/lib/display/display.ex b/lib/display/display.ex index bd05632..cda3ee4 100644 --- a/lib/display/display.ex +++ b/lib/display/display.ex @@ -72,7 +72,7 @@ defmodule Inky.Display do } end - @spec spec_for(:phat_original | :phat_ssd1608 | :what, :black | :red | :yellow) :: + @spec spec_for(:phat_il91874 | :phat_ssd1608 | :what, :black | :red | :yellow) :: Inky.Display.t() def spec_for(type = :phat_ssd1608, accent) do # Keep it minimal. Details are specified in `Inky.HAL.PhatSSD1608`. @@ -87,7 +87,7 @@ defmodule Inky.Display do } end - def spec_for(type = :phat_original, accent) do + def spec_for(type = :phat_il91874, accent) do %__MODULE__{ type: type, width: 212, @@ -133,7 +133,7 @@ defmodule Inky.Display do columns = case type do :what -> width - :phat_original -> height + :phat_il91874 -> height :test_small -> height end @@ -144,7 +144,7 @@ defmodule Inky.Display do rows = case type do :what -> height - :phat_original -> width + :phat_il91874 -> width :test_small -> width end diff --git a/lib/hal/hal_phat_original.ex b/lib/hal/hal_phat_il91874.ex similarity index 96% rename from lib/hal/hal_phat_original.ex rename to lib/hal/hal_phat_il91874.ex index 440f407..a546d08 100644 --- a/lib/hal/hal_phat_original.ex +++ b/lib/hal/hal_phat_il91874.ex @@ -1,10 +1,13 @@ -defmodule Inky.HAL.PhatOriginal do +defmodule Inky.HAL.PhatIL91874 do @default_io_mod Inky.IO.Phat @moduledoc """ An `Inky.HAL` implementation responsible for sending commands to the Inky screen. It delegates to whatever IO module its user provides at init, but defaults to #{inspect(@default_io_mod)} + + Specific to the IL91874 chip which was in the original batch of pHAT's: + https://github.com/pimoroni/inky-phat/blob/4c0ac9ffae25d2ee055f41f1d958c64b17f574bd/library/inkyphat/inky212x104.py#L2 """ @behaviour Inky.HAL diff --git a/lib/inky.ex b/lib/inky.ex index ca6b6f0..b222c83 100644 --- a/lib/inky.ex +++ b/lib/inky.ex @@ -17,7 +17,7 @@ defmodule Inky do @push_timeout 5000 @valid_accents [:black, :red, :yellow] @valid_types [ - :phat_original, + :phat_il91874, :phat_ssd1608, :impression_4, :impression_5_7, @@ -30,7 +30,7 @@ defmodule Inky do @enforce_keys [:display, :hal_state] defstruct border: :black, display: nil, - hal_mod: Inky.HAL.PhatOriginal, + hal_mod: Inky.HAL.PhatIL91874, hal_state: nil, pixels: %{}, type: nil, @@ -88,7 +88,7 @@ defmodule Inky do end defp verify_required_accent!(type, accent) - when type in [:phat_original, :phat_ssd1608, :what] do + when type in [:phat_il91874, :phat_ssd1608, :what] do if accent in @valid_accents do accent else @@ -191,7 +191,7 @@ defmodule Inky do opts[:hal_mod] else case type do - :phat_original -> Inky.HAL.PhatOriginal + :phat_il91874 -> Inky.HAL.PhatIL91874 :phat_ssd1608 -> Inky.HAL.PhatSSD1608 :impression_4 -> Inky.HAL.ImpressionUC8159 :impression_5_7 -> Inky.HAL.ImpressionUC8159 diff --git a/test/hal_phat_original_test.exs b/test/hal_phat_il91874_test.exs similarity index 68% rename from test/hal_phat_original_test.exs rename to test/hal_phat_il91874_test.exs index 7f59480..2a0a94d 100644 --- a/test/hal_phat_original_test.exs +++ b/test/hal_phat_il91874_test.exs @@ -1,10 +1,10 @@ -defmodule Inky.HAL.PhatOriginalTest do +defmodule Inky.HAL.PhatIL91874Test do @moduledoc false use ExUnit.Case alias Inky.Display - alias Inky.HAL.PhatOriginal + alias Inky.HAL.PhatIL91874 alias Inky.TestIO import Inky.TestUtil, only: [gather_messages: 0, pos2col: 2] @@ -19,7 +19,7 @@ defmodule Inky.HAL.PhatOriginalTest do setup_all do pixels = - Display.spec_for(:phat_original, :black) + Display.spec_for(:phat_il91874, :black) |> init_pixels() %{pixels: pixels} @@ -27,9 +27,9 @@ defmodule Inky.HAL.PhatOriginalTest do describe "happy paths" do test "that init dispatches properly" do - display = Display.spec_for(:phat_original, :black) + display = Display.spec_for(:phat_il91874, :black) # act - PhatOriginal.init(%{ + PhatIL91874.init(%{ display: display, io_args: [], io_mod: TestIO @@ -42,7 +42,7 @@ defmodule Inky.HAL.PhatOriginalTest do test "that update dispatches properly when the device is never busy", ctx do # arrange, read_busy always returns 0 - display = Display.spec_for(:phat_original, :black) + display = Display.spec_for(:phat_il91874, :black) init_args = %{ display: display, @@ -52,10 +52,10 @@ defmodule Inky.HAL.PhatOriginalTest do io_mod: TestIO } - state = PhatOriginal.init(init_args) + state = PhatIL91874.init(init_args) # act - :ok = PhatOriginal.handle_update(ctx.pixels, display.accent, :await, state) + :ok = PhatIL91874.handle_update(ctx.pixels, display.accent, :await, state) # assert assert_received {:init, _} @@ -67,7 +67,7 @@ defmodule Inky.HAL.PhatOriginalTest do test "that update dispatches properly when the device is a little busy", ctx do # arrange, read_busy is a little busy each time, we expect two wait-loops. - display = Display.spec_for(:phat_original, :black) + display = Display.spec_for(:phat_il91874, :black) init_args = %{ display: display, @@ -77,10 +77,10 @@ defmodule Inky.HAL.PhatOriginalTest do io_mod: TestIO } - state = PhatOriginal.init(init_args) + state = PhatIL91874.init(init_args) # act - :ok = PhatOriginal.handle_update(ctx.pixels, display.accent, :await, state) + :ok = PhatIL91874.handle_update(ctx.pixels, display.accent, :await, state) # assert assert_received {:init, _} @@ -105,46 +105,46 @@ defmodule Inky.HAL.PhatOriginalTest do end test "test border, black accent", ctx do - display = Display.spec_for(:phat_original, :black) + display = Display.spec_for(:phat_il91874, :black) init_black = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - black_state = PhatOriginal.init(init_black) + black_state = PhatIL91874.init(init_black) # black accent, black border - :ok = PhatOriginal.handle_update(ctx.pixels, :black, :await, black_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :black, :await, black_state) assert get_border_command() == [send_command: {60, 0}] # black accent, white border - :ok = PhatOriginal.handle_update(ctx.pixels, :white, :await, black_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :white, :await, black_state) assert get_border_command() == [send_command: {60, 49}] end test "red accent, red border", ctx do # arrange - display = Display.spec_for(:phat_original, :red) + display = Display.spec_for(:phat_il91874, :red) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = PhatOriginal.init(init_red) + red_state = PhatIL91874.init(init_red) # act, explicit border - :ok = PhatOriginal.handle_update(ctx.pixels, :red, :await, red_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :red, :await, red_state) assert get_border_command() == [send_command: {60, 115}] # act, implicit border - :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, red_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :accent, :await, red_state) assert get_border_command() == [send_command: {60, 115}] end test "yellow accent, yellow border", ctx do # arrange - display = Display.spec_for(:phat_original, :yellow) + display = Display.spec_for(:phat_il91874, :yellow) init_yellow = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - yellow_state = PhatOriginal.init(init_yellow) + yellow_state = PhatIL91874.init(init_yellow) # act, explicit border - :ok = PhatOriginal.handle_update(ctx.pixels, :yellow, :await, yellow_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :yellow, :await, yellow_state) assert get_border_command() == [send_command: {60, 51}] # act, implicit border - :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, yellow_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :accent, :await, yellow_state) assert get_border_command() == [send_command: {60, 51}] end @@ -152,19 +152,19 @@ defmodule Inky.HAL.PhatOriginalTest do # arrange display = Display.spec_for(:what, :yellow) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = PhatOriginal.init(init_red) + red_state = PhatIL91874.init(init_red) - :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, red_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :accent, :await, red_state) assert get_vhs_and_vhl_voltage_command() == [send_command: {0x04, 0x07}] end test "yellow display, phat", ctx do # arrange - display = Display.spec_for(:phat_original, :yellow) + display = Display.spec_for(:phat_il91874, :yellow) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = PhatOriginal.init(init_red) + red_state = PhatIL91874.init(init_red) - :ok = PhatOriginal.handle_update(ctx.pixels, :accent, :await, red_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :accent, :await, red_state) assert get_vhs_and_vhl_voltage_command() == [send_command: {0x04, 0x07}] end @@ -172,9 +172,9 @@ defmodule Inky.HAL.PhatOriginalTest do # arrange display = Display.spec_for(:what, :red) init_red = %{display: display, io_args: [read_busy: 0], io_mod: TestIO} - red_state = PhatOriginal.init(init_red) + red_state = PhatIL91874.init(init_red) - :ok = PhatOriginal.handle_update(ctx.pixels, :red, :await, red_state) + :ok = PhatIL91874.handle_update(ctx.pixels, :red, :await, red_state) assert get_vhs_and_vhl_voltage_command() == [send_command: {0x04, <<0x30, 0xAC, 0x22>>}] end end From 4174f7ebda6df2417fdef7f49217a474c0cc53e2 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sat, 12 Aug 2023 19:33:03 -1000 Subject: [PATCH 33/36] Rename Buttons to ImpressionButtons Also improve the moduledocs --- lib/{buttons.ex => impression_buttons.ex} | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) rename lib/{buttons.ex => impression_buttons.ex} (83%) diff --git a/lib/buttons.ex b/lib/impression_buttons.ex similarity index 83% rename from lib/buttons.ex rename to lib/impression_buttons.ex index 998879f..6fb9c59 100644 --- a/lib/buttons.ex +++ b/lib/impression_buttons.ex @@ -1,19 +1,21 @@ -defmodule Inky.Buttons do +defmodule Inky.ImpressionButtons do @moduledoc """ - The Inky Impression has 4 buttons that are monitored independently from the display - and can be started in supervison. + Adds support for the 4 buttons on the Inky Impressions + + The 4 buttons are monitored independently of the display and can be started in the + supervison tree. Supply the `:handler` option as an atom, a pid, or `{module, function, args}` tuple - specifying where to send events to. If no handler is supplied, events are simply logged + specifying where to send events to. If no handler is supplied, events are simply logged. ```elixir - Inky.Buttons.start_link(handler: self()) + Inky.ImpressionButtons.start_link(handler: self()) ``` You can also query the current value of a button at any time ```elixir - Inky.Buttons.get_value(:a) + Inky.ImpressionButtons.get_value(:a) ``` """ @@ -38,7 +40,7 @@ defmodule Inky.Buttons do @type t :: %Event{ action: :pressed | :released, - name: Buttons.name(), + name: Inky.ImpressionsButtons.name(), value: 1 | 0, timestamp: non_neg_integer() } @@ -54,7 +56,9 @@ defmodule Inky.Buttons do Options: - * `:handler` - pass an atom, a pid, or an MFA to receive button events + * `:handler` - pass an atom a pid, or an MFA to receive button events + MFA stands for Module Function Args, here's an example MFA that would print out the events `{IO, :inspect, []}` + Note: the event will be prepended to the argument list """ @spec start_link(keyword) :: GenServer.on_start() def start_link(opts \\ []) do From c81122d5fbd10f22a6643bf534a7ed3360002139 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sat, 12 Aug 2023 19:50:44 -1000 Subject: [PATCH 34/36] Improve the readme --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2a84991..bba600f 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ This is a port of Pimoroni's [python Inky library](https://github.com/pimoroni/inky) written in Elixir. This library is -intended to support both Inky pHAT and wHAT, but since we only have pHATs, the -wHAT support may not be fully functional. +intended to support the Inky pHAT and Inky Impression. Eventually it will support the Inky wHAT as well but it is not currently supported. See the [API reference](https://hexdocs.pm/inky/api-reference.html) for details on how to use the functionality provided. @@ -19,15 +18,15 @@ can not see results without using a physical device. ### Scenic Driver -A [basic driver](https://github.com/pappersverk/scenic_driver_inky) for scenic -is in the works, check it out, to follow how it is progressing. +There is [basic driver](https://github.com/pappersverk/scenic_driver_inky) for use with +[scenic](https://github.com/ScenicFramework/scenic). ## Getting started Inky is available on Hex. Add inky to your mix.exs deps: ```elixir -{:inky, "~> 1.0.1"}, +{:inky, "~> 1.0.2"}, ``` Run `mix deps.get` to get the new dep. @@ -46,7 +45,8 @@ config in init, adjust accordingly): ```elixir # Start your Inky process ... -{:ok, pid} = Inky.start_link(:phat_il91874, accent: :red, name: InkySample) +type = :phat_il91874 +{:ok, pid} = Inky.start_link(type, accent: :red, name: InkySample) painter = fn x, y, w, h, _pixels_so_far -> wh = w / 2 @@ -65,3 +65,12 @@ Inky.set_pixels(InkySample, painter, border: :white) # Flip a few pixels Inky.set_pixels(pid, %{{0,0}: :black, {3,49}: :red, {23, 4}: white}) ``` + +## Figuring out the value to pass for `type` + +- Inky pHAT ordered roughly before 2019 -> `:phat_il91874` +- Inky pHAT ordered roughly after 2019 -> `:phat_ssd1608` +- Inky Impression 4" -> `:impression_4` +- Inky Impression 5.7" -> `:impression_5_7` +- Inky Impression 7.3" -> `:impression_7_3` +- Inky wHAT: **Not currently supported** From 83a37a8a0dad7de83f00b491aa82a929078b39ad Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sat, 12 Aug 2023 19:54:48 -1000 Subject: [PATCH 35/36] Rename bit_size to bit_count --- lib/display/pixelutil.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/display/pixelutil.ex b/lib/display/pixelutil.ex index 8185603..b2a6e43 100644 --- a/lib/display/pixelutil.ex +++ b/lib/display/pixelutil.ex @@ -3,7 +3,7 @@ defmodule Inky.PixelUtil do PixelUtil maps pixels to bitstrings to be sent to an Inky screen """ - def pixels_to_bits(pixels, width, height, rotation_degrees, color_map, bit_size \\ 1) do + def pixels_to_bits(pixels, width, height, rotation_degrees, color_map, bit_count \\ 1) do {outer_axis, dimension_vectors} = rotation_degrees |> normalised_rotation() @@ -14,7 +14,7 @@ defmodule Inky.PixelUtil do |> do_pixels_to_bits( &pixels[pixel_key(outer_axis, &1, &2)], &(color_map[&1] || color_map.miss), - bit_size + bit_count ) end @@ -62,10 +62,10 @@ defmodule Inky.PixelUtil do defp rotated_dimension(_width, height, {:y, 1}), do: 0..(height - 1) defp rotated_dimension(_width, height, {:y, -1}), do: (height - 1)..0 - defp do_pixels_to_bits({i_range, j_range}, pixel_picker, cmap, bit_size) do + defp do_pixels_to_bits({i_range, j_range}, pixel_picker, cmap, bit_count) do for i <- i_range, j <- j_range, - do: <>, + do: <>, into: <<>> end From 535ba1db96bb246aed408112777af2352b094ef9 Mon Sep 17 00:00:00 2001 From: Jason Axelson Date: Sat, 12 Aug 2023 19:56:24 -1000 Subject: [PATCH 36/36] IO.Phat moduledoc --- lib/hal/io_phat.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/hal/io_phat.ex b/lib/hal/io_phat.ex index 2590d47..e25f173 100644 --- a/lib/hal/io_phat.ex +++ b/lib/hal/io_phat.ex @@ -1,7 +1,8 @@ defmodule Inky.IO.Phat do @moduledoc """ - An `Inky.InkyIO` implementation intended for use with raspberry pis and relies on - Circuits.GPIO and Cirtuits.SPI. + An `Inky.InkyIO` implementation used for the pHATs + + Relies on Circuits.GPIO and Cirtuits.SPI. """ @behaviour Inky.InkyIO