FastArduino v1.10
C++ library to build fast but small Arduino/AVR projects
Loading...
Searching...
No Matches
i2c_device_utilities.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
21#ifndef I2C_DEVICE_UTILITIES_H
22#define I2C_DEVICE_UTILITIES_H
23
24#include "flash.h"
25#include "functors.h"
26#include "future.h"
27#include "initializer_list.h"
28#include "iterator.h"
29#include "time.h"
30#include "utilities.h"
31#include "i2c_device.h"
32
33namespace i2c
34{
36 // Internal type used by WriteRegisterFuture
37 template<typename T, typename FUNCTOR = functor::Identity<T>> class WriteContent
38 {
39 using ARG_TYPE = typename FUNCTOR::ARG_TYPE;
40 public:
41 WriteContent() = default;
42 WriteContent(uint8_t reg, const ARG_TYPE& value)
43 : register_{reg}, value_{functor::Functor<FUNCTOR>::call(value)} {}
44
45 uint8_t reg() const
46 {
47 return register_;
48 }
49
50 private:
51 uint8_t register_{};
52 T value_{};
53 };
55
81 template<typename MANAGER, typename T, typename FUNCTOR = functor::Identity<T>>
82 class ReadRegisterFuture: public MANAGER::template FUTURE<T, uint8_t>
83 {
84 using ARG_TYPE = typename FUNCTOR::ARG_TYPE;
85 using PARENT = typename MANAGER::template FUTURE<T, uint8_t>;
86
87 protected:
89 using ABSTRACT_FUTURE = typename MANAGER::ABSTRACT_FUTURE;
90
91 uint8_t reg() const
92 {
93 return this->get_input();
94 }
96
97 public:
105 explicit ReadRegisterFuture(uint8_t reg,
107 : PARENT{reg, notification} {}
109 bool get(T& result)
110 {
111 ARG_TYPE temp;
112 if (!PARENT::get(temp)) return false;
113 result = functor::Functor<FUNCTOR>::call(temp);
114 return true;
115 }
117 };
118
153 template<typename MANAGER, uint8_t REGISTER, typename T, typename FUNCTOR = functor::Identity<T>>
154 class TReadRegisterFuture: public ReadRegisterFuture<MANAGER, T, FUNCTOR>
155 {
157 public:
166 : PARENT{REGISTER, notification} {}
168 void reset_()
169 {
170 PARENT::reset_(REGISTER);
171 }
173 };
174
201 template<typename MANAGER, typename T, typename FUNCTOR = functor::Identity<T>>
202 class WriteRegisterFuture: public MANAGER::template FUTURE<void, WriteContent<T, FUNCTOR>>
203 {
204 using CONTENT = WriteContent<T, FUNCTOR>;
205 using ARG_TYPE = typename FUNCTOR::ARG_TYPE;
206 using PARENT = typename MANAGER::template FUTURE<void, CONTENT>;
207
208 protected:
210 using ABSTRACT_FUTURE = typename MANAGER::ABSTRACT_FUTURE;
211
212 uint8_t reg() const
213 {
214 return this->get_input().reg();
215 }
217
218 public:
228 uint8_t reg, const ARG_TYPE& value,
230 : PARENT{CONTENT{reg, value}, notification} {}
231 };
232
268 template<typename MANAGER, uint8_t REGISTER, typename T, typename FUNCTOR = functor::Identity<T>>
269 class TWriteRegisterFuture: public WriteRegisterFuture<MANAGER, T, FUNCTOR>
270 {
271 using ARG_TYPE = typename FUNCTOR::ARG_TYPE;
272 using CONTENT = WriteContent<T, FUNCTOR>;
274 public:
282 explicit TWriteRegisterFuture(const ARG_TYPE& value = ARG_TYPE{},
284 : PARENT{REGISTER, value, notification} {}
286 void reset_(const T& input = T{})
287 {
288 PARENT::reset_(CONTENT{REGISTER, input});
289 }
291 };
292
294 template<typename T>
295 class WriteMultiContentBase
296 {
297 protected:
298 WriteMultiContentBase() = default;
299
300 struct Pair
301 {
302 Pair() = default;
303 Pair(uint8_t reg, T value = T{}) : reg_{reg}, value_{value} {}
304 uint8_t reg_ = 0;
305 T value_{};
306 };
307
308 static void init(Pair* content, std::initializer_list<T> values)
309 {
310 const T* val_ptr = values.begin();
311 while (val_ptr != values.end())
312 {
313 (*content).value_ = *val_ptr++;
314 ++content;
315 }
316 }
317 };
318
319 template<typename T, uint8_t... REGISTERS>
320 class WriteMultiContent : public WriteMultiContentBase<T>
321 {
322 using PARENT = WriteMultiContentBase<T>;
323 public:
324 explicit constexpr WriteMultiContent(std::initializer_list<T> values) : content_{ REGISTERS... }
325 {
326 PARENT::init(content_, values);
327 }
328
329 T value(uint8_t index) const
330 {
331 return content_[index].value_;
332 }
333
334 private:
335 typename PARENT::Pair content_[sizeof...(REGISTERS)];
336 };
338
358 template<typename MANAGER, typename T, uint8_t... REGISTERS>
359 class TWriteMultiRegisterFuture: public MANAGER::template FUTURE<void, WriteMultiContent<T, REGISTERS...>>
360 {
361 using CONTENT = WriteMultiContent<T, REGISTERS...>;
362 using PARENT = typename MANAGER::template FUTURE<void, CONTENT>;
363
364 protected:
366 using ABSTRACT_FUTURE = typename MANAGER::ABSTRACT_FUTURE;
368
369 public:
374 static constexpr uint8_t NUM_WRITES = sizeof...(REGISTERS);
375
380 static constexpr uint8_t WRITE_SIZE = sizeof(T) + 1;
381
395 : PARENT{CONTENT{values}, notification} {}
397 void reset_(std::initializer_list<T> values)
398 {
399 PARENT::reset_(CONTENT{values});
400 }
402 };
403
405 // Forward declaration of I2CDevice
406 template<typename MANAGER> class I2CDevice;
407 template<typename MANAGER> bool await_same_future_group(
408 I2CDevice<MANAGER>& device,const uint8_t* buffer, uint8_t size);
410
412 template<typename MANAGER> class I2CFutureHelper
413 {
414 static_assert(I2CManager_trait<MANAGER>::IS_I2CMANAGER, "MANAGER must be an I2C Manager");
415
416 protected:
417 using DEVICE = I2CDevice<MANAGER>;
418 using ABSTRACT_FUTURE = typename MANAGER::ABSTRACT_FUTURE;
419
420 I2CFutureHelper() = default;
421
422 // Check launch_commands() return and update own status if needed
423 bool check_error(int error, ABSTRACT_FUTURE& target)
424 {
425 if (error == 0) return true;
426 target.set_future_error_(error);
427 return false;
428 }
429
430 bool check_status(const ABSTRACT_FUTURE& source, future::FutureStatus status, ABSTRACT_FUTURE& target)
431 {
432 // First check that current future was executed successfully
433 if (status != future::FutureStatus::READY)
434 {
435 target.set_future_error_(source.error());
436 return false;
437 }
438 return true;
439 }
440
441 static constexpr I2CLightCommand read(uint8_t read_count = 0, bool finish_future = false, bool stop = false)
442 {
443 return DEVICE::read(read_count, finish_future, stop);
444 }
445
446 static constexpr I2CLightCommand write(uint8_t write_count = 0, bool finish_future = false, bool stop = false)
447 {
448 return DEVICE::write(write_count, finish_future, stop);
449 }
450
451 int launch_commands(ABSTRACT_FUTURE& future, utils::range<I2CLightCommand> commands)
452 {
453 return device_->launch_commands(future, commands);
454 }
455
456 void set_device(DEVICE& device)
457 {
458 device_ = &device;
459 }
460
461 DEVICE& device()
462 {
463 return *device_;
464 }
465
466 private:
467 // The device that uses this future
468 DEVICE* device_ = nullptr;
469 friend DEVICE;
470 };
472
474 template<typename MANAGER> class AbstractI2CFuturesGroup :
475 public future::AbstractFuturesGroup<typename MANAGER::ABSTRACT_FUTURE>,
476 public I2CFutureHelper<MANAGER>
477 {
478 using HELPER = I2CFutureHelper<MANAGER>;
480
481 protected:
482 using ABSTRACT_FUTURE = typename MANAGER::ABSTRACT_FUTURE;
483
484 explicit AbstractI2CFuturesGroup(future::FutureNotification notification = future::FutureNotification::NONE)
485 : GROUP{notification} {}
486
487 // Check launch_commands() return and update own status if needed
488 bool check_error(int error)
489 {
490 return HELPER::check_error(error, *this);
491 }
492
493 bool check_status(const ABSTRACT_FUTURE& source, future::FutureStatus status)
494 {
495 return HELPER::check_status(source, status, *this);
496 }
497 };
499
552 template<typename MANAGER> class I2CFuturesGroup :
553 public AbstractI2CFuturesGroup<MANAGER>
554 {
555 using PARENT = AbstractI2CFuturesGroup<MANAGER>;
556 using MANAGER_TRAIT = I2CManager_trait<MANAGER>;
557 using ABSTRACT_FUTURE = typename PARENT::ABSTRACT_FUTURE;
558
559 protected:
568 I2CFuturesGroup(ABSTRACT_FUTURE** futures, uint8_t size,
570 : PARENT{notification}, futures_{futures}, size_{size} {}
571
586 bool start(typename PARENT::DEVICE& device)
587 {
588 PARENT::set_device(device);
589 if (MANAGER_TRAIT::IS_ASYNC)
590 {
591 return next_future();
592 }
593 else
594 {
595 // In sync mode, we replace recursive calls (generated by on_status_change()) by a loop
596 while (index_ != size_)
597 {
598 if (!next_future()) return false;
599 }
600 return true;
601 }
602 }
603
605 void on_status_change(const ABSTRACT_FUTURE& future, future::FutureStatus status)
606 {
607 // First check if it is one of our futures!
608 if (!is_own_future(future)) return;
609 PARENT::on_status_change_pre_step(future, status);
610 // In sync mode, we must avoid recursive calls generated by on_status_change()!
611 if (MANAGER_TRAIT::IS_ASYNC)
612 {
613 // First check that current future was executed successfully
614 if (status == future::FutureStatus::READY)
615 next_future();
616 }
617 }
619
620 private:
621 bool is_own_future(const ABSTRACT_FUTURE& future) const
622 {
623 uint8_t i = size_;
624 ABSTRACT_FUTURE** ptr = futures_;
625 while (i != 0)
626 {
627 if (*ptr == &future) return true;
628 ++ptr;
629 --i;
630 }
631 return false;
632 }
633
634 bool next_future()
635 {
636 if (index_ == size_)
637 // Future is finished
638 return false;
639
640 ABSTRACT_FUTURE& future = *futures_[index_++];
641 // Check if future has read, write or both
642 const bool stop = (index_ == size_);
643 const bool read = future.get_future_value_size_();
644 const bool write = future.get_storage_value_size_();
645 int error = 0;
646 if (read && write)
647 {
648 error = PARENT::launch_commands(future, {PARENT::write(), PARENT::read(0, false, stop)});
649 }
650 else if (read)
651 {
652 error = PARENT::launch_commands(future, {PARENT::read(0, false, stop)});
653 }
654 else if (write)
655 {
656 error = PARENT::launch_commands(future, {PARENT::write(0, false, stop)});
657 }
658 else
659 {
660 //FIXME we consider that any other future is an I2CFuturesGroup, which might not always be correct!
661 I2CFuturesGroup& group = static_cast<I2CFuturesGroup&>(future);
662 if (!group.start(PARENT::device()))
663 error = errors::EILSEQ;
664 }
665 return PARENT::check_error(error);
666 }
667
668 ABSTRACT_FUTURE** futures_;
669 uint8_t size_;
670 uint8_t index_ = 0;
671
673 };
674
689 template<typename MANAGER> class I2CSameFutureGroup : public AbstractI2CFuturesGroup<MANAGER>
690 {
691 using PARENT = AbstractI2CFuturesGroup<MANAGER>;
692 using MANAGER_TRAIT = I2CManager_trait<MANAGER>;
693 using ABSTRACT_FUTURE = typename PARENT::ABSTRACT_FUTURE;
695 using CONTENT = WriteContent<uint8_t>;
696 static constexpr uint8_t FUTURE_SIZE = F::IN_SIZE;
697
698 public:
708 I2CSameFutureGroup(uint16_t address, uint8_t size,
710 : PARENT{notification}, address_{address}, size_{size}
711 {
712 PARENT::init({&future_}, size / FUTURE_SIZE);
714 }
715
718 {
720 }
722
737 bool start(typename PARENT::DEVICE& device)
738 {
739 PARENT::set_device(device);
740 if (MANAGER_TRAIT::IS_ASYNC)
741 {
742 return next_future();
743 }
744 else
745 {
746 // In sync mode, we replace recursive calls (generated by on_status_change()) by a loop
747 while (size_)
748 {
749 if (!next_future()) return false;
750 }
751 return true;
752 }
753 }
754
755 private:
756 bool next_future()
757 {
758 if (size_ == 0)
759 // Future is finished already
760 return false;
761
762 uint8_t reg = next_byte();
763 uint8_t val = next_byte();
764 const bool stop = (size_ == 0);
765 future_.reset_(CONTENT{reg, val});
766 return PARENT::check_error(
767 PARENT::launch_commands(future_, {PARENT::write(0, false, stop)}));
768 }
769
770 // Get the next byte, from the flash
771 uint8_t next_byte()
772 {
773 uint8_t data = 0;
774 --size_;
775 return flash::read_flash(address_++, data);
776 }
777
778 void on_status_change(const ABSTRACT_FUTURE& future, future::FutureStatus status)
779 {
780 if (&future != &future_) return;
781 PARENT::on_status_change_pre_step(future, status);
782 // In sync mode, we must avoid recursive calls generated by on_status_change()!
783 if (MANAGER_TRAIT::IS_ASYNC)
784 {
785 // First check that current future was executed successfully
786 if (status == future::FutureStatus::READY)
787 next_future();
788 }
789 }
790
791 // Address of flah mempry holding information about bytes to write
792 uint16_t address_;
793 uint8_t size_;
794 // The future reused for all writes
795 F future_{0, 0, future::FutureNotification::STATUS};
796
798 friend bool await_same_future_group<MANAGER>(I2CDevice<MANAGER>&, const uint8_t*, uint8_t);
799 };
800
818 template<typename MANAGER>
819 bool await_same_future_group(I2CDevice<MANAGER>& device,const uint8_t* buffer, uint8_t size)
820 {
821 I2CSameFutureGroup<MANAGER> future{uint16_t(buffer), size};
822 if (!future.start(device)) return false;
823 return (future.await() == future::FutureStatus::READY);
824 }
825
826#ifdef EXPERIMENTAL_API
855 namespace actions
856 {
857 static constexpr uint8_t END = 0x00;
858 static constexpr uint8_t WRITE = 0x10;
859 static constexpr uint8_t READ = 0x20;
860 static constexpr uint8_t MARKER = 0x30;
861 static constexpr uint8_t LOOP = 0x31;
862 static constexpr uint8_t INCLUDE = 0x40;
863
864 static constexpr uint8_t STOP_MASK = 0x80;
865 static constexpr uint8_t ACTION_MASK = 0x70;
866 static constexpr uint8_t COUNT_MASK = 0x0F;
867
868 static constexpr uint8_t write(uint8_t count, bool stop = false)
869 {
870 return WRITE | (count & COUNT_MASK) | (stop ? STOP_MASK : 0x00);
871 }
872 static constexpr uint8_t read(uint8_t count, bool stop = false)
873 {
874 return READ | (count & COUNT_MASK) | (stop ? STOP_MASK : 0x00);
875 }
876 static constexpr bool is_write(uint8_t action)
877 {
878 return (action & ACTION_MASK) == WRITE;
879 }
880 static constexpr bool is_read(uint8_t action)
881 {
882 return (action & ACTION_MASK) == READ;
883 }
884 static constexpr bool is_stop(uint8_t action)
885 {
886 return action & STOP_MASK;
887 }
888 static constexpr uint8_t count(uint8_t action)
889 {
890 return action & COUNT_MASK;
891 }
892 }
893
894 //FIXME wont't work in SYNC mode (too many recursion calls through future listeners)!!!
895 template<typename MANAGER> class ComplexI2CFuturesGroup : public AbstractI2CFuturesGroup<MANAGER>
896 {
897 using PARENT = AbstractI2CFuturesGroup<MANAGER>;
898 using MANAGER_TRAIT = I2CManager_trait<MANAGER>;
899 using ABSTRACT_FUTURE = typename PARENT::ABSTRACT_FUTURE;
900
901 protected:
902 ComplexI2CFuturesGroup(uint16_t flash_config,
904 : PARENT{notification}, address_{flash_config} {}
905
906 enum class ProcessAction : uint8_t
907 {
908 DONE,
909 MARKER,
910 INCLUDE,
911 READ,
912 WRITE
913 };
914
915 //TODO Find better API to do the most in superclass instead of subclass (and avoid templates)
916 ProcessAction process_action()
917 {
918 while ((action_ = next_byte()) == actions::LOOP)
919 {
920 // Store loop address for later use and skip to next action
921 loop_ = address_;
922 }
923 if (action_ == actions::END)
924 {
925 // Future is finished
926 PARENT::set_future_finish_();
927 return ProcessAction::DONE;
928 }
929 if (action_ == actions::MARKER) return ProcessAction::MARKER;
930 if (action_ == actions::INCLUDE) return ProcessAction::INCLUDE;
931 if (actions::is_read(action_)) return ProcessAction::READ;
932 if (actions::is_write(action_)) return ProcessAction::WRITE;
933
934 // Error: unrecognized action code
935 PARENT::check_error(errors::EILSEQ);
936 return ProcessAction::DONE;
937 }
938
939 void loop()
940 {
941 address_ = loop_;
942 }
943
944 // Get the next byte, from the flash
945 uint8_t next_byte()
946 {
947 uint8_t data = 0;
948 return flash::read_flash(address_++, data);
949 }
950
951 bool is_stop() const
952 {
953 return actions::is_stop(action_);
954 }
955
956 uint8_t count() const
957 {
958 return actions::count(action_);
959 }
960
961 private:
962 // Information, read from flash, about futures to create and launch
963 uint16_t address_;
964 // Address to restart from in case of a loop
965 uint16_t loop_ = 0;
966 // Current action code
967 uint8_t action_ = 0;
968 };
969#endif
970}
971
972#endif /* I2C_DEVICE_UTILITIES_H */
Abstract class to allow aggregation of several futures.
Definition: future.h:1576
Base class for all I2C devices.
Definition: i2c_device.h:84
Abstract class to allow aggregation of several futures in relation to I2C transactions.
bool start(typename PARENT::DEVICE &device)
Start the I2C transactions needed by this group of futures.
I2CFuturesGroup(ABSTRACT_FUTURE **futures, uint8_t size, future::FutureNotification notification=future::FutureNotification::NONE)
Called by subclass constructor, this constructs a new I2CFuturesGroup instance with the provided list...
Class to allow dynamic creation of futures from values stored in flash memory, leading to launch of I...
I2CSameFutureGroup(uint16_t address, uint8_t size, future::FutureNotification notification=future::FutureNotification::NONE)
Construct a new I2CSameFutureGroup from an array of bytes stored in flash memory.
bool start(typename PARENT::DEVICE &device)
Start the I2C transactions needed by this group of futures.
General Future that can be used to read an I2C device register.
ReadRegisterFuture(uint8_t reg, future::FutureNotification notification=future::FutureNotification::NONE)
Create a ReadRegisterFuture future for a given device register reg.
Generic Future that can be used to read an I2C device register.
TReadRegisterFuture(future::FutureNotification notification=future::FutureNotification::NONE)
Create a TReadRegisterFuture future.
Generic Future that can be used to write to several I2C device registers.
static constexpr uint8_t WRITE_SIZE
The number of bytes to write for each register command in the I2C transaction.
TWriteMultiRegisterFuture(std::initializer_list< T > values, future::FutureNotification notification=future::FutureNotification::NONE)
Create a TWriteMultiRegisterFuture future.
static constexpr uint8_t NUM_WRITES
Number of write commands to use inside the complete I2C transaction in order to perform this multiple...
Generic Future that can be used to write to an I2C device register.
TWriteRegisterFuture(const ARG_TYPE &value=ARG_TYPE{}, future::FutureNotification notification=future::FutureNotification::NONE)
Create a TWriteRegisterFuture future.
General Future that can be used to write to an I2C device register.
WriteRegisterFuture(uint8_t reg, const ARG_TYPE &value, future::FutureNotification notification=future::FutureNotification::NONE)
Create a WriteRegisterFuture future for a given device register reg.
C++ standard support for "initializer_list".
constexpr const T * begin() const
The first element of this initializer_list.
constexpr const T * end() const
One past the last element of this initializer_list.
Iterable class that can embed arrays or initializer lists through implicit conversion.
Definition: iterator.h:51
Flash memory utilities.
Useful functors that can be used as template arguments, particularly in I2C device utilities.
Utility API to handle the concept of futures.
#define DECL_FUTURE_LISTENERS_FRIEND
This macro shall be used in a class containing a private callback method void on_status_change(const ...
Definition: future.h:178
I2C Device API.
Utilities to convert arrays into an iterable (usable if for x: list construct).
constexpr const int EILSEQ
Illegal byte sequence.
Definition: errors.h:65
T * read_flash(uint16_t address, T *buffer, uint8_t size)
Read flash memory content at given address into buffer.
Definition: flash.h:50
This namespace defines a few useful functors.
Definition: functors.h:38
Contains the API around Future implementation.
Definition: future.h:312
FutureStatus
Status of a Future.
Definition: future.h:321
@ READY
The status of a Future once its output value has been fully set by a provider.
FutureNotification
Notification(s) dispatched by a Future.
Definition: future.h:405
@ STATUS
Notification is dispatched whenever the Future status changes.
@ NONE
No notification is dispatched by the Future.
Define API to define and manage I2C devices.
Definition: i2c.h:51
bool await_same_future_group(I2CDevice< MANAGER > &device, const uint8_t *buffer, uint8_t size)
Helper function that creates a I2CSameFutureGroup instance for the provided flash array,...
void register_handler(Handler &handler)
Register a class instance containing methods that shall be called back by an ISR.
Definition: interrupts.h:185
void unregister_handler(Handler &handler)
Unregister a class instance that was previously registered with interrupt::register_handler.
Definition: interrupts.h:207
void init()
This function must be called once in your program, before any use of an SPI device.
Definition: spi.cpp:20
Utility to instantiate and execute a functor from its type.
Definition: functors.h:219
Simple time utilities.
General utilities API that have broad application in programs.