diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD index 8831e4275..910bff28a 100644 --- a/pw_i2c/BUILD +++ b/pw_i2c/BUILD @@ -49,6 +49,21 @@ pw_cc_library( ], ) +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_cc_test( "//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 b93958770..ed64c41a7 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("initiator") { ] } +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 @@ pw_test("address_test") { 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 000000000..50a7c816f --- /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 a82fb2340..5cb259209 100644 --- a/pw_i2c/docs.rst +++ b/pw_i2c/docs.rst @@ -18,3 +18,10 @@ pw::i2c::Initiator 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 000000000..8091fec58 --- /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