-
Notifications
You must be signed in to change notification settings - Fork 12
Start work on on Inky Impression #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
7d80cce
8ee2dda
6e11741
c56c9be
e415e0e
fc37b1f
cc82eda
99d2cd5
7a636df
753d5a8
3f46d9a
3142798
ae2e48f
07f75e6
63cdb4f
13793cd
853021b
46a3b40
9dfa181
5bfb57f
4689834
3bcf4f1
590c25f
d5bca11
9abc79f
80bdee8
f86d1b8
b30be19
a68e31a
40cda84
6a6cd42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| <<rows::unsigned-little-integer-16>> | ||
| 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feels like things are getting muddled in display.ex... we might want to consider how things could be split up. Not sure if it should be by amount of colours or "base type" of display. The what/phat share things, but I'm seeing quite a few things that aren't really compatible with the impression.
jasonmj marked this conversation as resolved.
Outdated
|
||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is why we don't copy-paste stuff 😅
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are all being used. Is there an outstanding issue here? |
||
|
|
||
| # | ||
| # 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these are required for one display but not the other, we should consider refactoring things so that we don't force re-usability where it is not required/meaningful (maybe that's exactly what you did here? :D). "False generalisation" only makes things unclear since you don't knew when/if something is required/missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reverted this change too, since these keys can peacefully coexist in the struct the defines the impression display. We're not using packed dimensions because it's redundant/unnecessary, but if someone wants to use them in the future, it'll be possible.
As for luts, it's not used at all for the impression. Not sure we need to define a separate struct+key enforcement just for that though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good food for thought, though... if the devices are diverting, perhaps our configurations should, too.
One way of dealing with it would be to have a protocol common for all types, for the things that are common to all displays and then have the specific drivers check if it is a value of the correct underlying type at runtime during init. But that might be a bit more than what you signed up for at this point and a bit of over-engineering before we have several displays where the fit is off... thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like it might be a worthwhile refactor to open a separate issue for.