Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7d80cce
Start work on on Inky Impression
lawik Oct 10, 2021
8ee2dda
More work on setup and update for Inky Impression
lawik Oct 27, 2021
6e11741
Add all of the Impression code but not working, added cs0 but that wo…
lawik Oct 27, 2021
c56c9be
Kinda working!
axelson Nov 8, 2021
e415e0e
Kind of like a flag
axelson Nov 8, 2021
fc37b1f
Cleaning
axelson Nov 9, 2021
cc82eda
Clean up cs pin calls
axelson Nov 9, 2021
99d2cd5
clean
axelson Nov 9, 2021
7a636df
Merge pull request #42 from axelson/jax/inky-impression
lawik Nov 9, 2021
753d5a8
Fix log typo
Jul 3, 2022
3f46d9a
Adds comments to byte commands defined for Impression in hal
Jul 3, 2022
3142798
Fix typo in comment
Jul 3, 2022
ae2e48f
Update mix deps for development on jasonmj
Jul 3, 2022
07f75e6
Update display definition for impression
Jul 3, 2022
63cdb4f
Add handling for impression in packed_width and packed_height
Jul 3, 2022
13793cd
Fix usage of state in log function
Jul 3, 2022
853021b
Add set_dimensions to do_update for impression
Jul 3, 2022
46a3b40
Replace buffer generation with PixelUtil.pixels_to_bits
Jul 3, 2022
9dfa181
Add a color_map for the impression
Jul 3, 2022
5bfb57f
Add notes about WIP toward impression support
Jul 3, 2022
4689834
Reverting dev changes to mix.exs
Jul 3, 2022
3bcf4f1
Cleaning up unused code
Jul 8, 2022
590c25f
Adding bit_size option to PixelUtil.pixels_to_bits
Jul 8, 2022
d5bca11
Merge pull request #46 from jasonmj/impression
lawik Jul 11, 2022
9abc79f
Cleans up code for PR
Jul 12, 2022
80bdee8
Adds painter test
Jul 12, 2022
f86d1b8
Support circuits_gpio 1.0
axelson Jul 13, 2022
b30be19
Merge pull request #47 from axelson/impression
Jul 13, 2022
a68e31a
Add support for buttons on Inky Impression
Jul 23, 2022
40cda84
Support sending button messages to a process by registered atom name
Aug 1, 2022
6a6cd42
Merge pull request #48 from jasonmj/impression
lawik Aug 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion lib/display/display.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Inky.Display do
"""

alias Inky.LookupTables

@type t() :: %__MODULE__{}

@enforce_keys [:type, :width, :height, :packed_dimensions, :rotation, :accent, :luts]

Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Collaborator

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?

Copy link
Copy Markdown
Collaborator

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.

Expand All @@ -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)

Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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.

Comment thread
jasonmj marked this conversation as resolved.
Outdated
end
261 changes: 261 additions & 0 deletions lib/hal/impression/rpihal.ex
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why we don't copy-paste stuff 😅

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
Loading