FastArduino v1.10
C++ library to build fast but small Arduino/AVR projects
Loading...
Searching...
No Matches
i2c_handler_common.h
Go to the documentation of this file.
1// Copyright 2016-2023 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 "ios.h"
30#include "future.h"
31#include "queue.h"
32#include "utilities.h"
33
34namespace i2c
35{
42 enum class DebugStatus : uint8_t
43 {
45 START = 0,
49 SLAW,
51 SLAR,
53 SEND,
55 RECV,
59 STOP,
60
62 SEND_OK,
66 RECV_OK,
69 };
70
82 using I2C_DEBUG_HOOK = void (*)(DebugStatus status, uint8_t data);
83
85 // Generic support for I2C debugging
86 template<bool IS_DEBUG_ = false, typename DEBUG_HOOK_ = I2C_DEBUG_HOOK> struct I2CDebugSupport
87 {
88 explicit I2CDebugSupport(UNUSED DEBUG_HOOK_ hook = nullptr) {}
89 void call_hook(UNUSED DebugStatus status, UNUSED uint8_t data = 0)
90 {
91 // Intentionally left empty
92 }
93 };
94 template<typename DEBUG_HOOK_> struct I2CDebugSupport<true, DEBUG_HOOK_>
95 {
96 explicit I2CDebugSupport(DEBUG_HOOK_ hook) : hook_{hook} {}
97 void call_hook(DebugStatus status, uint8_t data = 0)
98 {
99 hook_(status, data);
100 }
101 private:
102 DEBUG_HOOK_ hook_;
103 };
105
117 using I2C_STATUS_HOOK = void (*)(Status expected, Status actual);
118
120 // Generic support for I2C status hook
121 template<bool IS_STATUS_ = false, typename STATUS_HOOK_ = I2C_STATUS_HOOK> struct I2CStatusSupport
122 {
123 explicit I2CStatusSupport(UNUSED STATUS_HOOK_ hook = nullptr) {}
124 void call_hook(UNUSED Status expected, UNUSED Status actual)
125 {
126 // Intentionally left empty
127 }
128 };
129 template<typename STATUS_HOOK_> struct I2CStatusSupport<true, STATUS_HOOK_>
130 {
131 explicit I2CStatusSupport(STATUS_HOOK_ hook) : hook_{hook} {}
132 void call_hook(Status expected, Status actual)
133 {
134 hook_(expected, actual);
135 }
136 private:
137 STATUS_HOOK_ hook_;
138 };
140
146 enum class I2CErrorPolicy : uint8_t
147 {
153
161
167 };
168
170 // Type of commands in queue
171 class I2CCommandType
172 {
173 public:
174 constexpr I2CCommandType() = default;
175 constexpr I2CCommandType(const I2CCommandType&) = default;
176 I2CCommandType& operator=(const I2CCommandType&) = default;
177
178 bool is_none() const
179 {
180 return value_ == NONE;
181 }
182 bool is_write() const
183 {
184 return value_ & WRITE;
185 }
186 bool is_stop() const
187 {
188 return value_ & STOP;
189 }
190 bool is_finish() const
191 {
192 return value_ & FINISH;
193 }
194 bool is_end() const
195 {
196 return value_ & END;
197 }
198
199 private:
200 explicit constexpr I2CCommandType(uint8_t value) : value_{value} {}
201 constexpr I2CCommandType(bool write, bool stop, bool finish, bool end)
202 : value_{value(write, stop, finish, end)} {}
203
204 void add_flags(uint8_t value)
205 {
206 value_ |= value;
207 }
208
209 static constexpr uint8_t flags(bool stop, bool finish, bool end)
210 {
211 return bits::ORIF8(stop, STOP, finish, FINISH, end, END);
212 }
213
214 static constexpr const uint8_t NONE = 0;
215 static constexpr const uint8_t NOT_NONE = bits::BV8(0);
216 static constexpr const uint8_t WRITE = bits::BV8(1);
217 static constexpr const uint8_t STOP = bits::BV8(2);
218 static constexpr const uint8_t FINISH = bits::BV8(3);
219 static constexpr const uint8_t END = bits::BV8(4);
220
221 static constexpr uint8_t value(bool write, bool stop, bool finish, bool end)
222 {
223 return bits::ORIF8(true, NOT_NONE, write, WRITE, stop, STOP, finish, FINISH, end, END);
224 }
225
226 uint8_t value_ = NONE;
227
228 template<typename> friend class I2CDevice;
229 friend bool operator==(const I2CCommandType&, const I2CCommandType&);
230 friend bool operator!=(const I2CCommandType&, const I2CCommandType&);
231 };
232
233 template<typename OSTREAM> OSTREAM& operator<<(OSTREAM& out, const I2CCommandType& t)
234 {
235 if (t.is_none()) return out << F("NONE");
236 out << (t.is_write() ? F("WRITE") : F("READ"));
237 if (t.is_stop())
238 out << F("[STOP]");
239 if (t.is_finish())
240 out << F("[FINISH]");
241 if (t.is_end())
242 out << F("[END]");
243 return out;
244 }
245 bool operator==(const I2CCommandType& a, const I2CCommandType& b);
246 bool operator!=(const I2CCommandType& a, const I2CCommandType& b);
248
261 {
262 public:
264 constexpr I2CLightCommand() = default;
265 constexpr I2CLightCommand(const I2CLightCommand&) = default;
266
267 I2CCommandType type() const
268 {
269 return type_;
270 }
271 I2CCommandType& type()
272 {
273 return type_;
274 }
275 uint8_t byte_count() const
276 {
277 return byte_count_;
278 }
280
281 private:
282 constexpr I2CLightCommand(I2CCommandType type, uint8_t byte_count) : type_{type}, byte_count_{byte_count} {}
283
284 void decrement_byte_count()
285 {
286 --byte_count_;
287 }
288
289 void update_byte_count(uint8_t read_count, uint8_t write_count)
290 {
291 if (byte_count_ == 0)
292 byte_count_ = (type_.is_write() ? write_count : read_count);
293 }
294
295 // Type of this command
296 I2CCommandType type_ = I2CCommandType{};
297 // The number of remaining bytes to be read or write
298 uint8_t byte_count_ = 0;
299
300 template<typename> friend class I2CDevice;
301 template<typename, I2CMode, typename, bool, typename> friend class AbstractI2CSyncManager;
302 template<I2CMode, I2CErrorPolicy, bool, typename, bool, typename> friend class AbstractI2CAsyncManager;
303 };
304
319 template<typename T> class I2CCommand : public I2CLightCommand
320 {
321 public:
323 constexpr I2CCommand() = default;
324 constexpr I2CCommand(const I2CCommand&) = default;
325 constexpr I2CCommand& operator=(const I2CCommand&) = default;
326
327 uint8_t target() const
328 {
329 return target_;
330 }
331 T& future() const
332 {
333 return *future_;
334 }
336
337 private:
338 constexpr I2CCommand(
339 const I2CLightCommand& that, uint8_t target, T& future)
340 : I2CLightCommand{that}, target_{target}, future_{&future} {}
341
342 // Address of the target device (on 8 bits, already left-shifted)
343 uint8_t target_ = 0;
344 // A pointer to the future to be used for this command
345 T* future_ = nullptr;
346
347 template<I2CMode, I2CErrorPolicy, bool, typename, bool, typename> friend class AbstractI2CAsyncManager;
348 };
349
351 template<typename OSTREAM, typename T> OSTREAM& operator<<(OSTREAM& out, const I2CCommand<T>& c)
352 {
353 out << '{' << c.type() << ',';
355 out << streams::hex << c.target() << '}';
356 return out;
357 }
359
361 // Specific traits for I2CMode
362 template<I2CMode MODE_ = I2CMode::STANDARD> struct I2CMode_trait
363 {
364 static constexpr I2CMode MODE = MODE_;
365 static constexpr uint32_t RATE = 100'000UL;
366 static constexpr uint32_t FREQUENCY = ((F_CPU / RATE) - 16UL) / 2;
367 static constexpr const uint8_t T_HD_STA = utils::calculate_delay1_count(4.0);
368 static constexpr const uint8_t T_LOW = utils::calculate_delay1_count(4.7);
369 static constexpr const uint8_t T_HIGH = utils::calculate_delay1_count(4.0);
370 static constexpr const uint8_t T_SU_STA = utils::calculate_delay1_count(4.7);
371 static constexpr const uint8_t T_SU_STO = utils::calculate_delay1_count(4.0);
372 static constexpr const uint8_t T_BUF = utils::calculate_delay1_count(4.7);
373 static constexpr const uint8_t DELAY_AFTER_STOP = utils::calculate_delay1_count(4.0 + 4.7);
374 };
375 template<> struct I2CMode_trait<I2CMode::FAST>
376 {
377 static constexpr I2CMode MODE = I2CMode::FAST;
378 static constexpr uint32_t RATE = 400'000UL;
379 static constexpr uint32_t FREQUENCY = ((F_CPU / RATE) - 16UL) / 2;
380 static constexpr const uint8_t T_HD_STA = utils::calculate_delay1_count(0.6);
381 static constexpr const uint8_t T_LOW = utils::calculate_delay1_count(1.3);
382 static constexpr const uint8_t T_HIGH = utils::calculate_delay1_count(0.6);
383 static constexpr const uint8_t T_SU_STA = utils::calculate_delay1_count(0.6);
384 static constexpr const uint8_t T_SU_STO = utils::calculate_delay1_count(0.6);
385 static constexpr const uint8_t T_BUF = utils::calculate_delay1_count(1.3);
386 static constexpr const uint8_t DELAY_AFTER_STOP = utils::calculate_delay1_count(0.6 + 1.3);
387 };
389
418 template<typename ARCH_HANDLER_, I2CMode MODE_, typename STATUS_HOOK_, bool HAS_DEBUG_, typename DEBUG_HOOK_>
420 {
421 protected:
423 using ARCH_HANDLER = ARCH_HANDLER_;
424 using MODE_TRAIT = I2CMode_trait<MODE_>;
425 using I2C_TRAIT = board_traits::TWI_trait;
426 using REG8 = board_traits::REG8;
427 using DEBUG = I2CDebugSupport<HAS_DEBUG_, DEBUG_HOOK_>;
429
430 public:
436
441 template<typename OUT, typename IN> using FUTURE = future::FakeFuture<OUT, IN>;
442
450 void begin()
451 {
452 synchronized begin_();
453 }
454
461 void end()
462 {
463 synchronized end_();
464 }
465
473 void begin_()
474 {
475 handler_.begin_();
476 }
477
484 void end_()
485 {
486 handler_.end_();
487 }
488
489 protected:
491 explicit AbstractI2CSyncManager(
492 STATUS_HOOK_ status_hook = nullptr,
493 DEBUG_HOOK_ debug_hook = nullptr)
494 : handler_{status_hook}, debug_hook_{debug_hook} {}
496
497 private:
498 bool ensure_num_commands_(UNUSED uint8_t num_commands) const
499 {
500 return true;
501 }
502
503 bool push_command_(
504 I2CLightCommand command, uint8_t target, ABSTRACT_FUTURE& future)
505 {
506 // Check command is not empty
507 const I2CCommandType type = command.type();
508 if (type.is_none()) return true;
509 if (clear_commands_) return false;
510 // Execute command immediately, from start to optional stop
511 bool ok = (no_stop_ ? exec_repeat_start_() : exec_start_());
512 stopped_already_ = false;
513 if (!ok)
514 return handle_error(future);
515
516 if (type.is_write())
517 {
518 // Send device address
519 if (!exec_send_slaw_(target))
520 return handle_error(future);
521 // Send content
522 while (command.byte_count() > 0)
523 // In case of a NACK on data writing, we check if it is not last byte
524 if ((!exec_send_data_(command, future)) && (command.byte_count() > 0))
525 return handle_error(future);
526 }
527 else
528 {
529 // Send device address
530 if (!exec_send_slar_(target))
531 return handle_error(future);
532 // Receive content
533 while (command.byte_count() > 0)
534 if (!exec_receive_data_(command, future))
535 return handle_error(future);
536 }
537
538 // Check if we must force finish the future
539 if (type.is_finish())
540 future.set_future_finish_();
541 // Check if we must force a STOP
542 if (type.is_stop())
543 exec_stop_();
544 // Ensure STOP is generated or not depending on latest command executed
545 no_stop_ = !type.is_stop();
546 return true;
547 }
548
549 void last_command_pushed_()
550 {
551 // Check if previously executed command already did a STOP (and needed one)
552 if ((!no_stop_) && (!stopped_already_) && (!clear_commands_))
553 {
554 exec_stop_();
555 no_stop_ = false;
556 }
557 clear_commands_ = false;
558 stopped_already_ = false;
559 }
560
561 // Low-level methods to handle the bus in an asynchronous way
562 bool exec_start_()
563 {
564 debug_hook_.call_hook(DebugStatus::START);
565 return handler_.exec_start_();
566 }
567
568 bool exec_repeat_start_()
569 {
570 debug_hook_.call_hook(DebugStatus::REPEAT_START);
571 return handler_.exec_repeat_start_();
572 }
573
574 bool exec_send_slar_(uint8_t target)
575 {
576 debug_hook_.call_hook(DebugStatus::SLAR, target);
577 return handler_.exec_send_slar_(target);
578 }
579
580 bool exec_send_slaw_(uint8_t target)
581 {
582 debug_hook_.call_hook(DebugStatus::SLAW, target);
583 return handler_.exec_send_slaw_(target);
584 }
585
586 bool exec_send_data_(I2CLightCommand& command, ABSTRACT_FUTURE& future)
587 {
588 // Determine next data byte
589 uint8_t data = 0;
590 bool ok = future.get_storage_value_(data);
591 debug_hook_.call_hook(DebugStatus::SEND, data);
592 debug_hook_.call_hook(ok ? DebugStatus::SEND_OK : DebugStatus::SEND_ERROR);
593 // This should only happen if there are 2 concurrent consumers for that Future
594 if (!ok)
595 {
596 future.set_future_error_(errors::EILSEQ);
597 return false;
598 }
599 command.decrement_byte_count();
600 return handler_.exec_send_data_(data);
601 }
602
603 bool exec_receive_data_(I2CLightCommand& command, ABSTRACT_FUTURE& future)
604 {
605 // Is this the last byte to receive?
606 const bool last_byte = (command.byte_count() == 1);
607 const DebugStatus debug = (last_byte ? DebugStatus::RECV_LAST : DebugStatus::RECV);
608 debug_hook_.call_hook(debug);
609
610 uint8_t data;
611 if (handler_.exec_receive_data_(last_byte, data))
612 {
613 const bool ok = future.set_future_value_(data);
614 debug_hook_.call_hook(ok ? DebugStatus::RECV_OK : DebugStatus::RECV_ERROR, data);
615 // This should only happen in case there are 2 concurrent providers for this future
616 if (ok)
617 {
618 command.decrement_byte_count();
619 }
620 else
621 {
622 future.set_future_error_(errors::EILSEQ);
623 }
624 return ok;
625 }
626 return false;
627 }
628
629 void exec_stop_()
630 {
631 debug_hook_.call_hook(DebugStatus::STOP);
632 handler_.exec_stop_();
633 // If so then delay 4.0us + 4.7us (100KHz) or 0.6us + 1.3us (400KHz)
634 // (ATMEGA328P datasheet 29.7 Tsu;sto + Tbuf)
635 _delay_loop_1(MODE_TRAIT::DELAY_AFTER_STOP);
636 stopped_already_ = true;
637 }
638
639 // This method is called when an error has occurred
640 bool handle_error(ABSTRACT_FUTURE& future)
641 {
642 if (future.status() != future::FutureStatus::ERROR)
643 // The future must be marked as error
644 future.set_future_error_(errors::EPROTO);
645
646 // Clear command belonging to the same transaction (i.e. same future)
647 // ie forbid any new command until last command (add new flag for that)
648 clear_commands_ = true;
649 // In case of an error, immediately send a STOP condition
650 exec_stop_();
651 return false;
652 }
653
654 // Flags for storing I2C transaction operation state
655 bool no_stop_ = false;
656 bool clear_commands_ = false;
657 bool stopped_already_ = false;
658
659 ARCH_HANDLER handler_;
660 DEBUG debug_hook_;
661
662 template<typename> friend class I2CDevice;
663 };
664
666 // Specific traits for I2C Manager
667 template<typename T> struct I2CManager_trait
668 {
669 static constexpr bool IS_I2CMANAGER = false;
670 static constexpr bool IS_ASYNC = false;
671 static constexpr bool IS_DEBUG = false;
672 static constexpr bool IS_STATUS = false;
673 static constexpr I2CMode MODE = I2CMode::STANDARD;
674 };
675
676 template<bool IS_ASYNC_, bool IS_STATUS_, bool IS_DEBUG_, I2CMode MODE_>
677 struct I2CManager_trait_impl
678 {
679 static constexpr bool IS_I2CMANAGER = true;
680 static constexpr bool IS_ASYNC = IS_ASYNC_;
681 static constexpr bool IS_DEBUG = IS_DEBUG_;
682 static constexpr bool IS_STATUS = IS_STATUS_;
683 static constexpr I2CMode MODE = MODE_;
684 };
686}
687
688#endif /* I2C_HANDLER_COMMON_HH */
Useful bits manipulation utilities.
Base class for all FakeFutures.
Definition: future.h:1142
Actual FakeFuture, it has the exact same API as Future and can be used in lieu of Future.
Definition: future.h:1300
Abstract asynchronous I2C Manager.
Abstract synchronous I2C Manager for all MCU architectures.
void begin()
Prepare and enable the MCU for I2C transmission.
void end()
Disable MCU I2C transmission.
future::AbstractFakeFuture ABSTRACT_FUTURE
The abstract base class of all futures to be defined for this I2C Manager.
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.
Base class for all I2C devices.
Definition: i2c_device.h:84
Light atomic I2C command as prepared by an I2C device.
static constexpr fmtflags basefield
Bitmask constant used with setf(fmtflags, fmtflags) when changing the output base format.
Definition: ios.h:227
static constexpr fmtflags hex
Read or write integral values using hexadecimal (0..9,A..F) base format.
Definition: ios.h:218
#define UNUSED
Specific GCC attribute to declare an argument or variable unused, so that the compiler does not emit ...
Definition: defines.h:45
#define F(ptr)
Force string constant to be stored as flash storage.
Definition: flash.h:150
Utility API to handle the concept of futures.
I2C API common definitions.
C++-like std::iostream facilities.
@ NONE
No interrupt will be generated by the Anolog Comparator.
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
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.h:312
@ ERROR
The status of a Future once a value provider has reported an error to it.
DEBUG
Indicate what in I2C protocol shall be debugged.
Definition: i2c_debug.h:102
Define API to define and manage I2C devices.
Definition: i2c.h:51
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.
I2CErrorPolicy
I2C Manager policy to use in case of an error during I2C transaction.
@ CLEAR_ALL_COMMANDS
In case of an error during I2C transaction, then all I2CCommand currently in queue will be removed.
@ CLEAR_TRANSACTION_COMMANDS
In case of an error during I2C transaction, then all pending I2CCommand of the current transaction wi...
@ DO_NOTHING
Do nothing at all in case of an error; useful only with a synchronous I2C Manager.
I2CMode
I2C available transmission modes.
Definition: i2c.h:168
@ FAST
I2C Fast mode, less than 400KHz.
Status
Transmission status codes.
Definition: i2c.h:67
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:531
Utility API to handle ring-buffer queue containers.
General utilities API that have broad application in programs.