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