FastArduino v1.10
C++ library to build fast but small Arduino/AVR projects
Loading...
Searching...
No Matches
i2c_device.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_HH
22#define I2C_DEVICE_HH
23
24#include "initializer_list.h"
25#include "iterator.h"
26#include "errors.h"
27#include "i2c.h"
28#include "future.h"
29#include "i2c_handler.h"
30#include "utilities.h"
31
32namespace i2c
33{
35 // Forward declaration
36 template<typename MANAGER> class I2CFutureHelper;
38
39 // Trick to support MODE as I2CDevice constructor template argument (deducible)
41 template<I2CMode MODE> struct Mode {};
43
45 static constexpr Mode I2C_STANDARD = Mode<I2CMode::STANDARD>{};
47 static constexpr Mode I2C_FAST = Mode<I2CMode::FAST>{};
48
49 // Internal (private) utility class to start/end synchronization or do nothing at all
51 template<bool DISABLE = true> class DisableInterrupts
52 {
53 DisableInterrupts()
54 {
55 cli();
56 }
57 ~DisableInterrupts()
58 {
59 SREG = sreg_;
60 }
61
62 const uint8_t sreg_ = SREG;
63
64 template<typename> friend class I2CDevice;
65 };
66 template<> class DisableInterrupts<false>
67 {
68 DisableInterrupts() = default;
69
70 template<typename> friend class I2CDevice;
71 };
73
82 template<typename MANAGER_>
84 {
85 public:
87 using MANAGER = MANAGER_;
88
89 private:
90 using MANAGER_TRAIT = I2CManager_trait<MANAGER>;
91 // Ensure MANAGER is an accepted I2C Manager type
92 static_assert(
93 MANAGER_TRAIT::IS_I2CMANAGER, "MANAGER_ must be a valid I2C Manager type");
94
95 protected:
101 using ABSTRACT_FUTURE = typename MANAGER::ABSTRACT_FUTURE;
102
108 template<typename OUT, typename IN> using FUTURE = typename MANAGER::template FUTURE<OUT, IN>;
109
127 template<I2CMode MODE>
128 I2CDevice(MANAGER& manager, uint8_t device, UNUSED Mode<MODE> mode, bool auto_stop)
129 : device_{device}, handler_{manager}, auto_stop_flags_{I2CCommandType::flags(auto_stop, true, true)}
130 {
131 // Ensure that MANAGER I2C mode is compliant with the best mode for this device
132 static_assert((MODE == I2CMode::FAST) || (MODE == MANAGER_TRAIT::MODE),
133 "MANAGER_ I2CMode must be compliant with this device best mode");
134 }
135
136 I2CDevice(const I2CDevice&) = delete;
137 I2CDevice& operator=(const I2CDevice&) = delete;
138
146 void set_device(uint8_t device)
147 {
148 device_ = device;
149 }
150
173 static constexpr I2CLightCommand read(uint8_t read_count = 0, bool finish_future = false, bool stop = false)
174 {
175 const I2CCommandType type{false, stop, finish_future, false};
176 return I2CLightCommand{type, read_count};
177 }
178
202 static constexpr I2CLightCommand write(uint8_t write_count = 0, bool finish_future = false, bool stop = false)
203 {
204 const I2CCommandType type{true, stop, finish_future, false};
205 return I2CLightCommand{type, write_count};
206 }
207
245 {
246 uint8_t num_commands = commands.size();
247 if (num_commands == 0) return errors::EINVAL;
248 {
249 // Truly asynchronous mode (ATmega only)
250 // The whole code block in launch_commands() must be synchronized
251 UNUSED auto outer_sync = DisableInterrupts<MANAGER_TRAIT::IS_ASYNC>{};
252 uint8_t max_read;
253 uint8_t max_write;
254 {
255 // Synchronous mode
256 // Only the next few method calls shall be synchronized
257 UNUSED auto inner_sync = DisableInterrupts<!MANAGER_TRAIT::IS_ASYNC>{};
258 // pre-conditions (must be synchronized)
259 if (!handler_.ensure_num_commands_(num_commands)) return errors::EAGAIN;
260 max_read = future.get_future_value_size_();
261 max_write = future.get_storage_value_size_();
262 }
263
264 // That check is normally usefull only in debug mode
265 if (MANAGER_TRAIT::IS_DEBUG && (!check_commands(max_write, max_read, commands)))
266 return errors::EINVAL;
267
268 // Now push each command to the I2C Manager
269 int error = 0;
270 for (I2CLightCommand command : commands)
271 {
272 // update command.byte_count if 0
273 command.update_byte_count(max_read, max_write);
274 // force future finish for last command in transaction
275 --num_commands;
276 if (num_commands == 0)
277 command.type().add_flags(auto_stop_flags_);
278 // Note: on ATtiny, this method blocks until I2C command is finished!
279 if (!handler_.push_command_(command, device_, future))
280 {
281 error = errors::EPROTO;
282 break;
283 }
284 }
285 // Notify handler that transaction is complete
286 handler_.last_command_pushed_();
287 return error;
288 }
289 }
290
309 template<typename F> int async_read(F& future, bool stop = true)
310 {
311 return launch_commands(future, {write(), read(0, false, stop)});
312 }
313
334 template<typename F, typename T = uint8_t> bool sync_read(T& result)
335 {
336 F future{};
337 if (async_read<F>(future) != 0) return false;
338 return future.get(result);
339 }
340
357 template<typename F> int async_write(F& future, bool stop = true)
358 {
359 return launch_commands(future, {write(0, false, stop)});
360 }
361
379 template<typename F> int async_multi_write(F& future, bool stop = true)
380 {
381 constexpr uint8_t NUM_WRITES = F::NUM_WRITES;
382 constexpr uint8_t WRITE_SIZE = F::WRITE_SIZE;
383 I2CLightCommand writes[NUM_WRITES];
384 prepare_multi_write_commands(writes, NUM_WRITES, WRITE_SIZE, stop);
385 return launch_commands(future, utils::range(writes, NUM_WRITES));
386 }
387
406 template<typename F, typename T = uint8_t> bool sync_write(const T& value)
407 {
408 F future{value};
409 if (async_write<F>(future) != 0) return false;
410 return (future.await() == future::FutureStatus::READY);
411 }
412
429 template<typename F> bool sync_write()
430 {
431 F future{};
432 if (async_write<F>(future) != 0) return false;
433 return (future.await() == future::FutureStatus::READY);
434 }
435
436 private:
437 static bool check_commands(
438 uint8_t max_write, uint8_t max_read, const utils::range<I2CLightCommand>& commands)
439 {
440 // Limit total number of bytes read or written in a transaction to 255
441 uint8_t total_read = 0;
442 uint8_t total_write = 0;
443 for (const I2CLightCommand& command : commands)
444 {
445 // Count number of bytes read and written
446 if (command.type().is_write())
447 total_write += (command.byte_count() ? command.byte_count() : max_write);
448 else
449 total_read += (command.byte_count() ? command.byte_count() : max_read);
450 }
451 // check sum of read commands byte_count matches future output size
452 // check sum of write commands byte_count matches future input size
453 return (total_write == max_write) && (total_read == max_read);
454 }
455
456 void prepare_multi_write_commands(I2CLightCommand* commands, uint8_t count, uint8_t write_size, bool stop)
457 {
458 I2CLightCommand command = write(write_size, false, stop);
459 for (uint8_t i = 0; i < count; ++i)
460 *commands++ = command;
461 }
462
463 uint8_t device_ = 0;
464 MANAGER& handler_;
465 const uint8_t auto_stop_flags_;
466 friend class I2CFutureHelper<MANAGER>;
467 };
468}
469
470#endif /* I2C_DEVICE_HH */
Base class for all I2C devices.
Definition: i2c_device.h:84
bool sync_read(T &result)
Helper method that launches I2C commands for a simple Future performing one write followed by one rea...
Definition: i2c_device.h:334
void set_device(uint8_t device)
Change the I2C address of this device.
Definition: i2c_device.h:146
static constexpr I2CLightCommand write(uint8_t write_count=0, bool finish_future=false, bool stop=false)
Build a write I2CLightCommand that can be later pushed to the I2C Manager for proper handling.
Definition: i2c_device.h:202
int async_write(F &future, bool stop=true)
Helper method that asynchronously launches I2C commands for a simple Future performing only one write...
Definition: i2c_device.h:357
int async_multi_write(F &future, bool stop=true)
Helper method that asynchronously launches I2C commands for a simple Future performing several regist...
Definition: i2c_device.h:379
bool sync_write(const T &value)
Helper method that launches I2C commands for a simple Future performing only one write (typically for...
Definition: i2c_device.h:406
static constexpr I2CLightCommand read(uint8_t read_count=0, bool finish_future=false, bool stop=false)
Build a read I2CLightCommand that can be later pushed to the I2C Manager for proper handling.
Definition: i2c_device.h:173
MANAGER_ MANAGER
the type of I2C Manager that can handle this device.
Definition: i2c_device.h:87
int async_read(F &future, bool stop=true)
Helper method that asynchronously launches I2C commands for a simple Future performing one write foll...
Definition: i2c_device.h:309
bool sync_write()
Helper method that launches I2C commands for a simple Future performing only one write (typically for...
Definition: i2c_device.h:429
typename MANAGER::ABSTRACT_FUTURE ABSTRACT_FUTURE
The abstract base class of all futures to be defined for a device.
Definition: i2c_device.h:101
I2CDevice(MANAGER &manager, uint8_t device, UNUSED Mode< MODE > mode, bool auto_stop)
Create a new I2C device.
Definition: i2c_device.h:128
int launch_commands(ABSTRACT_FUTURE &future, utils::range< I2CLightCommand > commands)
Launch execution (asynchronously or synchronously, depending on MANAGER) of a chain of I2CLightComman...
Definition: i2c_device.h:243
typename MANAGER::template FUTURE< OUT, IN > FUTURE
The template base class of all futures to be defined for a device.
Definition: i2c_device.h:108
Light atomic I2C command as prepared by an I2C device.
Iterable class that can embed arrays or initializer lists through implicit conversion.
Definition: iterator.h:51
#define UNUSED
Specific GCC attribute to declare an argument or variable unused, so that the compiler does not emit ...
Definition: defines.h:45
Common errors definition.
#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.
Common I2C Manager API.
Utilities to convert arrays into an iterable (usable if for x: list construct).
Mode
Mode used when drawing pixels.
Definition: display.h:102
constexpr const int EINVAL
Invalid argument or invalid Future.
Definition: errors.h:49
constexpr const int EPROTO
Protocol error.
Definition: errors.h:58
constexpr const int EAGAIN
Try again.
Definition: errors.h:46
Contains the API around Future implementation.
Definition: future.h:312
@ READY
The status of a Future once its output value has been fully set by a provider.
Define API to define and manage I2C devices.
Definition: i2c.h:51
static constexpr Mode I2C_STANDARD
Constant determining that best supported I2C mode for an I2CDevice is STANDARD (100kHz).
Definition: i2c_device.h:45
@ FAST
I2C Fast mode, less than 400KHz.
static constexpr Mode I2C_FAST
Constant determining that best supported I2C mode for an I2CDevice is FAST (400kHz).
Definition: i2c_device.h:47
General utilities API that have broad application in programs.