FastArduino  v1.9
C++ library to build fast but small Arduino/AVR projects
i2c_handler_common.h
Go to the documentation of this file.
1 // Copyright 2016-2022 Jean-Francois Poilpret
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
16 
22 #ifndef I2C_HANDLER_COMMON_HH
23 #define I2C_HANDLER_COMMON_HH
24 
25 #include <stdint.h>
26 #include "boards/board_traits.h"
27 #include "bits.h"
28 #include "i2c.h"
29 #include "streams.h"
30 #include "future.h"
31 #include "lifecycle.h"
32 #include "queue.h"
33 #include "utilities.h"
34 
35 namespace i2c
36 {
43  enum class DebugStatus : uint8_t
44  {
46  START = 0,
50  SLAW,
52  SLAR,
54  SEND,
56  RECV,
58  RECV_LAST,
60  STOP,
61 
63  SEND_OK,
65  SEND_ERROR,
67  RECV_OK,
70  };
71 
83  using I2C_DEBUG_HOOK = void (*)(DebugStatus status, uint8_t data);
84 
96  using I2C_STATUS_HOOK = void (*)(Status expected, Status actual);
97 
99  // Type of commands in queue
100  class I2CCommandType
101  {
102  public:
103  constexpr I2CCommandType() = default;
104  constexpr I2CCommandType(const I2CCommandType&) = default;
105  explicit constexpr I2CCommandType(uint8_t value) : value_{value} {}
106  constexpr I2CCommandType(bool write, bool stop, bool finish, bool end)
107  : value_{value(write, stop, finish, end)} {}
108  I2CCommandType& operator=(const I2CCommandType&) = default;
109 
110  bool is_none() const
111  {
112  return value_ == NONE;
113  }
114  bool is_write() const
115  {
116  return value_ & WRITE;
117  }
118  bool is_stop() const
119  {
120  return value_ & STOP;
121  }
122  bool is_finish() const
123  {
124  return value_ & FINISH;
125  }
126  bool is_end() const
127  {
128  return value_ & END;
129  }
130  void add_flags(uint8_t value)
131  {
132  value_ |= value;
133  }
134 
135  static constexpr uint8_t flags(bool stop, bool finish, bool end)
136  {
137  return bits::ORIF8(stop, STOP, finish, FINISH, end, END);
138  }
139 
140  private:
141  static constexpr const uint8_t NONE = 0;
142  static constexpr const uint8_t NOT_NONE = bits::BV8(0);
143  static constexpr const uint8_t WRITE = bits::BV8(1);
144  static constexpr const uint8_t STOP = bits::BV8(2);
145  static constexpr const uint8_t FINISH = bits::BV8(3);
146  static constexpr const uint8_t END = bits::BV8(4);
147 
148  static constexpr uint8_t value(bool write, bool stop, bool finish, bool end)
149  {
150  return bits::ORIF8(true, NOT_NONE, write, WRITE, stop, STOP, finish, FINISH, end, END);
151  }
152 
153  uint8_t value_ = NONE;
154 
155  friend bool operator==(const I2CCommandType&, const I2CCommandType&);
156  friend bool operator!=(const I2CCommandType&, const I2CCommandType&);
157  };
158 
159  streams::ostream& operator<<(streams::ostream& out, const I2CCommandType& t);
160  bool operator==(const I2CCommandType& a, const I2CCommandType& b);
161  bool operator!=(const I2CCommandType& a, const I2CCommandType& b);
163 
176  {
177  public:
179  constexpr I2CLightCommand() = default;
180  constexpr I2CLightCommand(const I2CLightCommand&) = default;
181  constexpr I2CLightCommand(I2CCommandType type, uint8_t byte_count) : type_{type}, byte_count_{byte_count} {}
182 
183  I2CCommandType type() const
184  {
185  return type_;
186  }
187  I2CCommandType& type()
188  {
189  return type_;
190  }
191  uint8_t byte_count() const
192  {
193  return byte_count_;
194  }
195  void decrement_byte_count()
196  {
197  --byte_count_;
198  }
199  void update_byte_count(uint8_t read_count, uint8_t write_count)
200  {
201  if (byte_count_ == 0)
202  byte_count_ = (type_.is_write() ? write_count : read_count);
203  }
205 
206  private:
207  // Type of this command
208  I2CCommandType type_ = I2CCommandType{};
209  // The number of remaining bytes to be read or write
210  uint8_t byte_count_ = 0;
211  };
212 
227  template<typename T> class I2CCommand : public I2CLightCommand
228  {
229  public:
231  constexpr I2CCommand() = default;
232  constexpr I2CCommand(const I2CCommand&) = default;
233  constexpr I2CCommand(
234  const I2CLightCommand& that, uint8_t target, T future)
235  : I2CLightCommand{that}, target_{target}, future_{future} {}
236  constexpr I2CCommand& operator=(const I2CCommand&) = default;
237 
238  uint8_t target() const
239  {
240  return target_;
241  }
242  T future() const
243  {
244  return future_;
245  }
246 
247  void set_target(uint8_t target, T future)
248  {
249  target_ = target;
250  future_ = future;
251  }
253 
254  private:
255  // Address of the target device (on 8 bits, already left-shifted)
256  uint8_t target_ = 0;
257  // A proxy to the future to be used for this command
258  T future_{};
259  };
260 
262  template<typename T>
263  streams::ostream& operator<<(streams::ostream& out, const I2CCommand<T>& c)
264  {
265  out << '{' << c.type() << ','
266  << streams::hex << c.target() << '}' << streams::flush;
267  return out;
268  }
270 
272  // Generic support for I2C debugging
273  template<bool IS_DEBUG_ = false, typename DEBUG_HOOK_ = I2C_DEBUG_HOOK> struct I2CDebugSupport
274  {
275  explicit I2CDebugSupport(UNUSED DEBUG_HOOK_ hook = nullptr) {}
276  void call_hook(UNUSED DebugStatus status, UNUSED uint8_t data = 0)
277  {
278  // Intentionally left empty
279  }
280  };
281  template<typename DEBUG_HOOK_> struct I2CDebugSupport<true, DEBUG_HOOK_>
282  {
283  explicit I2CDebugSupport(DEBUG_HOOK_ hook) : hook_{hook} {}
284  void call_hook(DebugStatus status, uint8_t data = 0)
285  {
286  hook_(status, data);
287  }
288  private:
289  DEBUG_HOOK_ hook_;
290  };
292 
294  // Generic support for I2C status hook
295  template<bool IS_STATUS_ = false, typename STATUS_HOOK_ = I2C_STATUS_HOOK> struct I2CStatusSupport
296  {
297  explicit I2CStatusSupport(UNUSED STATUS_HOOK_ hook = nullptr) {}
298  void call_hook(UNUSED Status expected, UNUSED Status actual)
299  {
300  // Intentionally left empty
301  }
302  };
303  template<typename STATUS_HOOK_> struct I2CStatusSupport<true, STATUS_HOOK_>
304  {
305  explicit I2CStatusSupport(STATUS_HOOK_ hook) : hook_{hook} {}
306  void call_hook(Status expected, Status actual)
307  {
308  hook_(expected, actual);
309  }
310  private:
311  STATUS_HOOK_ hook_;
312  };
314 
316  // Generic support for LifeCycle resolution
317  template<bool HAS_LIFECYCLE_ = false> struct I2CLifeCycleSupport
318  {
319  template<typename T> using PROXY = lifecycle::DirectProxy<T>;
320  template<typename T> static PROXY<T> make_proxy(const T& dest)
321  {
322  return lifecycle::make_direct_proxy(dest);
323  }
324  explicit I2CLifeCycleSupport(UNUSED lifecycle::AbstractLifeCycleManager* lifecycle_manager = nullptr) {}
325  template<typename T> T& resolve(PROXY<T> proxy) const
326  {
327  return *proxy();
328  }
329  };
330  template<> struct I2CLifeCycleSupport<true>
331  {
332  template<typename T> using PROXY = lifecycle::LightProxy<T>;
333  template<typename T> static PROXY<T> make_proxy(const T& dest)
334  {
335  return lifecycle::make_light_proxy(dest);
336  }
337  explicit I2CLifeCycleSupport(lifecycle::AbstractLifeCycleManager* lifecycle_manager)
338  : lifecycle_manager_{*lifecycle_manager} {}
339  template<typename T> T& resolve(PROXY<T> proxy) const
340  {
341  return *proxy(lifecycle_manager_);
342  }
343  private:
344  lifecycle::AbstractLifeCycleManager& lifecycle_manager_;
345  };
347 
349  // Specific traits for I2CMode
350  template<I2CMode MODE_ = I2CMode::STANDARD> struct I2CMode_trait
351  {
352  static constexpr I2CMode MODE = MODE_;
353  static constexpr uint32_t RATE = 100'000UL;
354  static constexpr uint32_t FREQUENCY = ((F_CPU / RATE) - 16UL) / 2;
355  static constexpr const uint8_t T_HD_STA = utils::calculate_delay1_count(4.0);
356  static constexpr const uint8_t T_LOW = utils::calculate_delay1_count(4.7);
357  static constexpr const uint8_t T_HIGH = utils::calculate_delay1_count(4.0);
358  static constexpr const uint8_t T_SU_STA = utils::calculate_delay1_count(4.7);
359  static constexpr const uint8_t T_SU_STO = utils::calculate_delay1_count(4.0);
360  static constexpr const uint8_t T_BUF = utils::calculate_delay1_count(4.7);
361  static constexpr const uint8_t DELAY_AFTER_STOP = utils::calculate_delay1_count(4.0 + 4.7);
362  };
363  template<> struct I2CMode_trait<I2CMode::FAST>
364  {
365  static constexpr I2CMode MODE = I2CMode::FAST;
366  static constexpr uint32_t RATE = 400'000UL;
367  static constexpr uint32_t FREQUENCY = ((F_CPU / RATE) - 16UL) / 2;
368  static constexpr const uint8_t T_HD_STA = utils::calculate_delay1_count(0.6);
369  static constexpr const uint8_t T_LOW = utils::calculate_delay1_count(1.3);
370  static constexpr const uint8_t T_HIGH = utils::calculate_delay1_count(0.6);
371  static constexpr const uint8_t T_SU_STA = utils::calculate_delay1_count(0.6);
372  static constexpr const uint8_t T_SU_STO = utils::calculate_delay1_count(0.6);
373  static constexpr const uint8_t T_BUF = utils::calculate_delay1_count(1.3);
374  static constexpr const uint8_t DELAY_AFTER_STOP = utils::calculate_delay1_count(0.6 + 1.3);
375  };
377 
409  // Abstract generic class for synchronous I2C management
410  template<typename ARCH_HANDLER_, I2CMode MODE_, bool HAS_LC_,
411  typename STATUS_HOOK_, bool HAS_DEBUG_, typename DEBUG_HOOK_>
413  {
414  protected:
415  using ARCH_HANDLER = ARCH_HANDLER_;
416  using MODE_TRAIT = I2CMode_trait<MODE_>;
417  using I2C_TRAIT = board_traits::TWI_trait;
418  using REG8 = board_traits::REG8;
419  using DEBUG = I2CDebugSupport<HAS_DEBUG_, DEBUG_HOOK_>;
420  using LC = I2CLifeCycleSupport<HAS_LC_>;
421 
422  public:
424  template<typename T> using PROXY = typename LC::template PROXY<T>;
425  template<typename OUT, typename IN> using FUTURE = future::FakeFuture<OUT, IN>;
426 
434  void begin()
435  {
436  synchronized begin_();
437  }
438 
445  void end()
446  {
447  synchronized end_();
448  }
449 
457  void begin_()
458  {
459  handler_.begin_();
460  }
461 
468  void end_()
469  {
470  handler_.end_();
471  }
472 
473  protected:
475  explicit AbstractI2CSyncManager(
476  lifecycle::AbstractLifeCycleManager* lifecycle_manager = nullptr,
477  STATUS_HOOK_ status_hook = nullptr,
478  DEBUG_HOOK_ debug_hook = nullptr)
479  : handler_{status_hook}, lc_{lifecycle_manager}, debug_hook_{debug_hook} {}
481 
482  private:
483  bool ensure_num_commands_(UNUSED uint8_t num_commands) const
484  {
485  return true;
486  }
487 
488  bool push_command_(
489  I2CLightCommand command, uint8_t target, PROXY<ABSTRACT_FUTURE> proxy)
490  {
491  // Check command is not empty
492  const I2CCommandType type = command.type();
493  if (type.is_none()) return true;
494  if (clear_commands_) return false;
495  ABSTRACT_FUTURE& future = lc_.resolve(proxy);
496  // Execute command immediately, from start to optional stop
497  bool ok = (no_stop_ ? exec_repeat_start_() : exec_start_());
498  stopped_already_ = false;
499  if (!ok)
500  return handle_error(future);
501 
502  if (type.is_write())
503  {
504  // Send device address
505  if (!exec_send_slaw_(target))
506  return handle_error(future);
507  // Send content
508  while (command.byte_count() > 0)
509  // In case of a NACK on data writing, we check if it is not last byte
510  if ((!exec_send_data_(command, future)) && (command.byte_count() > 0))
511  return handle_error(future);
512  }
513  else
514  {
515  // Send device address
516  if (!exec_send_slar_(target))
517  return handle_error(future);
518  // Receive content
519  while (command.byte_count() > 0)
520  if (!exec_receive_data_(command, future))
521  return handle_error(future);
522  }
523 
524  // Check if we must force finish the future
525  if (type.is_finish())
526  future.set_future_finish_();
527  // Check if we must force a STOP
528  if (type.is_stop())
529  exec_stop_();
530  // Ensure STOP is generated or not depending on latest command executed
531  no_stop_ = !type.is_stop();
532  return true;
533  }
534 
535  void last_command_pushed_()
536  {
537  // Check if previously executed command already did a STOP (and needed one)
538  if ((!no_stop_) && (!stopped_already_) && (!clear_commands_))
539  {
540  exec_stop_();
541  no_stop_ = false;
542  }
543  clear_commands_ = false;
544  stopped_already_ = false;
545  }
546 
547  template<typename T> T& resolve(PROXY<T> proxy) const
548  {
549  return lc_.resolve(proxy);
550  }
551 
552  // Low-level methods to handle the bus in an asynchronous way
553  bool exec_start_()
554  {
555  debug_hook_.call_hook(DebugStatus::START);
556  return handler_.exec_start_();
557  }
558 
559  bool exec_repeat_start_()
560  {
561  debug_hook_.call_hook(DebugStatus::REPEAT_START);
562  return handler_.exec_repeat_start_();
563  }
564 
565  bool exec_send_slar_(uint8_t target)
566  {
567  debug_hook_.call_hook(DebugStatus::SLAR, target);
568  return handler_.exec_send_slar_(target);
569  }
570 
571  bool exec_send_slaw_(uint8_t target)
572  {
573  debug_hook_.call_hook(DebugStatus::SLAW, target);
574  return handler_.exec_send_slaw_(target);
575  }
576 
577  bool exec_send_data_(I2CLightCommand& command, ABSTRACT_FUTURE& future)
578  {
579  // Determine next data byte
580  uint8_t data = 0;
581  bool ok = future.get_storage_value_(data);
582  debug_hook_.call_hook(DebugStatus::SEND, data);
583  debug_hook_.call_hook(ok ? DebugStatus::SEND_OK : DebugStatus::SEND_ERROR);
584  // This should only happen if there are 2 concurrent consumers for that Future
585  if (!ok)
586  {
587  future.set_future_error_(errors::EILSEQ);
588  return false;
589  }
590  command.decrement_byte_count();
591  return handler_.exec_send_data_(data);
592  }
593 
594  bool exec_receive_data_(I2CLightCommand& command, ABSTRACT_FUTURE& future)
595  {
596  // Is this the last byte to receive?
597  const bool last_byte = (command.byte_count() == 1);
598  const DebugStatus debug = (last_byte ? DebugStatus::RECV_LAST : DebugStatus::RECV);
599  debug_hook_.call_hook(debug);
600 
601  uint8_t data;
602  if (handler_.exec_receive_data_(last_byte, data))
603  {
604  const bool ok = future.set_future_value_(data);
605  debug_hook_.call_hook(ok ? DebugStatus::RECV_OK : DebugStatus::RECV_ERROR, data);
606  // This should only happen in case there are 2 concurrent providers for this future
607  if (ok)
608  {
609  command.decrement_byte_count();
610  }
611  else
612  {
613  future.set_future_error_(errors::EILSEQ);
614  }
615  return ok;
616  }
617  return false;
618  }
619 
620  void exec_stop_()
621  {
622  debug_hook_.call_hook(DebugStatus::STOP);
623  handler_.exec_stop_();
624  // If so then delay 4.0us + 4.7us (100KHz) or 0.6us + 1.3us (400KHz)
625  // (ATMEGA328P datasheet 29.7 Tsu;sto + Tbuf)
626  _delay_loop_1(MODE_TRAIT::DELAY_AFTER_STOP);
627  stopped_already_ = true;
628  }
629 
630  // This method is called when an error has occurred
631  bool handle_error(ABSTRACT_FUTURE& future)
632  {
633  if (future.status() != future::FutureStatus::ERROR)
634  // The future must be marked as error
635  future.set_future_error_(errors::EPROTO);
636 
637  // Clear command belonging to the same transaction (i.e. same future)
638  // ie forbid any new command until last command (add new flag for that)
639  clear_commands_ = true;
640  // In case of an error, immediately send a STOP condition
641  exec_stop_();
642  return false;
643  }
644 
645  // Flags for storing I2C transaction operation state
646  bool no_stop_ = false;
647  bool clear_commands_ = false;
648  bool stopped_already_ = false;
649 
650  ARCH_HANDLER handler_;
651  LC lc_;
652  DEBUG debug_hook_;
653 
654  template<typename> friend class I2CDevice;
655  };
656 
658  // Specific traits for I2C Manager
659  template<typename T> struct I2CManager_trait
660  {
661  static constexpr bool IS_I2CMANAGER = false;
662  static constexpr bool IS_ASYNC = false;
663  static constexpr bool HAS_LIFECYCLE = false;
664  static constexpr bool IS_DEBUG = false;
665  static constexpr bool IS_STATUS = false;
666  static constexpr I2CMode MODE = I2CMode::STANDARD;
667  };
668 
669  template<bool IS_ASYNC_, bool HAS_LIFECYCLE_, bool IS_STATUS_, bool IS_DEBUG_, I2CMode MODE_>
670  struct I2CManager_trait_impl
671  {
672  static constexpr bool IS_I2CMANAGER = true;
673  static constexpr bool IS_ASYNC = IS_ASYNC_;
674  static constexpr bool HAS_LIFECYCLE = HAS_LIFECYCLE_;
675  static constexpr bool IS_DEBUG = IS_DEBUG_;
676  static constexpr bool IS_STATUS = IS_STATUS_;
677  static constexpr I2CMode MODE = MODE_;
678  };
680 }
681 
682 #endif /* I2C_HANDLER_COMMON_HH */
Useful bits manipulation utilities.
Base class for all FakeFutures.
Definition: future.h:1062
Actual FakeFuture, it has the exact same API as Future and can be used in lieu of Future.
Definition: future.h:1223
Abstract synchronous I2C Manager for all MCU architectures.
void begin_()
Prepare and enable the MCU for I2C transmission.
void end_()
Disable MCU I2C transmission.
void begin()
Prepare and enable the MCU for I2C transmission.
void end()
Disable MCU I2C transmission.
Atomic I2C command as used internally by an asynchronous I2C Manager.
Light atomic I2C command as prepared by an I2C device.
The abstract base class of all LifeCycleManager.
Definition: lifecycle.h:132
A kind of proxy class that encapsulates access to a fixed T instance.
Definition: lifecycle.h:826
A light proxy class that encapsulates access to a fixed T instance, or to a dynamic LifeCycle<T> inst...
Definition: lifecycle.h:643
Output stream wrapper to provide formatted output API, a la C++.
Definition: streams.h:61
#define UNUSED
Specific GCC attribute to declare an argument or variable unused, so that the compiler does not emit ...
Definition: defines.h:45
Utility API to handle the concept of futures.
I2C API common definitions.
Utility API to handle lifecycle of objects so that:
static constexpr uint8_t BV8(uint8_t bit)
Create a uint8_t bitmask for the given bit number.
Definition: bits.h:41
static constexpr uint8_t ORIF8(bool flag1, uint8_t val1, bool flag2, uint8_t val2)
Create a uint8_t bitwise OR boolean operation between uint8_t operands, conditioned by bool flags.
Definition: bits.h:361
static constexpr const Tone END
This special tone marks the end of a melody (as a sequence of Tones).
Definition: tone_player.h:44
constexpr const int EPROTO
Protocol error.
Definition: errors.h:58
constexpr const int EILSEQ
Illegal byte sequence.
Definition: errors.h:65
Contains the API around Future implementation.
Definition: future.cpp:18
@ ERROR
The status of a Future once a value provider has reported an error to it.
Define API to define and manage I2C devices.
Definition: i2c.cpp:18
void(*)(Status expected, Status actual) I2C_STATUS_HOOK
The default status observer hook type.
DebugStatus
List of debug states that are reported by the I2C Manager in debug mode.
@ SEND_OK
The latest sent byte has been acknowledged by the slave.
@ SEND_ERROR
The latest sent byte has not been acknowledged by the slave.
@ SLAR
A slave address has just been sent for reading.
@ RECV_OK
I2C Manager has acknowledged the latest received byte from the slave.
@ SEND
A byte has just be sent to the slave.
@ STOP
A stop condition has just been sent.
@ REPEAT_START
A repeat start condition has just been sent.
@ RECV_LAST
The last byte is being received from the slave.
@ START
A start condition has just been sent.
@ RECV
A byte is being received from the slave.
@ RECV_ERROR
I2C Manager has not acknowledged the latest received byte from the slave.
@ SLAW
A slave address has just been sent for writing.
void(*)(DebugStatus status, uint8_t data) I2C_DEBUG_HOOK
The default debugging hook type.
I2CMode
I2C available transmission modes.
Definition: i2c.h:108
@ STANDARD
I2C Standard mode, less than 100KHz.
@ FAST
I2C Fast mode, less than 400KHz.
Status
Transmission status codes.
Definition: i2c.h:66
DirectProxy< T > make_direct_proxy(const T &dest)
Utility template function to create a DirectProxy<T> from dest without the need to speicify T.
Definition: lifecycle.h:905
LightProxy< T > make_light_proxy(const T &dest)
Utility template function to create a LightProxy<T> from dest without the need to speicify T.
Definition: lifecycle.h:781
Proxy< T > make_proxy(const T &dest)
Utility template function to create a Proxy<T> from dest without the need to speicify T.
Definition: lifecycle.h:593
void flush(FSTREAM &stream)
Manipulator for an output stream, which will flush the stream buffer.
Definition: streams.h:713
void hex(FSTREAM &stream)
Manipulator for an output or input stream, which will set the base, used to represent (output) or int...
Definition: ios.h:774
bool operator==(const RTTTime &a, const RTTTime &b)
Compare 2 RTTTime instances.
Definition: time.h:204
bool operator!=(const RTTTime &a, const RTTTime &b)
Compare 2 RTTTime instances.
Definition: time.h:216
constexpr uint8_t calculate_delay1_count(float time_us)
Calculate the count to pass to delay1() in order to reach time_us microseconds delay.
Definition: utilities.h:485
Utility API to handle ring-buffer queue containers.
C++-like std::iostream facilities.
General utilities API that have broad application in programs.