Skip to content

Commit

Permalink
Merge pull request #9 from pappersverk/change-display-frame
Browse files Browse the repository at this point in the history
Add `display_raw_frame/2`, `put_buffer/1` and `get_buffer/0`
  • Loading branch information
luisgabrielroldan authored Dec 28, 2021
2 parents cb4fab3 + ace02f9 commit f7d0921
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OLED

[![CircleCI](https://circleci.com/gh/pappersverk/oled.svg?style=svg)](https://circleci.com/gh/pappersverk/oled)
![Test Status](https://github.com/pappersverk/oled/actions/workflows/tests.yml/badge.svg)
[![Hex version](https://img.shields.io/hexpm/v/oled.svg "Hex version")](https://hex.pm/packages/oled)


Expand Down
37 changes: 36 additions & 1 deletion lib/oled/display.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,21 @@ defmodule OLED.Display do
def display_frame(data, opts \\ []),
do: Server.display_frame(@me, data, opts)

def display_raw_frame(data, opts \\ []),
do: Server.display_raw_frame(@me, data, opts)

def clear(),
do: Server.clear(@me)

def clear(pixel_state),
do: Server.clear(@me, pixel_state)

def put_buffer(data),
do: Server.put_buffer(@me, data)

def get_buffer(),
do: Server.get_buffer(@me)

def put_pixel(x, y, opts \\ []),
do: Server.put_pixel(@me, x, y, opts)

Expand Down Expand Up @@ -118,10 +127,21 @@ defmodule OLED.Display do
@callback display() :: :ok

@doc """
Transfer a data frame to the display buffer.
Transfer a data frame to the screen. The data frame format is equal to the display buffer
that gets altered via the drawing commands.
Calling this function transfers the data frame directly to the screen and does not alter the display buffer.
"""
@callback display_frame(data :: binary(), opts :: Server.display_frame_opts()) :: :ok

@doc """
Transfer a raw data frame to the screen.
A raw data frame is in a different format than the display buffer.
To transform a display buffer to a raw data frame, `OLED.Display.Impl.SSD1306.translate_buffer/3` can be used.
"""
@callback display_raw_frame(data :: binary(), opts :: Server.display_frame_opts()) :: :ok

@doc """
Clear the buffer.
"""
Expand All @@ -132,6 +152,21 @@ defmodule OLED.Display do
"""
@callback clear(pixel_state :: Server.pixel_state()) :: :ok

@doc """
Override the current buffer which is the internal data structure that is sent to the screen with `c:display/0`.
A possible use-case is to draw some content, get the buffer via `c:get_buffer/0`
and set it again at a later time to save calls to the draw functions.
"""
@callback put_buffer(data :: binary()) :: :ok | {:error, term()}

@doc """
Get the current buffer which is the internal data structure that is changed by the draw methods
and sent to the screen with `c:display/0`.
"""
@callback get_buffer() :: {:ok, binary()}


@doc """
Put a pixel on the buffer. The pixel can be on or off and be drawed in xor mode (if the pixel is already on is turned off).
"""
Expand Down
50 changes: 34 additions & 16 deletions lib/oled/display/impl/ssd_1306.ex
Original file line number Diff line number Diff line change
Expand Up @@ -129,29 +129,21 @@ defmodule OLED.Display.Impl.SSD1306 do

buffer = translate_buffer(buffer, width, opts[:memory_mode])

display_frame(state, buffer, opts)
display_raw_frame(state, buffer, opts)
end

def display(error, _opts),
do: error

defp translate_buffer(buffer, width, :horizontal) do
for <<page::binary-size(width) <- buffer>> do
for(<<b::1 <- page>>, do: b)
|> Enum.chunk_every(width)
|> Enum.zip()
|> Enum.map(fn bits ->
bits
|> Tuple.to_list()
|> Enum.reverse()
|> Enum.into(<<>>, fn bit -> <<bit::1>> end)
end)
end
|> List.flatten()
|> Enum.into(<<>>)
def display_frame(%__MODULE__{width: width} = state, data, opts) do
opts = Keyword.merge(@display_opts, opts)

buffer = translate_buffer(data, width, opts[:memory_mode])

display_raw_frame(state, buffer, opts)
end

def display_frame(%__MODULE__{} = state, data, opts) do
def display_raw_frame(%__MODULE__{} = state, data, opts) do
memory_mode = get_memory_mode(opts[:memory_mode] || :horizontal)

if byte_size(data) == state.width * state.height / 8 do
Expand All @@ -165,6 +157,21 @@ defmodule OLED.Display.Impl.SSD1306 do
end
end

def translate_buffer(buffer, width, :horizontal) do
transformation =
for x <- 0..(width - 1), y <- 0..7 do
(7 - y) * width + x
end

for <<page::binary-size(width) <- buffer>>, into: <<>> do
for source <- transformation, into: <<>> do
rest = width * 8 - source - 1
<<_::size(source)-unit(1), b::1, _::size(rest)-unit(1)>> = page
<<b::1>>
end
end
end

def clear_buffer(%__MODULE__{width: w, height: h} = state, pixel_state)
when pixel_state in [:on, :off] do
value =
Expand All @@ -187,6 +194,17 @@ defmodule OLED.Display.Impl.SSD1306 do
def clear_buffer(error, _pixel_state),
do: error

def put_buffer(%__MODULE__{} = state, data) do
if byte_size(data) == state.width * state.height / 8 do
%{state | buffer: data}
else
{:error, :invalid_data_size}
end
end

def get_buffer(%__MODULE__{buffer: buffer}),
do: {:ok, buffer}

def get_dimensions(%__MODULE__{width: width, height: height}),
do: {:ok, width, height}

Expand Down
31 changes: 31 additions & 0 deletions lib/oled/display/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,22 @@ defmodule OLED.Display.Server do
def display_frame(server, data, opts \\ []),
do: GenServer.call(server, {:display_frame, data, opts})

@doc false
def display_raw_frame(server, data, opts \\ []),
do: GenServer.call(server, {:display_raw_frame, data, opts})

@doc false
def clear(server, pixel_state \\ :off),
do: GenServer.call(server, {:clear, pixel_state})

@doc false
def put_buffer(server, data),
do: GenServer.call(server, {:put_buffer, data})

@doc false
def get_buffer(server),
do: GenServer.call(server, :get_buffer)

@doc false
def put_pixel(server, x, y, opts \\ []),
do: GenServer.call(server, {:put_pixel, x, y, opts})
Expand Down Expand Up @@ -96,12 +108,31 @@ defmodule OLED.Display.Server do
|> handle_response(impl, state)
end

@doc false
def handle_call({:display_raw_frame, data, opts}, _from, {impl, state}) do
state
|> impl.display_raw_frame(data, opts)
|> handle_response(impl, state)
end

def handle_call({:clear, pixel_state}, _from, {impl, state}) do
state
|> impl.clear_buffer(pixel_state)
|> handle_response(impl, state)
end

def handle_call({:put_buffer, data}, _from, {impl, state}) do
state
|> impl.put_buffer(data)
|> handle_response(impl, state)
end

def handle_call(:get_buffer, _from, {impl, state}) do
res = impl.get_buffer(state)

{:reply, res, {impl, state}}
end

def handle_call({:put_pixel, x, y, opts}, _from, {impl, state}) do
state
|> impl.put_pixel(x, y, opts)
Expand Down
105 changes: 103 additions & 2 deletions test/oled/display/impl/ssd_1306_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ defmodule OLED.Display.Impl.SSD1306Test do
end

describe "display_frame/2" do
test "with valid data" do
data =
for v <- 1..8, into: <<>> do
<<v>>
end

state = %SSD1306{
dev: %DummyDev{},
width: 8,
height: 8,
}

assert %SSD1306{} = SSD1306.display_frame(state, data, [])

assert_received {:command, 32}
assert_received {:command, 0}
assert_received {:transfer, <<0, 0, 0, 0, 128, 120, 102, 85>>}
end
end

describe "display_raw_frame/2" do
test "with valid data" do
state = %SSD1306{
dev: %DummyDev{},
Expand All @@ -47,7 +68,7 @@ defmodule OLED.Display.Impl.SSD1306Test do
<<0>>
end

assert %SSD1306{} = SSD1306.display_frame(state, data, memory_mode: :vertical)
assert %SSD1306{} = SSD1306.display_raw_frame(state, data, memory_mode: :vertical)

assert_received {:command, 32}
assert_received {:command, 1}
Expand All @@ -67,7 +88,87 @@ defmodule OLED.Display.Impl.SSD1306Test do
end

assert {:error, :invalid_data_size} =
SSD1306.display_frame(state, data, memory_mode: :vertical)
SSD1306.display_raw_frame(state, data, memory_mode: :vertical)
end
end

describe "put_buffer/1" do
test "with valid data" do
state = %SSD1306{
dev: %DummyDev{},
width: 8,
height: 8,
}

data =
for v <- 1..8, into: <<>> do
<<v>>
end

assert %SSD1306{buffer: buffer} = SSD1306.put_buffer(state, data)

assert buffer = <<0, 0, 0, 0, 128, 120, 102, 85>>
end

test "with invalid data" do
state = %SSD1306{
dev: %DummyDev{},
width: 8,
height: 8
}

data =
for v <- 1..16, into: <<>> do
<<v>>
end

assert {:error, :invalid_data_size} =
SSD1306.put_buffer(state, data)
end
end

describe "get_buffer/0" do
test "with valid data" do
data =
for v <- 1..8, into: <<>> do
<<v>>
end

state = %SSD1306{
dev: %DummyDev{},
width: 8,
height: 8,
buffer: data
}

assert {:ok, buffer} = SSD1306.get_buffer(state)
assert buffer = <<0, 0, 0, 0, 128, 120, 102, 85>>
end
end

describe "translate_buffer/3" do
test "with valid data" do
# Buffer is generated using the following draw functions:
# buffer =
# OLED.BufferTestHelper.build_state(32, 16)
# |> OLED.Display.Impl.SSD1306.Draw.line_h(1, 0, 30, [])
# |> OLED.Display.Impl.SSD1306.Draw.line_h(1, 15, 30, [])
# |> OLED.Display.Impl.SSD1306.Draw.line_v(0, 1, 14, [])
# |> OLED.Display.Impl.SSD1306.Draw.line_v(31, 1, 14, [])
# |> OLED.Display.Impl.SSD1306.Draw.circle(10, 8, 6, [])
# |> OLED.Display.Impl.SSD1306.Draw.circle(21, 8, 6, [])
# |> Map.get(:buffer)

buffer = <<127, 255, 255, 254, 128, 0, 0, 1, 128, 248, 31, 1, 131, 6, 96, 193, 132, 1,
128, 33, 132, 1, 128, 33, 136, 1, 128, 17, 136, 1, 128, 17, 136, 1, 128, 17,
136, 1, 128, 17, 136, 1, 128, 17, 132, 1, 128, 33, 132, 1, 128, 33, 131, 6,
96, 193, 128, 248, 31, 1, 127, 255, 255, 254>>

assert SSD1306.translate_buffer(buffer, 32, :horizontal)
== <<254, 1, 1, 1, 193, 49, 9, 9, 5, 5, 5, 5, 5, 9, 9, 241, 241, 9, 9, 5, 5, 5, 5,
5, 9, 9, 49, 193, 1, 1, 1, 254, 127, 128, 128, 128, 135, 152, 160, 160, 192,
192, 192, 192, 192, 160, 160, 159, 159, 160, 160, 192, 192, 192, 192, 192,
160, 160, 152, 135, 128, 128, 128, 127>>
end
end
end

0 comments on commit f7d0921

Please sign in to comment.