Skip to content

Commit

Permalink
Merge pull request #614 from fadushin/i2c-driver-refactor
Browse files Browse the repository at this point in the history
I2C driver API changes

This change set makes the following breaking changes to the Erlang i2c driver
API

* The return type of i2c functions that return an error has changed to a tuple
including the error atom as well as a mnemonic reason.
* The return type of i2c:read_bytes has changed to a tuple of the form
{ok, Binary} when a binary is successfully read from the device.

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Jul 6, 2023
2 parents 2bf026a + fee6466 commit 31b1035
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 72 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ additional information.
- Changed the return type of the `network:start` function to return the tuple `{ok, Pid}` on a
successful call, instead of the bare atom `ok`. Applications that use `network:start` and
check the return value will need to be modified.
- The return type of `i2c:read_bytes` has changed from returning just a binary to
returning the tuple `{ok, Binary}` when successful.
- The return type of many `i2c` operations under error conditions has changed from
`error` to `{error, Reason}`, for improved diagnostics.

### Removed
- ESP-IDF v3.x support.
Expand Down
2 changes: 1 addition & 1 deletion doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ In other cases, you may just need to write a byte or sequence of bytes in one op
Reading bytes is more straightforward. Simply use `i2c:read_bytes/3,4`, specifying the port instance, device address, optionally a register, and the number of bytes to read:

%% erlang
BinaryData = i2c:read_bytes(I2C, DeviceAddress, Register, Len)
{ok, BinaryData} = i2c:read_bytes(I2C, DeviceAddress, Register, Len)

To close the I2C driver and free any resources in use by it, use the `i2c:close/1` function, supplying a reference to the I2C driver instance created via `i2c:open/1`:

Expand Down
2 changes: 1 addition & 1 deletion examples/elixir/esp32/SHT31.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule SHT31 do
end

defp read(i2c) do
bin = read_sensor(i2c)
{:ok, bin} = read_sensor(i2c)
parse_bin(bin)
end

Expand Down
2 changes: 1 addition & 1 deletion examples/erlang/esp32/sht31.erl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ loop(I2C) ->
loop(I2C).

read(I2C) ->
Bin = read_sensor(I2C),
{ok, Bin} = read_sensor(I2C),
parse_bin(Bin).

parse_bin(B) ->
Expand Down
33 changes: 17 additions & 16 deletions libs/eavmlib/src/i2c.erl
Original file line number Diff line number Diff line change
Expand Up @@ -78,72 +78,72 @@ close(I2C) ->
%%-----------------------------------------------------------------------------
%% @param I2C I2C instance created via `open/1'
%% @param Address I2C Address of the device (typically fixed for the device type)
%% @returns `ok' or `error'
%% @returns `ok' or `{error, Reason}'
%% @doc Begin a transmission of I2C commands
%%
%% This command is typically followed by one or more calls to
%% `write_byte/2' and then a call to `end_transmission/1'
%% @end
%%-----------------------------------------------------------------------------
-spec begin_transmission(I2C :: i2c(), Address :: address()) -> ok | error.
-spec begin_transmission(I2C :: i2c(), Address :: address()) -> ok | {error, Reason :: term()}.
begin_transmission(I2C, Address) ->
port:call(I2C, {begin_transmission, Address}).

%%-----------------------------------------------------------------------------
%% @param I2C I2C instance created via `open/1'
%% @param Byte value to write
%% @returns `ok' or `error'
%% @returns `ok' or `{error, Reason}'
%% @doc Write a byte to the device.
%%
%% This command must be wrapped in a `begin_transmission/2'
%% and `end_transmission/1' call.
%% @end
%%-----------------------------------------------------------------------------
-spec write_byte(I2C :: i2c(), Byte :: byte()) -> ok | error.
-spec write_byte(I2C :: i2c(), Byte :: byte()) -> ok | {error, Reason :: term()}.
write_byte(I2C, Byte) ->
port:call(I2C, {write_byte, Byte}).

%%-----------------------------------------------------------------------------
%% @param I2C I2C instance created via `open/1'
%% @param Bytes value to write
%% @returns `ok' or `error'
%% @returns `ok' or `{error, Reason}'
%% @doc Write a sequence of bytes to the device.
%%
%% This command must be wrapped in a `begin_transmission/2'
%% and `end_transmission/1' call.
%% @end
%%-----------------------------------------------------------------------------
-spec write_bytes(I2C :: i2c(), Bytes :: binary()) -> ok | error.
-spec write_bytes(I2C :: i2c(), Bytes :: binary()) -> ok | {error, Reason :: term()}.
write_bytes(I2C, Bytes) ->
port:call(I2C, {write_bytes, Bytes}).

%%-----------------------------------------------------------------------------
%% @param I2C I2C instance created via `open/1'
%% @param Address I2C Address of the device (typically fixed for the device type)
%% @returns `ok' or `error'
%% @returns `ok' or `{error, Reason}'
%% @doc End a transmission of I2C commands
%%
%% This command is typically preceded by a call to `begin_transmission/2'
%% and one or more calls to `write_byte/2'.
%% @end
%%-----------------------------------------------------------------------------
-spec end_transmission(I2C :: i2c()) -> ok | error.
-spec end_transmission(I2C :: i2c()) -> ok | {error, Reason :: term()}.
end_transmission(I2C) ->
port:call(I2C, {end_transmission}).

%%-----------------------------------------------------------------------------
%% @param I2C I2C instance created via `open/1'
%% @param Address I2C Address of the device (typically fixed for the device type)
%% @param Count The number of bytes to read
%% @returns the read binary data or `error'
%% @returns `{ok, Data}' which includes the read binary data or `{error, Reason}'
%% @doc Read a block of bytes from the I2C device.
%%
%% This command is not wrapped in a `begin_transmission/2'
%% and `end_transmission/1' call.
%% @end
%%-----------------------------------------------------------------------------
-spec read_bytes(I2C :: i2c(), Address :: address(), Count :: non_neg_integer()) ->
error | binary().
{ok, Data :: binary()} | {error, Reason :: term()}.
read_bytes(I2C, Address, Count) ->
port:call(I2C, {read_bytes, Address, Count}).

Expand All @@ -152,7 +152,7 @@ read_bytes(I2C, Address, Count) ->
%% @param Address I2C Address of the device (typically fixed for the device type)
%% @param Register The register address in the device from which to read data
%% @param Count The number of bytes to read
%% @returns the read binary data or `error'
%% @returns `{ok, Data}' which includes the read binary data or `{error, Reason}'
%% @doc Read a block of bytes from the I2C device starting at a specified
%% register address
%%
Expand All @@ -162,22 +162,23 @@ read_bytes(I2C, Address, Count) ->
%%-----------------------------------------------------------------------------
-spec read_bytes(
I2C :: i2c(), Address :: address(), Register :: register(), Count :: non_neg_integer()
) -> error | binary().
) -> {ok, binary()} | {error, Reason :: term()}.
read_bytes(I2C, Address, Register, Count) ->
port:call(I2C, {read_bytes, Address, Count, Register}).

%%-----------------------------------------------------------------------------
%% @param I2C I2C instance created via `open/1'
%% @param Address I2C Address of the device (typically fixed for the device type)
%% @param BinOrInt The binary or byte value to write
%% @returns `ok' or `error'
%% @returns `ok' or `{error, Reason}'
%% @doc Write a block of bytes to the I2C device.
%%
%% This command is not wrapped in a `begin_transmission/2'
%% and `end_transmission/1' call.
%% @end
%%-----------------------------------------------------------------------------
-spec write_bytes(I2C :: i2c(), Address :: address(), BinOrInt :: binary() | byte()) -> ok | error.
-spec write_bytes(I2C :: i2c(), Address :: address(), BinOrInt :: binary() | byte()) ->
ok | {error, Reason :: term()}.
write_bytes(I2C, Address, BinOrInt) ->
port:call(I2C, {write_bytes, Address, BinOrInt}).

Expand All @@ -186,7 +187,7 @@ write_bytes(I2C, Address, BinOrInt) ->
%% @param Address I2C Address of the device (typically fixed for the device type)
%% @param Register The register address in the device to which to write data
%% @param BinOrInt The binary or byte value to write
%% @returns `ok' or `error'
%% @returns `ok' or `{error, Reason}'
%% @doc Write a block of bytes to the I2C device starting at a specified
%% register address.
%%
Expand All @@ -196,6 +197,6 @@ write_bytes(I2C, Address, BinOrInt) ->
%%-----------------------------------------------------------------------------
-spec write_bytes(
I2C :: i2c(), Address :: address(), Register :: register(), BinOrInt :: binary() | integer()
) -> ok | error.
) -> ok | {error, Reason :: term()}.
write_bytes(I2C, Address, Register, BinOrInt) ->
port:call(I2C, {write_bytes, Address, BinOrInt, Register}).
19 changes: 10 additions & 9 deletions libs/exavmlib/lib/I2C.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ defmodule I2C do
This command is typically followed by one or more calls to
write_byte/2 and then a call to end_transmission/1.
"""
@spec begin_transmission(pid(), address()) :: :ok | :error
@spec begin_transmission(pid(), address()) :: :ok | {:error, term()}
def begin_transmission(driver, device) do
AVMPort.call(driver, {:begin_transmission, device})
end
Expand All @@ -101,7 +101,7 @@ defmodule I2C do
This command must be wrapped in a begin_transmission/2
and end_transmission/1 call.
"""
@spec write_byte(pid(), byte()) :: :ok | :error
@spec write_byte(pid(), byte()) :: :ok | {:error, term()}
def write_byte(driver, data) do
AVMPort.call(driver, {:write_byte, data})
end
Expand All @@ -116,7 +116,7 @@ defmodule I2C do
This command must be wrapped in a begin_transmission/2
and end_transmission/1 call.
"""
@spec write_bytes(pid(), binary()) :: :ok | :error
@spec write_bytes(pid(), binary()) :: :ok | {:error, term()}
def write_bytes(driver, data) do
AVMPort.call(driver, {:write_bytes, data})
end
Expand All @@ -130,7 +130,7 @@ defmodule I2C do
This command is typically preceded by a call to begin_transmission/2
and one or more calls to write_byte/2 or write_bytes/2.
"""
@spec end_transmission(pid()) :: :ok | :error
@spec end_transmission(pid()) :: :ok | {:error, term()}
def end_transmission(driver) do
AVMPort.call(driver, {:end_transmission})
end
Expand All @@ -146,7 +146,7 @@ defmodule I2C do
This command is not wrapped in a begin_transmission/2
and end_transmission/1 call.
"""
@spec read_bytes(pid(), address(), non_neg_integer()) :: :error | binary()
@spec read_bytes(pid(), address(), non_neg_integer()) :: {:ok, binary()} | {:error, term()}
def read_bytes(driver, addr, count) do
AVMPort.call(driver, {:read_bytes, addr, count})
end
Expand All @@ -164,7 +164,8 @@ defmodule I2C do
This command is not wrapped in a begin_transmission/2
and end_transmission/1 call.
"""
@spec read_bytes(pid(), address(), register(), non_neg_integer()) :: :error | binary()
@spec read_bytes(pid(), address(), register(), non_neg_integer()) ::
{:ok, binary()} | {:error, term()}
def read_bytes(device, addr, reg, count) do
AVMPort.call(device, {:read_bytes, addr, count, reg})
end
Expand All @@ -180,7 +181,7 @@ defmodule I2C do
This command is not wrapped in a begin_transmission/2
and end_transmission/1 call.
"""
@spec write_bytes(pid(), address(), binary() | byte()) :: :ok | :error
@spec write_bytes(pid(), address(), binary() | byte()) :: :ok | {:error, term()}
def write_bytes(driver, addr, data) do
AVMPort.call(driver, {:write_bytes, addr, data})
end
Expand All @@ -198,7 +199,7 @@ defmodule I2C do
This command is not wrapped in a begin_transmission/2
and end_transmission/1 call.
"""
@spec write_bytes(pid(), address(), register(), binary() | integer()) :: :ok | :error
@spec write_bytes(pid(), address(), register(), binary() | integer()) :: :ok | {:error, term()}
def write_bytes(driver, addr, reg, data) do
AVMPort.call(driver, {:write_bytes, addr, data, reg})
end
Expand All @@ -212,7 +213,7 @@ defmodule I2C do
This function will close the connection to the I2C driver and
free any resources in use by it.
"""
@spec close(pid()) :: :ok | :error
@spec close(pid()) :: :ok | {:error, term()}
def close(driver) do
AVMPort.call(driver, {:close})
end
Expand Down
Loading

0 comments on commit 31b1035

Please sign in to comment.