Skip to content

Commit 23dc088

Browse files
committed
Support setting I2C transaction timeouts
1 parent 650a7b4 commit 23dc088

File tree

6 files changed

+88
-47
lines changed

6 files changed

+88
-47
lines changed

c_src/i2c_nif.c

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ int test_close(int fd)
3838
else
3939
return -1;
4040
}
41-
int test_ioctl(int fd, unsigned long request, void *arg)
41+
int test_ioctl(int fd, unsigned long request, ...)
4242
{
4343
static int flaky = 0;
44+
va_list ap;
4445

4546
// The flaky I2C bus (0x30) works only on a retry. Reading anything besides 0x30 resets the counter.
4647
if (fd != 0x30)
@@ -50,11 +51,15 @@ int test_ioctl(int fd, unsigned long request, void *arg)
5051
return -1;
5152

5253
if (request == I2C_FUNCS) {
53-
unsigned long *funcs = (unsigned long *) arg;
54+
va_start(ap, request);
55+
unsigned long *funcs = va_arg(ap, unsigned long *);
56+
va_end(ap);
5457
*funcs = 0;
5558
return 0;
5659
} else if (request == I2C_RDWR) {
57-
struct i2c_rdwr_ioctl_data *data = (struct i2c_rdwr_ioctl_data *) arg;
60+
va_start(ap, request);
61+
struct i2c_rdwr_ioctl_data *data = va_arg(ap, struct i2c_rdwr_ioctl_data *);
62+
va_end(ap);
5863
if (fd == 0x30) {
5964
// Flake out if first read or write
6065
if (flaky == 0) {
@@ -76,6 +81,9 @@ int test_ioctl(int fd, unsigned long request, void *arg)
7681
}
7782
}
7883
return data->nmsgs;
84+
} else if (request == I2C_TIMEOUT) {
85+
// Ignore calls to set the timeout.
86+
return 0;
7987
} else {
8088
// Unknown ioctl
8189
return -1;
@@ -226,8 +234,10 @@ static ERL_NIF_TERM i2c_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
226234
{
227235
struct I2cNifPriv *priv = enif_priv_data(env);
228236
ErlNifBinary path;
237+
int timeout_ms;
229238

230-
if (!enif_inspect_binary(env, argv[0], &path))
239+
if (!enif_inspect_binary(env, argv[0], &path) ||
240+
!enif_get_int(env, argv[1], &timeout_ms))
231241
return enif_make_badarg(env);
232242

233243
char devpath[32];
@@ -244,6 +254,17 @@ static ERL_NIF_TERM i2c_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
244254
return enif_make_errno_error(env);
245255
}
246256

257+
// Change the I2C timeout
258+
if (timeout_ms >= 0) {
259+
int timeout_cs = (timeout_ms + 5) / 10;
260+
if (timeout_cs == 0)
261+
timeout_cs = 1;
262+
if (do_ioctl(fd, I2C_TIMEOUT, timeout_cs) < 0) {
263+
close(fd);
264+
return enif_make_errno_error(env);
265+
}
266+
}
267+
247268
struct I2cNifRes *i2c_nif_res = enif_alloc_resource(priv->i2c_nif_res_type, sizeof(struct I2cNifRes));
248269
i2c_nif_res->fd = fd;
249270
ERL_NIF_TERM res_term = enif_make_resource(env, i2c_nif_res);
@@ -411,7 +432,7 @@ static ERL_NIF_TERM i2c_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
411432

412433
static ErlNifFunc nif_funcs[] =
413434
{
414-
{"open", 1, i2c_open, ERL_NIF_DIRTY_JOB_IO_BOUND},
435+
{"open", 2, i2c_open, ERL_NIF_DIRTY_JOB_IO_BOUND},
415436
{"read", 4, i2c_read, ERL_NIF_DIRTY_JOB_IO_BOUND},
416437
{"write", 4, i2c_write, ERL_NIF_DIRTY_JOB_IO_BOUND},
417438
{"write_read", 5, i2c_write_read, ERL_NIF_DIRTY_JOB_IO_BOUND},

lib/i2c.ex

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,30 @@ defmodule Circuits.I2C do
3737
"""
3838
@type backend() :: {module(), keyword()}
3939

40-
@type opt() :: {:retries, non_neg_integer()}
40+
@typedoc """
41+
I2C open options
42+
43+
See I2C backend documentation, device driver caveats, and function calls in this
44+
module for notes. In general,
45+
46+
* `:retries` - the number of times to retry a transaction. I.e. 2 retries means
47+
the transaction is attempted at most 3 times. Defaults to 0 retries.
48+
* `:timeout` - the time in milliseconds to wait for a transaction to complete.
49+
Any value <0 means to use the device driver or hardware default. The default
50+
value is -1 to avoid setting it, but it is very common for the default to be 1000 ms.
51+
"""
52+
@type open_options() :: keyword()
53+
54+
@typedoc """
55+
I2C transfer options
56+
57+
See I2C backend documentation, device driver caveats, and function calls in this
58+
module for notes. In general,
59+
60+
* `:retries` - override the number of times to retry a transaction from what was
61+
passed to `open/3`.
62+
"""
63+
@type transfer_options() :: keyword()
4164

4265
@typedoc """
4366
Connection to a real or virtual I2C controller
@@ -79,10 +102,12 @@ defmodule Circuits.I2C do
79102
Options depend on the backend. The following are for the I2CDev (default)
80103
backend:
81104
82-
* `:retries` - the default number of retries to automatically do on reads
83-
and writes (defaults to no retries)
105+
* `:retries` - the number of times to retry a transaction. I.e. 2 retries means
106+
the transaction is attempted at most 3 times. Defaults to 0 retries.
107+
* `:timeout` - the time in milliseconds to wait for a transaction to complete
108+
or <0 to avoid setting it. Defaults to -1.
84109
"""
85-
@spec open(String.t(), keyword()) :: {:ok, Bus.t()} | {:error, term()}
110+
@spec open(String.t(), open_options()) :: {:ok, Bus.t()} | {:error, term()}
86111
def open(bus_name, options \\ []) when is_binary(bus_name) do
87112
{module, default_options} = default_backend()
88113
module.open(bus_name, Keyword.merge(default_options, options))
@@ -91,43 +116,38 @@ defmodule Circuits.I2C do
91116
@doc """
92117
Initiate a read transaction to the I2C device at the specified `address`
93118
94-
Options:
95-
96-
* `:retries` - number of retries before failing (defaults to no retries)
119+
See `t:transfer_options/0` for options, but backend may provide more.
97120
"""
98-
@spec read(Bus.t(), address(), pos_integer(), [opt()]) :: {:ok, binary()} | {:error, term()}
121+
@spec read(Bus.t(), address(), pos_integer(), transfer_options()) ::
122+
{:ok, binary()} | {:error, term()}
99123
def read(bus, address, bytes_to_read, opts \\ []) do
100124
Bus.read(bus, address, bytes_to_read, opts)
101125
end
102126

103127
@doc """
104128
Initiate a read transaction and raise on error
105129
"""
106-
@spec read!(Bus.t(), address(), pos_integer(), [opt()]) :: binary()
130+
@spec read!(Bus.t(), address(), pos_integer(), transfer_options()) :: binary()
107131
def read!(bus, address, bytes_to_read, opts \\ []) do
108132
unwrap_or_raise(read(bus, address, bytes_to_read, opts))
109133
end
110134

111135
@doc """
112136
Write `data` to the I2C device at `address`.
113137
114-
Options:
115-
116-
* `:retries` - number of retries before failing (defaults to no retries)
138+
See `t:transfer_options/0` for options, but backend may provide more.
117139
"""
118-
@spec write(Bus.t(), address(), iodata(), [opt()]) :: :ok | {:error, term()}
140+
@spec write(Bus.t(), address(), iodata(), transfer_options()) :: :ok | {:error, term()}
119141
def write(bus, address, data, opts \\ []) do
120142
Bus.write(bus, address, data, opts)
121143
end
122144

123145
@doc """
124146
Write `data` to the I2C device at `address` and raise on error
125147
126-
Options:
127-
128-
* `:retries` - number of retries before failing (defaults to no retries)
148+
See `t:transfer_options/0` for options, but backend may provide more.
129149
"""
130-
@spec write!(Bus.t(), address(), iodata(), [opt()]) :: :ok
150+
@spec write!(Bus.t(), address(), iodata(), transfer_options()) :: :ok
131151
def write!(bus, address, data, opts \\ []) do
132152
unwrap_or_raise_ok(write(bus, address, data, opts))
133153
end
@@ -143,11 +163,9 @@ defmodule Circuits.I2C do
143163
transaction will be issued that way. On the Raspberry Pi, this can be enabled
144164
globally with `File.write!("/sys/module/i2c_bcm2708/parameters/combined", "1")`
145165
146-
Options:
147-
148-
* `:retries` - number of retries before failing (defaults to no retries)
166+
See `t:transfer_options/0` for options, but backend may provide more.
149167
"""
150-
@spec write_read(Bus.t(), address(), iodata(), pos_integer(), [opt()]) ::
168+
@spec write_read(Bus.t(), address(), iodata(), pos_integer(), transfer_options()) ::
151169
{:ok, binary()} | {:error, term()}
152170
def write_read(bus, address, write_data, bytes_to_read, opts \\ []) do
153171
Bus.write_read(bus, address, write_data, bytes_to_read, opts)
@@ -156,11 +174,9 @@ defmodule Circuits.I2C do
156174
@doc """
157175
Write `data` to an I2C device and then immediately issue a read. Raise on errors.
158176
159-
Options:
160-
161-
* `:retries` - number of retries before failing (defaults to no retries)
177+
See `t:transfer_options/0` for options, but backend may provide more.
162178
"""
163-
@spec write_read!(Bus.t(), address(), iodata(), pos_integer(), [opt()]) :: binary()
179+
@spec write_read!(Bus.t(), address(), iodata(), pos_integer(), transfer_options()) :: binary()
164180
def write_read!(bus, address, write_data, bytes_to_read, opts \\ []) do
165181
unwrap_or_raise(write_read(bus, address, write_data, bytes_to_read, opts))
166182
end

lib/i2c/backend.ex

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ defmodule Circuits.I2C.Backend do
1414
Support for options is backend-specific. Backends are encouraged to
1515
implement the following:
1616
17-
* `:retries` - a number of times to attempt to retry the transaction
18-
before failing
17+
* `:retries` - the number of retries for this transaction
1918
"""
20-
@type options() :: keyword()
19+
@type transfer_options() :: keyword()
2120

2221
@doc """
2322
Return the I2C bus names on this system
2423
25-
No supported options
24+
See backend documentation for options.
2625
"""
2726
@callback bus_names(options :: keyword()) :: [binary()]
2827

@@ -32,10 +31,7 @@ defmodule Circuits.I2C.Backend do
3231
Bus names are typically of the form `"i2c-n"` and available buses may be
3332
found by calling `bus_names/1`.
3433
35-
Options:
36-
37-
* `:retries` - Specify a nonnegative integer for how many times to retry
38-
a failed I2C operation.
34+
See `t:Circuits.I2C.open_options/0` for guidance on options.
3935
"""
4036
@callback open(bus_name :: String.t(), options :: keyword()) ::
4137
{:ok, Bus.t()} | {:error, term()}

lib/i2c/i2c_dev.ex

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ defmodule Circuits.I2C.I2CDev do
4848
4949
Options:
5050
51-
* `:retries` - Specify a nonnegative integer for how many times to retry
52-
a failed I2C operation.
51+
* `:retries` - the number of times to retry a transaction. I.e. 2 retries means
52+
the transaction is attempted at most 3 times. Defaults to 0 retries.
53+
* `:timeout` - the time in milliseconds to wait for a transaction to complete.
54+
Any value <0 means to use the device driver default which is probably 1000 ms.
5355
"""
5456
@impl Backend
5557
def open(bus_name, options) do
@@ -59,7 +61,13 @@ defmodule Circuits.I2C.I2CDev do
5961
raise ArgumentError, "retries must be a non-negative integer"
6062
end
6163

62-
with {:ok, ref, flags} <- Nif.open(bus_name) do
64+
timeout = Keyword.get(options, :timeout, -1)
65+
66+
if not is_integer(timeout) do
67+
raise ArgumentError, "timeout must be an integer"
68+
end
69+
70+
with {:ok, ref, flags} <- Nif.open(bus_name, timeout) do
6371
{:ok, %__MODULE__{ref: ref, flags: flags, retries: retries}}
6472
end
6573
end

lib/i2c/i2c_nif.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defmodule Circuits.I2C.Nif do
1212
:erlang.load_nif(:code.priv_dir(:circuits_i2c) ++ ~c"/i2c_nif", 0)
1313
end
1414

15-
def open(_device), do: :erlang.nif_error(:nif_not_loaded)
15+
def open(_device, _timeout), do: :erlang.nif_error(:nif_not_loaded)
1616
def read(_ref, _address, _count, _retries), do: :erlang.nif_error(:nif_not_loaded)
1717
def write(_ref, _address, _data, _retries), do: :erlang.nif_error(:nif_not_loaded)
1818

test/i2c/i2c_nif_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ defmodule Circuits.I2CNifTest do
1818

1919
describe "open/1" do
2020
test "i2c-test-0 and i2c-test-1 work" do
21-
{:ok, i2c, flags} = Nif.open("i2c-test-0")
21+
{:ok, i2c, flags} = Nif.open("i2c-test-0", -1)
2222
Nif.close(i2c)
2323
assert flags == []
2424

25-
{:ok, i2c, flags} = Nif.open("i2c-test-1")
25+
{:ok, i2c, flags} = Nif.open("i2c-test-1", -1)
2626
Nif.close(i2c)
2727
assert flags == []
2828

29-
assert {:error, _} = Nif.open("i2c-2")
29+
assert {:error, _} = Nif.open("i2c-2", -1)
3030
end
3131
end
3232

@@ -37,7 +37,7 @@ defmodule Circuits.I2CNifTest do
3737
assert {:module, Circuits.I2C.Nif} == :code.ensure_loaded(Circuits.I2C.Nif)
3838

3939
# Try running something to verify that it works.
40-
{:ok, i2c, _} = Nif.open("i2c-test-0")
40+
{:ok, i2c, _} = Nif.open("i2c-test-0", -1)
4141
assert is_reference(i2c)
4242
Nif.close(i2c)
4343

@@ -75,7 +75,7 @@ defmodule Circuits.I2CNifTest do
7575
assert {:module, Circuits.I2C.Nif} == :code.ensure_loaded(Circuits.I2C.Nif)
7676

7777
# Try running something to verify that it works.
78-
{:ok, i2c, _} = Nif.open("i2c-test-0")
78+
{:ok, i2c, _} = Nif.open("i2c-test-0", -1)
7979
assert is_reference(i2c)
8080
Nif.close(i2c)
8181

0 commit comments

Comments
 (0)