FastArduino  v1.8
C++ library to build fast but small Arduino/AVR projects
i2c_handler_common.h
Go to the documentation of this file.
1 // Copyright 2016-2021 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 (stop ? STOP : 0U) | (finish ? FINISH : 0U) | (end ? END : 0U);
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 NOT_NONE | (write ? WRITE : 0U) | (stop ? STOP : 0U) | (finish ? FINISH : 0U) | (end ? END : 0U);
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 */
683 
future
Contains the API around Future implementation.
Definition: future.cpp:18
lifecycle::DirectProxy
A kind of proxy class that encapsulates access to a fixed T instance.
Definition: lifecycle.h:826
future.h
Utility API to handle the concept of futures.
streams::ostream
Output stream wrapper to provide formatted output API, a la C++.
Definition: streams.h:61
streams.h
C++-like std::iostream facilities.
i2c::I2CMode
I2CMode
I2C available transmission modes.
Definition: i2c.h:108
i2c::DebugStatus::SLAW
@ SLAW
A slave address has just been sent for writing.
time::operator==
bool operator==(const RTTTime &a, const RTTTime &b)
Compare 2 RTTTime instances.
Definition: time.h:204
errors::EILSEQ
constexpr const int EILSEQ
Illegal byte sequence.
Definition: errors.h:65
future::FakeFuture
Actual FakeFuture, it has the exact same API as Future and can be used in lieu of Future.
Definition: future.h:1223
i2c::AbstractI2CSyncManager
Abstract synchronous I2C Manager for all MCU architectures.
Definition: i2c_handler_common.h:413
i2c::DebugStatus
DebugStatus
List of debug states that are reported by the I2C Manager in debug mode.
Definition: i2c_handler_common.h:44
i2c::DebugStatus::STOP
@ STOP
A stop condition has just been sent.
i2c::DebugStatus::REPEAT_START
@ REPEAT_START
A repeat start condition has just been sent.
i2c::DebugStatus::START
@ START
A start condition has just been sent.
bits::BV8
static constexpr uint8_t BV8(uint8_t bit)
Create a uint8_t bitmask for the given bit number.
Definition: bits.h:41
bits.h
Useful bits manipulation utilities.
lifecycle::make_light_proxy
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
streams::hex
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
i2c::DebugStatus::SLAR
@ SLAR
A slave address has just been sent for reading.
devices::audio::SpecialTone::END
static constexpr const Tone END
This special tone marks the end of a melody (as a sequence of Tones).
Definition: tone_player.h:44
i2c::Status
Status
Transmission status codes.
Definition: i2c.h:66
i2c::AbstractI2CSyncManager::end
void end()
Disable MCU I2C transmission.
Definition: i2c_handler_common.h:445
i2c::I2CCommand
Atomic I2C command as used internally by an asynchronous I2C Manager.
Definition: i2c_handler_common.h:228
lifecycle::make_direct_proxy
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
lifecycle::AbstractLifeCycleManager
The abstract base class of all LifeCycleManager.
Definition: lifecycle.h:132
UNUSED
#define UNUSED
Specific GCC attribute to declare an argument or variable unused, so that the compiler does not emit ...
Definition: defines.h:45
queue.h
Utility API to handle ring-buffer queue containers.
utilities.h
General utilities API that have broad application in programs.
lifecycle::LightProxy
A light proxy class that encapsulates access to a fixed T instance, or to a dynamic LifeCycle<T> inst...
Definition: lifecycle.h:643
time::operator!=
bool operator!=(const RTTTime &a, const RTTTime &b)
Compare 2 RTTTime instances.
Definition: time.h:216
i2c::DebugStatus::RECV
@ RECV
A byte is being received from the slave.
future::AbstractFakeFuture
Base class for all FakeFutures.
Definition: future.h:1062
i2c::I2C_DEBUG_HOOK
void(*)(DebugStatus status, uint8_t data) I2C_DEBUG_HOOK
The default debugging hook type.
Definition: i2c_handler_common.h:83
i2c::DebugStatus::RECV_LAST
@ RECV_LAST
The last byte is being received from the slave.
i2c
Define API to define and manage I2C devices.
Definition: i2c.cpp:18
i2c::AbstractI2CSyncManager::begin_
void begin_()
Prepare and enable the MCU for I2C transmission.
Definition: i2c_handler_common.h:457
lifecycle::make_proxy
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
i2c::AbstractI2CSyncManager::begin
void begin()
Prepare and enable the MCU for I2C transmission.
Definition: i2c_handler_common.h:434
lifecycle.h
Utility API to handle lifecycle of objects so that:
i2c::I2CLightCommand
Light atomic I2C command as prepared by an I2C device.
Definition: i2c_handler_common.h:176
i2c::DebugStatus::SEND
@ SEND
A byte has just be sent to the slave.
i2c.h
I2C API common definitions.
i2c::I2C_STATUS_HOOK
void(*)(Status expected, Status actual) I2C_STATUS_HOOK
The default status observer hook type.
Definition: i2c_handler_common.h:96
utils::calculate_delay1_count
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:458
i2c::AbstractI2CSyncManager::end_
void end_()
Disable MCU I2C transmission.
Definition: i2c_handler_common.h:468
errors::EPROTO
constexpr const int EPROTO
Protocol error.
Definition: errors.h:58
streams::flush
void flush(FSTREAM &stream)
Manipulator for an output stream, which will flush the stream buffer.
Definition: streams.h:713