FastArduino  v1.7
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_>;
422  template<typename T> using PROXY = typename LC::template PROXY<T>;
423  template<typename OUT, typename IN> using FUTURE = future::FakeFuture<OUT, IN>;
424 
425  public:
433  void begin()
434  {
435  synchronized begin_();
436  }
437 
444  void end()
445  {
446  synchronized end_();
447  }
448 
456  void begin_()
457  {
458  handler_.begin_();
459  }
460 
467  void end_()
468  {
469  handler_.end_();
470  }
471 
472  protected:
474  explicit AbstractI2CSyncManager(
475  lifecycle::AbstractLifeCycleManager* lifecycle_manager = nullptr,
476  STATUS_HOOK_ status_hook = nullptr,
477  DEBUG_HOOK_ debug_hook = nullptr)
478  : handler_{status_hook}, lc_{lifecycle_manager}, debug_hook_{debug_hook} {}
480 
481  private:
482  bool ensure_num_commands_(UNUSED uint8_t num_commands) const
483  {
484  return true;
485  }
486 
487  bool push_command_(
488  I2CLightCommand command, uint8_t target, PROXY<ABSTRACT_FUTURE> proxy)
489  {
490  // Check command is not empty
491  const I2CCommandType type = command.type();
492  if (type.is_none()) return true;
493  if (clear_commands_) return false;
494  ABSTRACT_FUTURE& future = lc_.resolve(proxy);
495  // Execute command immediately, from start to optional stop
496  bool ok = (no_stop_ ? exec_repeat_start_() : exec_start_());
497  stopped_already_ = false;
498  if (!ok)
499  return handle_error(future);
500 
501  if (type.is_write())
502  {
503  // Send device address
504  if (!exec_send_slaw_(target))
505  return handle_error(future);
506  // Send content
507  while (command.byte_count() > 0)
508  // In case of a NACK on data writing, we check if it is not last byte
509  if ((!exec_send_data_(command, future)) && (command.byte_count() > 0))
510  return handle_error(future);
511  }
512  else
513  {
514  // Send device address
515  if (!exec_send_slar_(target))
516  return handle_error(future);
517  // Receive content
518  while (command.byte_count() > 0)
519  if (!exec_receive_data_(command, future))
520  return handle_error(future);
521  }
522 
523  // Check if we must force finish the future
524  if (type.is_finish())
525  future.set_future_finish_();
526  // Check if we must force a STOP
527  if (type.is_stop())
528  exec_stop_();
529  // Ensure STOP is generated or not depending on latest command executed
530  no_stop_ = !type.is_stop();
531  return true;
532  }
533 
534  void last_command_pushed_()
535  {
536  // Check if previously executed command already did a STOP (and needed one)
537  if ((!no_stop_) && (!stopped_already_) && (!clear_commands_))
538  {
539  exec_stop_();
540  no_stop_ = false;
541  }
542  clear_commands_ = false;
543  stopped_already_ = false;
544  }
545 
546  template<typename T> T& resolve(PROXY<T> proxy) const
547  {
548  return lc_.resolve(proxy);
549  }
550 
551  // Low-level methods to handle the bus in an asynchronous way
552  bool exec_start_()
553  {
554  debug_hook_.call_hook(DebugStatus::START);
555  return handler_.exec_start_();
556  }
557 
558  bool exec_repeat_start_()
559  {
560  debug_hook_.call_hook(DebugStatus::REPEAT_START);
561  return handler_.exec_repeat_start_();
562  }
563 
564  bool exec_send_slar_(uint8_t target)
565  {
566  debug_hook_.call_hook(DebugStatus::SLAR, target);
567  return handler_.exec_send_slar_(target);
568  }
569 
570  bool exec_send_slaw_(uint8_t target)
571  {
572  debug_hook_.call_hook(DebugStatus::SLAW, target);
573  return handler_.exec_send_slaw_(target);
574  }
575 
576  bool exec_send_data_(I2CLightCommand& command, ABSTRACT_FUTURE& future)
577  {
578  // Determine next data byte
579  uint8_t data = 0;
580  bool ok = future.get_storage_value_(data);
581  debug_hook_.call_hook(DebugStatus::SEND, data);
582  debug_hook_.call_hook(ok ? DebugStatus::SEND_OK : DebugStatus::SEND_ERROR);
583  // This should only happen if there are 2 concurrent consumers for that Future
584  if (!ok)
585  {
586  future.set_future_error_(errors::EILSEQ);
587  return false;
588  }
589  command.decrement_byte_count();
590  return handler_.exec_send_data_(data);
591  }
592 
593  bool exec_receive_data_(I2CLightCommand& command, ABSTRACT_FUTURE& future)
594  {
595  // Is this the last byte to receive?
596  const bool last_byte = (command.byte_count() == 1);
597  const DebugStatus debug = (last_byte ? DebugStatus::RECV_LAST : DebugStatus::RECV);
598  debug_hook_.call_hook(debug);
599 
600  uint8_t data;
601  if (handler_.exec_receive_data_(last_byte, data))
602  {
603  const bool ok = future.set_future_value_(data);
604  debug_hook_.call_hook(ok ? DebugStatus::RECV_OK : DebugStatus::RECV_ERROR, data);
605  // This should only happen in case there are 2 concurrent providers for this future
606  if (ok)
607  {
608  command.decrement_byte_count();
609  }
610  else
611  {
612  future.set_future_error_(errors::EILSEQ);
613  }
614  return ok;
615  }
616  return false;
617  }
618 
619  void exec_stop_()
620  {
621  debug_hook_.call_hook(DebugStatus::STOP);
622  handler_.exec_stop_();
623  // If so then delay 4.0us + 4.7us (100KHz) or 0.6us + 1.3us (400KHz)
624  // (ATMEGA328P datasheet 29.7 Tsu;sto + Tbuf)
625  _delay_loop_1(MODE_TRAIT::DELAY_AFTER_STOP);
626  stopped_already_ = true;
627  }
628 
629  // This method is called when an error has occurred
630  bool handle_error(ABSTRACT_FUTURE& future)
631  {
632  if (future.status() != future::FutureStatus::ERROR)
633  // The future must be marked as error
634  future.set_future_error_(errors::EPROTO);
635 
636  // Clear command belonging to the same transaction (i.e. same future)
637  // ie forbid any new command until last command (add new flag for that)
638  clear_commands_ = true;
639  // In case of an error, immediately send a STOP condition
640  exec_stop_();
641  return false;
642  }
643 
644  // Flags for storing I2C transaction operation state
645  bool no_stop_ = false;
646  bool clear_commands_ = false;
647  bool stopped_already_ = false;
648 
649  ARCH_HANDLER handler_;
650  LC lc_;
651  DEBUG debug_hook_;
652 
653  template<typename> friend class I2CDevice;
654  };
655 
657  // Specific traits for I2C Manager
658  template<typename T> struct I2CManager_trait
659  {
660  static constexpr bool IS_I2CMANAGER = false;
661  static constexpr bool IS_ASYNC = false;
662  static constexpr bool HAS_LIFECYCLE = false;
663  static constexpr bool IS_DEBUG = false;
664  static constexpr bool IS_STATUS = false;
665  static constexpr I2CMode MODE = I2CMode::STANDARD;
666  };
667 
668  template<bool IS_ASYNC_, bool HAS_LIFECYCLE_, bool IS_STATUS_, bool IS_DEBUG_, I2CMode MODE_>
669  struct I2CManager_trait_impl
670  {
671  static constexpr bool IS_I2CMANAGER = true;
672  static constexpr bool IS_ASYNC = IS_ASYNC_;
673  static constexpr bool HAS_LIFECYCLE = HAS_LIFECYCLE_;
674  static constexpr bool IS_DEBUG = IS_DEBUG_;
675  static constexpr bool IS_STATUS = IS_STATUS_;
676  static constexpr I2CMode MODE = MODE_;
677  };
679 }
680 
681 #endif /* I2C_HANDLER_COMMON_HH */
682 
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:1170
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:444
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:1018
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:456
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:433
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:437
i2c::AbstractI2CSyncManager::end_
void end_()
Disable MCU I2C transmission.
Definition: i2c_handler_common.h:467
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