• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <cstdint>
17 
18 #include "pw_bytes/span.h"
19 #include "pw_chrono/system_clock.h"
20 #include "pw_i2c/address.h"
21 #include "pw_status/status.h"
22 
23 namespace pw::i2c {
24 
25 /// @brief The common, base driver interface for initiating thread-safe
26 /// transactions with devices on an I2C bus. Other documentation may call this
27 /// style of interface an I2C "master", <!-- inclusive-language: disable -->
28 /// "central", or "controller".
29 ///
30 /// `pw::i2c::Initiator` isn't required to support 10-bit addressing. If only
31 /// 7-bit addressing is supported, `pw::i2c::Initiator` fails a runtime
32 /// assertion when given an address that is out of 7-bit address range.
33 ///
34 /// The implementer of this pure virtual interface is responsible for ensuring
35 /// thread safety and enabling functionality such as initialization,
36 /// configuration, enabling and disabling, unsticking SDA, and detecting device
37 /// address registration collisions.
38 ///
39 /// @note `pw::i2c::Initiator` uses internal synchronization, so it's safe to
40 /// initiate transactions from multiple threads. However, a combined write and
41 /// read transaction may not be atomic when there are multiple initiators on
42 /// the bus. Furthermore, devices may require specific sequences of
43 /// transactions, and application logic must provide the synchronization to
44 /// execute these sequences correctly.
45 class Initiator {
46  public:
47   virtual ~Initiator() = default;
48 
49   /// Writes bytes to an I2C device and then reads bytes from that same
50   /// device as either one atomic I2C transaction or two independent I2C
51   /// transactions.
52   ///
53   /// If the I2C bus is a multi-initiator then the implementer of this
54   /// class **must** ensure it's a single-atomic transaction.
55   ///
56   /// The signal on the bus for the atomic transaction should look like this:
57   ///
58   /// @code
59   ///   START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES +
60   ///   START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
61   /// @endcode
62   ///
63   /// The signal on the bus for the two independent transactions should look
64   /// like this:
65   ///
66   /// @code
67   ///   START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
68   ///   START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
69   /// @endcode
70   ///
71   /// @param[in] device_address The address of the I2C device.
72   ///
73   /// @param[in] tx_buffer The transmit buffer.
74   ///
75   /// @param[out] rx_buffer The receive buffer.
76   ///
77   /// @param[in] timeout The maximum duration to block waiting for both
78   /// exclusive bus access and the completion of the I2C transaction or
79   /// transactions.
80   ///
81   /// @pre The provided address must be supported by the initiator. I.e.
82   /// don't use a 10-bit address if the initiator only supports 7-bit
83   /// addresses. This method fails a runtime assertion if this precondition
84   /// isn't met.
85   ///
86   /// @returns @rst
87   ///
88   /// .. pw-status-codes::
89   ///
90   ///    OK: The transaction or transactions succeeded.
91   ///
92   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
93   ///
94   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
95   ///    and complete the I2C transaction in time.
96   ///
97   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
98   ///    didn't respond or was unable to process the request.
99   ///
100   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
101   ///
102   /// @endrst
WriteReadFor(Address device_address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)103   Status WriteReadFor(Address device_address,
104                       ConstByteSpan tx_buffer,
105                       ByteSpan rx_buffer,
106                       chrono::SystemClock::duration timeout) {
107     return DoWriteReadFor(device_address, tx_buffer, rx_buffer, timeout);
108   }
109 
110   /// A variation of `pw::i2c::Initiator::WriteReadFor` that accepts explicit
111   /// sizes for the amount of bytes to write to the device and read from the
112   /// device.
WriteReadFor(Address device_address,const void * tx_buffer,size_t tx_size_bytes,void * rx_buffer,size_t rx_size_bytes,chrono::SystemClock::duration timeout)113   Status WriteReadFor(Address device_address,
114                       const void* tx_buffer,
115                       size_t tx_size_bytes,
116                       void* rx_buffer,
117                       size_t rx_size_bytes,
118                       chrono::SystemClock::duration timeout) {
119     return WriteReadFor(
120         device_address,
121         span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
122         span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
123         timeout);
124   }
125 
126   /// Write bytes to the I2C device.
127   ///
128   /// The signal on the bus should look like this:
129   ///
130   /// @code
131   ///   START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
132   /// @endcode
133   ///
134   /// @param[in] device_address The address of the I2C device.
135   ///
136   /// @param[in] tx_buffer The transmit buffer.
137   ///
138   /// @param[out] rx_buffer The receive buffer.
139   ///
140   /// @param[in] timeout The maximum duration to block waiting for both
141   /// exclusive bus access and the completion of the I2C transaction.
142   ///
143   /// @pre The provided address must be supported by the initiator. I.e.
144   /// don't use a 10-bit address if the initiator only supports 7-bit
145   /// addresses. This method fails a runtime assertion if this precondition
146   /// isn't met.
147   ///
148   /// @returns @rst
149   ///
150   /// .. pw-status-codes::
151   ///
152   ///    OK: The transaction succeeded.
153   ///
154   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
155   ///
156   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
157   ///    and complete the I2C transaction in time.
158   ///
159   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
160   ///    didn't respond or was unable to process the request.
161   ///
162   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
163   ///
164   /// @endrst
WriteFor(Address device_address,ConstByteSpan tx_buffer,chrono::SystemClock::duration timeout)165   Status WriteFor(Address device_address,
166                   ConstByteSpan tx_buffer,
167                   chrono::SystemClock::duration timeout) {
168     return WriteReadFor(device_address, tx_buffer, ByteSpan(), timeout);
169   }
170   /// A variation of `pw::i2c::Initiator::WriteFor` that accepts an explicit
171   /// size for the amount of bytes to write to the device.
WriteFor(Address device_address,const void * tx_buffer,size_t tx_size_bytes,chrono::SystemClock::duration timeout)172   Status WriteFor(Address device_address,
173                   const void* tx_buffer,
174                   size_t tx_size_bytes,
175                   chrono::SystemClock::duration timeout) {
176     return WriteFor(
177         device_address,
178         span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
179         timeout);
180   }
181 
182   /// Reads bytes from an I2C device.
183   ///
184   /// The signal on the bus should look like this:
185   ///
186   /// @code
187   ///   START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
188   /// @endcode
189   ///
190   /// @param[in] device_address The address of the I2C device.
191   ///
192   /// @param[out] rx_buffer The receive buffer.
193   ///
194   /// @param[in] timeout The maximum duration to block waiting for both
195   /// exclusive bus access and the completion of the I2C transaction.
196   ///
197   /// @pre The provided address must be supported by the initiator. I.e.
198   /// don't use a 10-bit address if the initiator only supports 7-bit
199   /// addresses. This method fails a runtime assertion if this precondition
200   /// isn't met.
201   ///
202   /// @returns @rst
203   ///
204   /// .. pw-status-codes::
205   ///
206   ///    OK: The transaction succeeded.
207   ///
208   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
209   ///
210   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
211   ///    and complete the I2C transaction in time.
212   ///
213   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
214   ///    didn't respond or was unable to process the request.
215   ///
216   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
217   ///
218   /// @endrst
ReadFor(Address device_address,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)219   Status ReadFor(Address device_address,
220                  ByteSpan rx_buffer,
221                  chrono::SystemClock::duration timeout) {
222     return WriteReadFor(device_address, ConstByteSpan(), rx_buffer, timeout);
223   }
224   /// A variation of `pw::i2c::Initiator::ReadFor` that accepts an explicit
225   /// size for the amount of bytes to read from the device.
ReadFor(Address device_address,void * rx_buffer,size_t rx_size_bytes,chrono::SystemClock::duration timeout)226   Status ReadFor(Address device_address,
227                  void* rx_buffer,
228                  size_t rx_size_bytes,
229                  chrono::SystemClock::duration timeout) {
230     return ReadFor(device_address,
231                    span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
232                    timeout);
233   }
234 
235   /// Probes the device for an I2C ACK after only writing the address.
236   /// This is done by attempting to read a single byte from the specified
237   /// device.
238   ///
239   /// @param[in] device_address The address of the I2C device.
240   ///
241   /// @param[in] timeout The maximum duration to block waiting for both
242   /// exclusive bus access and the completion of the I2C transaction.
243   ///
244   /// @pre The provided address must be supported by the initiator. I.e.
245   /// don't use a 10-bit address if the initiator only supports 7-bit
246   /// addresses. This method fails a runtime assertion if this precondition
247   /// isn't met.
248   ///
249   /// @returns @rst
250   ///
251   /// .. pw-status-codes::
252   ///
253   ///    OK: The transaction succeeded.
254   ///
255   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
256   ///
257   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
258   ///    and complete the I2C transaction in time.
259   ///
260   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
261   ///    didn't respond or was unable to process the request.
262   ///
263   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
264   ///
265   /// @endrst
ProbeDeviceFor(Address device_address,chrono::SystemClock::duration timeout)266   Status ProbeDeviceFor(Address device_address,
267                         chrono::SystemClock::duration timeout) {
268     std::byte ignored_buffer[1] = {};  // Read a byte to probe.
269     return WriteReadFor(
270         device_address, ConstByteSpan(), ignored_buffer, timeout);
271   }
272 
273  private:
274   virtual Status DoWriteReadFor(Address device_address,
275                                 ConstByteSpan tx_buffer,
276                                 ByteSpan rx_buffer,
277                                 chrono::SystemClock::duration timeout) = 0;
278 };
279 
280 }  // namespace pw::i2c
281