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>
This commit is contained in:
Kevin Zeng 2021-04-16 00:34:17 -07:00 committed by CQ Bot Account
parent a1113be66c
commit 6e15a13e9e
5 changed files with 282 additions and 1 deletions

View File

@ -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",
],
)

View File

@ -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" ]
}

52
pw_i2c/device_test.cc Normal file
View File

@ -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

View File

@ -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.

View File

@ -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