1 /*
2 * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #pragma once
8
9 #ifndef __cpp_exceptions
10 #error I2C class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
11 #endif
12
13 #include <exception>
14 #include <memory>
15 #include <chrono>
16 #include <vector>
17 #include <list>
18 #include <future>
19
20 #include "driver/i2c.h"
21 #include "esp_exception.hpp"
22
23 namespace idf {
24
25 struct I2CException : public ESPException {
26 I2CException(esp_err_t error);
27 };
28
29 struct I2CTransferException : public I2CException {
30 I2CTransferException(esp_err_t error);
31 };
32
33 /**
34 * Superclass for all transfer objects which are accepted by \c I2CMaster::transfer().
35 */
36 template<typename TReturn>
37 class I2CTransfer {
38 protected:
39 /**
40 * Wrapper around i2c_cmd_handle_t, makes it exception-safe.
41 */
42 struct I2CCommandLink {
43 I2CCommandLink();
44 ~I2CCommandLink();
45
46 i2c_cmd_handle_t handle;
47 };
48
49 public:
50 /**
51 * Helper typedef to facilitate type resolution during calls to I2CMaster::transfer().
52 */
53 typedef TReturn TransferReturnT;
54
55 /**
56 * @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
57 */
58 I2CTransfer(std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
59
~I2CTransfer()60 virtual ~I2CTransfer() { }
61
62 /**
63 * Do all general parts of the I2C transfer:
64 * - initialize the command link
65 * - issuing a start to the command link queue
66 * - calling \c queue_cmd() in the subclass to issue specific commands to the command link queue
67 * - issuing a stop to the command link queue
68 * - executing the assembled commands on the I2C bus
69 * - calling \c process_result() to process the results of the commands or calling process_exception() if
70 * there was an exception
71 * - deleting the command link
72 * This method is normally called by I2CMaster, but can also be used stand-alone if the bus corresponding to
73 * \c i2c_num has be initialized.
74 *
75 * @throws I2CException for any particular I2C error
76 */
77 TReturn do_transfer(i2c_port_t i2c_num, uint8_t i2c_addr);
78
79 protected:
80 /**
81 * Implementation of the I2C command is implemented by subclasses.
82 * The I2C command handle is initialized already at this stage.
83 * The first action is issuing the I2C address and the read/write bit, depending on what the subclass implements.
84 * On error, this method has to throw an instance of I2CException.
85 *
86 * @param handle the initialized command handle of the I2C driver.
87 * @param i2c_addr The slave's I2C address.
88 *
89 * @throw I2CException
90 */
91 virtual void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) = 0;
92
93 /**
94 * Implementation of whatever neccessary action after successfully sending the I2C command.
95 * On error, this method has to throw an instance of I2CException.
96 *
97 * @throw I2CException
98 */
99 virtual TReturn process_result() = 0;
100
101 /**
102 * For some calls to the underlying driver (e.g. \c i2c_master_cmd_begin() ), this general timeout will be passed.
103 */
104 const TickType_t driver_timeout;
105 };
106
107 /**
108 * @brief Super class for any I2C master or slave
109 */
110 class I2CBus {
111 public:
112 /*
113 * @brief Initialize I2C master bus.
114 *
115 * Initialize and install the bus driver in master mode.
116 *
117 * @param i2c_number The I2C port number.
118 */
119 I2CBus(i2c_port_t i2c_number);
120
121 /**
122 * @brief uninstall the bus driver.
123 */
124 virtual ~I2CBus();
125
126 /**
127 * The I2C port number.
128 */
129 const i2c_port_t i2c_num;
130 };
131
132 /**
133 * @brief Simple I2C Master object
134 *
135 * This class provides to ways to issue I2C read and write requests. The simplest way is to use \c sync_write() and
136 * sync_read() to write and read, respectively. As the name suggests, they block during the whole transfer.
137 * For all asynchrounous transfers as well as combined write-read transfers, use \c transfer().
138 */
139 class I2CMaster : public I2CBus {
140 public:
141 /**
142 * Initialize and install the driver of an I2C master peripheral.
143 *
144 * Initialize and install the bus driver in master mode. Pullups will be enabled for both pins. If you want a
145 * different configuration, use configure() and i2c_set_pin() of the underlying driver to disable one or both
146 * pullups.
147 *
148 * @param i2c_number The number of the I2C device.
149 * @param scl_gpio GPIO number of the SCL line.
150 * @param sda_gpio GPIO number of the SDA line.
151 * @param clock_speed The master clock speed.
152 * @param scl_pullup Enable SCL pullup.
153 * @param sda_pullup Enable SDA pullup.
154 *
155 * @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
156 */
157 I2CMaster(i2c_port_t i2c_number,
158 int scl_gpio,
159 int sda_gpio,
160 uint32_t clock_speed,
161 bool scl_pullup = true,
162 bool sda_pullup = true);
163
164 /**
165 * Delete the driver.
166 */
167 virtual ~I2CMaster();
168
169 /**
170 * Issue an asynchronous I2C transfer which is executed in the background.
171 *
172 * This method uses a C++ \c std::future as mechanism to wait for the asynchronous return value.
173 * The return value can be accessed with \c future::get(). \c future::get() also synchronizes with the thread
174 * doing the work in the background, i.e. it waits until the return value has been issued.
175 *
176 * The actual implementation is delegated to the TransferT object. It will be given the I2C number to work
177 * with.
178 *
179 * Requirements for TransferT: It should implement or imitate the interface of I2CTransfer.
180 *
181 * @param xfer The transfer to execute. What the transfer does, depends on it's implementation in
182 * \c TransferT::do_transfer(). It also determines the future template of this function, indicated by
183 * \c TransferT::TransferReturnT.
184 *
185 * @param i2c_addr The address of the I2C slave device targeted by the transfer.
186 *
187 * @return A future with \c TransferT::TransferReturnT. It depends on which template type is used for xfer.
188 * In case of a simple write (I2CWrite), it's future<void>.
189 * In case of a read (I2CRead), it's future<vector<uint8_t> > corresponding to the length of the read
190 * operation.
191 * If TransferT is a combined transfer with repeated reads (I2CComposed), then the return type is
192 * future<vector<vector<uint8_t> > >, a vector of results corresponding to the queued read operations.
193 *
194 * @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
195 * @throws std::exception for failures in libstdc++
196 */
197 template<typename TransferT>
198 std::future<typename TransferT::TransferReturnT> transfer(std::shared_ptr<TransferT> xfer, uint8_t i2c_addr);
199
200 /**
201 * Do a synchronous write.
202 *
203 * All data in data will be written to the I2C device with i2c_addr at once.
204 * This method will block until the I2C write is complete.
205 *
206 * @param i2c_addr The address of the I2C device to which the data shall be sent.
207 * @param data The data to send (size to be sent is determined by data.size()).
208 *
209 * @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
210 * @throws std::exception for failures in libstdc++
211 */
212 void sync_write(uint8_t i2c_addr, const std::vector<uint8_t> &data);
213
214 /**
215 * Do a synchronous read.
216 * This method will block until the I2C read is complete.
217 *
218 * n_bytes bytes of data will be read from the I2C device with i2c_addr.
219 * While reading the last byte, the master finishes the reading by sending a NACK, before issuing a stop.
220 *
221 * @param i2c_addr The address of the I2C device from which to read.
222 * @param n_bytes The number of bytes to read.
223 *
224 * @return the read bytes
225 *
226 * @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
227 * @throws std::exception for failures in libstdc++
228 */
229 std::vector<uint8_t> sync_read(uint8_t i2c_addr, size_t n_bytes);
230
231 /**
232 * Do a simple asynchronous write-read transfer.
233 *
234 * First, \c write_data will be written to the bus, then a number of \c read_n_bytes will be read from the bus
235 * with a repeated start condition. The slave device is determined by \c i2c_addr.
236 * While reading the last byte, the master finishes the reading by sending a NACK, before issuing a stop.
237 * This method will block until the I2C transfer is complete.
238 *
239 * @param i2c_addr The address of the I2C device from which to read.
240 * @param write_data The data to write to the bus before reading.
241 * @param read_n_bytes The number of bytes to read.
242 *
243 * @return the read bytes
244 *
245 * @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
246 * @throws std::exception for failures in libstdc++
247 */
248 std::vector<uint8_t> sync_transfer(uint8_t i2c_addr,
249 const std::vector<uint8_t> &write_data,
250 size_t read_n_bytes);
251 };
252
253 /**
254 * @brief Responsible for initialization and de-initialization of an I2C slave peripheral.
255 */
256 class I2CSlave : public I2CBus {
257 public:
258 /**
259 * Initialize and install the driver of an I2C slave peripheral.
260 *
261 * Initialize and install the bus driver in slave mode. Pullups will be enabled for both pins. If you want a
262 * different configuration, use configure() and i2c_set_pin() of the underlying driver to disable one or both
263 * pullups.
264 *
265 * @param i2c_number The number of the I2C device.
266 * @param scl_gpio GPIO number of the SCL line.
267 * @param sda_gpio GPIO number of the SDA line.
268 * @param slave_addr The address of the slave device on the I2C bus.
269 * @param rx_buf_len Receive buffer length.
270 * @param tx_buf_len Transmit buffer length.
271 * @param scl_pullup Enable SCL pullup.
272 * @param sda_pullup Enable SDA pullup.
273 *
274 * @throws
275 */
276 I2CSlave(i2c_port_t i2c_number,
277 int scl_gpio,
278 int sda_gpio,
279 uint8_t slave_addr,
280 size_t rx_buf_len,
281 size_t tx_buf_len,
282 bool scl_pullup = true,
283 bool sda_pullup = true);
284
285 /**
286 * Delete the driver.
287 */
288 virtual ~I2CSlave();
289
290 /**
291 * Schedule a raw data write once master is ready.
292 *
293 * The data is saved in a buffer, waiting for the master to pick it up.
294 */
295 virtual int write_raw(const uint8_t* data, size_t data_len, std::chrono::milliseconds timeout);
296
297 /**
298 * Read raw data from the bus.
299 *
300 * The data is read directly from the buffer. Hence, it has to be written already by master.
301 */
302 virtual int read_raw(uint8_t* buffer, size_t buffer_len, std::chrono::milliseconds timeout);
303 };
304
305 /**
306 * Implementation for simple I2C writes, which can be executed by \c I2CMaster::transfer().
307 * It stores the bytes to be written as a vector.
308 */
309 class I2CWrite : public I2CTransfer<void> {
310 public:
311 /**
312 * @param bytes The bytes which should be written.
313 * @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
314 */
315 I2CWrite(const std::vector<uint8_t> &bytes, std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
316
317 protected:
318 /**
319 * Write the address and set the read bit to 0 to issue the address and request a write.
320 * Then write the bytes.
321 *
322 * @param handle The initialized I2C command handle.
323 * @param i2c_addr The I2C address of the slave.
324 */
325 void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) override;
326
327 /**
328 * Set the value of the promise to unblock any callers waiting on it.
329 */
330 void process_result() override;
331
332 private:
333 /**
334 * The bytes to write.
335 */
336 std::vector<uint8_t> bytes;
337 };
338
339 /**
340 * Implementation for simple I2C reads, which can be executed by \c I2CMaster::transfer().
341 * It stores the bytes to be read as a vector to be returned later via a future.
342 */
343 class I2CRead : public I2CTransfer<std::vector<uint8_t> > {
344 public:
345 /**
346 * @param The number of bytes to read.
347 * @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
348 */
349 I2CRead(size_t size, std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
350
351 protected:
352 /**
353 * Write the address and set the read bit to 1 to issue the address and request a read.
354 * Then read into bytes.
355 *
356 * @param handle The initialized I2C command handle.
357 * @param i2c_addr The I2C address of the slave.
358 */
359 void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) override;
360
361 /**
362 * Set the return value of the promise to unblock any callers waiting on it.
363 */
364 std::vector<uint8_t> process_result() override;
365
366 private:
367 /**
368 * The bytes to read.
369 */
370 std::vector<uint8_t> bytes;
371 };
372
373 /**
374 * This kind of transfer uses repeated start conditions to chain transfers coherently.
375 * In particular, this can be used to chain multiple single write and read transfers into a single transfer with
376 * repeated starts as it is commonly done for I2C devices.
377 * The result is a vector of vectors representing the reads in the order of how they were added using add_read().
378 */
379 class I2CComposed : public I2CTransfer<std::vector<std::vector<uint8_t> > > {
380 public:
381 I2CComposed(std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
382
383 /**
384 * Add a read to the chain.
385 *
386 * @param size The size of the read in bytes.
387 */
388 void add_read(size_t size);
389
390 /**
391 * Add a write to the chain.
392 *
393 * @param bytes The bytes to write; size will be bytes.size()
394 */
395 void add_write(std::vector<uint8_t> bytes);
396
397 protected:
398 /**
399 * Write all chained transfers, including a repeated start issue after each but the last transfer.
400 *
401 * @param handle The initialized I2C command handle.
402 * @param i2c_addr The I2C address of the slave.
403 */
404 void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) override;
405
406 /**
407 * Creates the vector with the vectors from all reads.
408 */
409 std::vector<std::vector<uint8_t> > process_result() override;
410
411 private:
412 class CompTransferNode {
413 public:
~CompTransferNode()414 virtual ~CompTransferNode() { }
415 virtual void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) = 0;
process_result(std::vector<std::vector<uint8_t>> & read_results)416 virtual void process_result(std::vector<std::vector<uint8_t> > &read_results) { }
417 };
418
419 class CompTransferNodeRead : public CompTransferNode {
420 public:
CompTransferNodeRead(size_t size)421 CompTransferNodeRead(size_t size) : bytes(size) { }
422 void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) override;
423
424 void process_result(std::vector<std::vector<uint8_t> > &read_results) override;
425 private:
426 std::vector<uint8_t> bytes;
427 };
428
429 class CompTransferNodeWrite : public CompTransferNode {
430 public:
CompTransferNodeWrite(std::vector<uint8_t> bytes)431 CompTransferNodeWrite(std::vector<uint8_t> bytes) : bytes(bytes) { }
432 void queue_cmd(i2c_cmd_handle_t handle, uint8_t i2c_addr) override;
433 private:
434 std::vector<uint8_t> bytes;
435 };
436
437 /**
438 * The chained transfers.
439 */
440 std::list<std::shared_ptr<CompTransferNode> > transfer_list;
441 };
442
443 template<typename TReturn>
I2CTransfer(std::chrono::milliseconds driver_timeout)444 I2CTransfer<TReturn>::I2CTransfer(std::chrono::milliseconds driver_timeout)
445 : driver_timeout(driver_timeout.count()) { }
446
447 template<typename TReturn>
I2CCommandLink()448 I2CTransfer<TReturn>::I2CCommandLink::I2CCommandLink()
449 {
450 handle = i2c_cmd_link_create();
451 if (!handle) {
452 throw I2CException(ESP_ERR_NO_MEM);
453 }
454 }
455
456 template<typename TReturn>
~I2CCommandLink()457 I2CTransfer<TReturn>::I2CCommandLink::~I2CCommandLink()
458 {
459 i2c_cmd_link_delete(handle);
460 }
461
462 template<typename TReturn>
do_transfer(i2c_port_t i2c_num,uint8_t i2c_addr)463 TReturn I2CTransfer<TReturn>::do_transfer(i2c_port_t i2c_num, uint8_t i2c_addr)
464 {
465 I2CCommandLink cmd_link;
466
467 queue_cmd(cmd_link.handle, i2c_addr);
468
469 CHECK_THROW_SPECIFIC(i2c_master_stop(cmd_link.handle), I2CException);
470
471 CHECK_THROW_SPECIFIC(i2c_master_cmd_begin(i2c_num, cmd_link.handle, driver_timeout / portTICK_RATE_MS), I2CTransferException);
472
473 return process_result();
474 }
475
476 template<typename TransferT>
transfer(std::shared_ptr<TransferT> xfer,uint8_t i2c_addr)477 std::future<typename TransferT::TransferReturnT> I2CMaster::transfer(std::shared_ptr<TransferT> xfer, uint8_t i2c_addr)
478 {
479 if (!xfer) throw I2CException(ESP_ERR_INVALID_ARG);
480
481 return std::async(std::launch::async, [this](std::shared_ptr<TransferT> xfer, uint8_t i2c_addr) {
482 return xfer->do_transfer(i2c_num, i2c_addr);
483 }, xfer, i2c_addr);
484 }
485
486 } // idf
487