pw_i2c: Add Device class
This object will wrap the initiator along with an address.
Device is a high level wrapper to be used to stream the reading
or writing of arbitrary data to the device.
Testing:
Host test -- OK
Change-Id: I28afd9a956917b28d4db04e8b1be2788981b12c7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/41163
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Pigweed-Auto-Submit: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Kevin Zeng <zengk@google.com>
diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD
index 8831e42..910bff2 100644
--- a/pw_i2c/BUILD
+++ b/pw_i2c/BUILD
@@ -49,6 +49,21 @@
],
)
+pw_cc_library(
+ name = "device",
+ hdrs = [
+ "public/pw_i2c/device.h",
+ ],
+ includes = ["public"],
+ deps = [
+ ":address",
+ ":initiator",
+ "//pw_bytes",
+ "//pw_chrono:system_clock",
+ "//pw_status",
+ ],
+)
+
pw_cc_test(
name = "address_test",
srcs = [
@@ -59,3 +74,14 @@
"//pw_unit_test",
],
)
+
+pw_cc_test(
+ name = "device_test",
+ srcs = [
+ "device_test.cc",
+ ],
+ deps = [
+ ":device",
+ "//pw_unit_test",
+ ],
+)
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index b939587..ed64c41 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -15,6 +15,7 @@
import("//build_overrides/pigweed.gni")
import("$dir_pw_build/target_types.gni")
+import("$dir_pw_chrono/backend.gni")
import("$dir_pw_docgen/docs.gni")
import("$dir_pw_unit_test/test.gni")
@@ -40,8 +41,22 @@
]
}
+pw_source_set("device") {
+ public_configs = [ ":public_include_path" ]
+ public = [ "public/pw_i2c/device.h" ]
+ public_deps = [
+ ":address",
+ ":initiator",
+ "$dir_pw_bytes",
+ "$dir_pw_chrono:system_clock",
+ "$dir_pw_status",
+ ]
+}
pw_test_group("tests") {
- tests = [ ":address_test" ]
+ tests = [
+ ":address_test",
+ ":device_test",
+ ]
}
pw_test("address_test") {
@@ -49,6 +64,12 @@
deps = [ ":address" ]
}
+pw_test("device_test") {
+ enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
+ sources = [ "device_test.cc" ]
+ deps = [ ":device" ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
}
diff --git a/pw_i2c/device_test.cc b/pw_i2c/device_test.cc
new file mode 100644
index 0000000..50a7c81
--- /dev/null
+++ b/pw_i2c/device_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#include "pw_i2c/device.h"
+
+#include "gtest/gtest.h"
+#include "pw_bytes/byte_builder.h"
+
+namespace pw {
+namespace i2c {
+namespace {
+
+// Dummy test initiator that's used for testing.
+class TestInitiator : public Initiator {
+ public:
+ explicit TestInitiator() {}
+
+ private:
+ Status DoWriteReadFor(Address,
+ ConstByteSpan,
+ ByteSpan,
+ chrono::SystemClock::duration) override {
+ // Empty implementation.
+ return OkStatus();
+ }
+
+ ByteBuffer<10> write_buffer_;
+ ByteBuffer<10> read_buffer_;
+};
+
+// This test just checks to make sure the Device object compiles.
+// TODO(b/185609270): Full test coverage.
+TEST(DeviceCompilationTest, CompileOk) {
+ constexpr Address kDummyDeviceAddress = Address::SevenBit<0x3F>();
+
+ TestInitiator initiator;
+ Device device = Device(initiator, kDummyDeviceAddress);
+}
+
+} // namespace
+} // namespace i2c
+} // namespace pw
diff --git a/pw_i2c/docs.rst b/pw_i2c/docs.rst
index a82fb23..5cb2592 100644
--- a/pw_i2c/docs.rst
+++ b/pw_i2c/docs.rst
@@ -18,3 +18,10 @@
The common interface for initiating transactions with devices on an I2C bus.
Other documentation sources may call this style of interface an I2C "master",
"central" or "controller".
+
+pw::i2c::Device
+---------------
+The common interface for interfacing with generic I2C devices. This object
+contains ``pw::i2c::Address`` and wraps the ``pw::i2c::Initiator`` API.
+Common use case includes streaming arbitrary data (Read/Write). Only works
+with devices with a single device address.
diff --git a/pw_i2c/public/pw_i2c/device.h b/pw_i2c/public/pw_i2c/device.h
new file mode 100644
index 0000000..8091fec
--- /dev/null
+++ b/pw_i2c/public/pw_i2c/device.h
@@ -0,0 +1,175 @@
+// Copyright 2021 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+#pragma once
+
+#include "pw_bytes/span.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_i2c/address.h"
+#include "pw_i2c/initiator.h"
+#include "pw_status/status.h"
+
+namespace pw {
+namespace i2c {
+
+// Device is used to write/read arbitrary chunks of data over a bus to a device.
+// This object essentially just wrap the Initiator API with a fixed I2C device
+// address.
+class Device {
+ public:
+ constexpr Device(Initiator& initiator, Address device_address)
+ : initiator_(initiator), device_address_(device_address) {}
+
+ Device(const Device&) = delete;
+ ~Device() = default;
+
+ // Write bytes and then read bytes as either one atomic or two independent I2C
+ // transaction.
+ // The signal on the bus should appear as follows:
+ // 1) Write Only:
+ // START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
+ // 2) Read Only:
+ // START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+ // 3A) Write + Read (atomic):
+ // START + I2C Address + WRITE(0) + TX_BUFFER_BYTES +
+ // START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+ // 3B) Write + Read (separate):
+ // START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
+ // START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+ //
+ // The timeout defines the minimum duration one may block waiting for both
+ // exclusive bus access and the completion of the I2C transaction.
+ //
+ // Preconditions:
+ // The Address must be supported by the Initiator, i.e. do not use a 10
+ // address if the Initiator only supports 7 bit. This will assert.
+ //
+ // Returns:
+ // Ok - Success.
+ // InvalidArgument - device_address is larger than the 10 bit address space.
+ // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+ // and complete the I2C transaction in time.
+ // Unavailable - NACK condition occurred, meaning the addressed device did
+ // not respond or was unable to process the request.
+ // FailedPrecondition - The interface is not currently initialized and/or
+ // enabled.
+ Status WriteReadFor(ConstByteSpan tx_buffer,
+ ByteSpan rx_buffer,
+ chrono::SystemClock::duration for_at_least) {
+ return initiator_.WriteReadFor(
+ device_address_, tx_buffer, rx_buffer, for_at_least);
+ }
+ Status WriteReadFor(const void* tx_buffer,
+ size_t tx_size_bytes,
+ void* rx_buffer,
+ size_t rx_size_bytes,
+ chrono::SystemClock::duration for_at_least) {
+ return initiator_.WriteReadFor(device_address_,
+ tx_buffer,
+ tx_size_bytes,
+ rx_buffer,
+ rx_size_bytes,
+ for_at_least);
+ }
+
+ // Write bytes. The signal on the bus should appear as follows:
+ // START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
+ //
+ // The timeout defines the minimum duration one may block waiting for both
+ // exclusive bus access and the completion of the I2C transaction.
+ //
+ // Preconditions:
+ // The Address must be supported by the Initiator, i.e. do not use a 10
+ // address if the Initiator only supports 7 bit. This will assert.
+ //
+ // Returns:
+ // Ok - Success.
+ // InvalidArgument - device_address is larger than the 10 bit address space.
+ // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+ // and complete the I2C transaction in time.
+ // Unavailable - NACK condition occurred, meaning the addressed device did
+ // not respond or was unable to process the request.
+ // FailedPrecondition - The interface is not currently initialized and/or
+ // enabled.
+ Status WriteFor(ConstByteSpan tx_buffer,
+ chrono::SystemClock::duration for_at_least) {
+ return initiator_.WriteFor(device_address_, tx_buffer, for_at_least);
+ }
+ Status WriteFor(const void* tx_buffer,
+ size_t tx_size_bytes,
+ chrono::SystemClock::duration for_at_least) {
+ return initiator_.WriteFor(
+ device_address_, tx_buffer, tx_size_bytes, for_at_least);
+ }
+
+ // Read bytes. The signal on the bus should appear as follows:
+ // START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
+ //
+ // The timeout defines the minimum duration one may block waiting for both
+ // exclusive bus access and the completion of the I2C transaction.
+ //
+ // Preconditions:
+ // The Address must be supported by the Initiator, i.e. do not use a 10
+ // address if the Initiator only supports 7 bit. This will assert.
+ //
+ // Returns:
+ // Ok - Success.
+ // InvalidArgument - device_address is larger than the 10 bit address space.
+ // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+ // and complete the I2C transaction in time.
+ // Unavailable - NACK condition occurred, meaning the addressed device did
+ // not respond or was unable to process the request.
+ // FailedPrecondition - The interface is not currently initialized and/or
+ // enabled.
+ Status ReadFor(ByteSpan rx_buffer,
+ chrono::SystemClock::duration for_at_least) {
+ return initiator_.ReadFor(device_address_, rx_buffer, for_at_least);
+ }
+ Status ReadFor(void* rx_buffer,
+ size_t rx_size_bytes,
+ chrono::SystemClock::duration for_at_least) {
+ return initiator_.ReadFor(
+ device_address_, rx_buffer, rx_size_bytes, for_at_least);
+ }
+
+ // Probes the device for an I2C ACK after only writing the address.
+ // This is done by attempting to read a single byte from the specified device.
+ //
+ // The timeout defines the minimum duration one may block waiting for both
+ // exclusive bus access and the completion of the I2C transaction.
+ //
+ // Preconditions:
+ // The Address must be supported by the Initiator, i.e. do not use a 10
+ // address if the Initiator only supports 7 bit. This will assert.
+ //
+ // Returns:
+ // Ok - Success.
+ // InvalidArgument - device_address is larger than the 10 bit address space.
+ // DeadlineExceeded - Was unable to acquire exclusive Initiator access
+ // and complete the I2C transaction in time.
+ // Unavailable - NACK condition occurred, meaning the addressed device did
+ // not respond or was unable to process the request.
+ // FailedPrecondition - The interface is not currently initialized and/or
+ // enabled.
+ Status ProbeFor(chrono::SystemClock::duration for_at_least) {
+ return initiator_.ProbeDeviceFor(device_address_, for_at_least);
+ }
+
+ private:
+ Initiator& initiator_;
+ const Address device_address_;
+};
+
+} // namespace i2c
+} // namespace pw