36 次代碼提交 93af118363 ... ade4ce9198

作者 SHA1 備註 提交日期
  Ivan Baidakou ade4ce9198 fix typos 2 年之前
  Ivan Baidakou 50bd829dd1 more clean impl (but still buggy) 2 年之前
  Ivan Baidakou 2a16b476fb improve performance 2 年之前
  Ivan Baidakou eb9aaffee1 add cancel_event api 2 年之前
  Ivan Baidakou 3dc9d83b82 check and fix event_id uniqueness 2 年之前
  Ivan Baidakou e747338615 refactor events api & events internals 2 年之前
  basiliscos 00c53f8127 Merge branch 'mb' of am/cpp-rotor-light into master 2 年之前
  Aliaksei Makarau aae79fe3aa Update to use the board.h/.cpp 2 年之前
  Ivan Baidakou d5e0501a7a update stm32 info 2 年之前
  basiliscos 9d83da6a38 Merge branch 'nucleoH743_throughput' of korifey/cpp-rotor-light into master 2 年之前
  Andrey Nikolaev 5b1a4332d1 throughput example 2 年之前
  Ivan Baidakou 06cdab196b major examples refactoring 2 年之前
  Ivan Baidakou c3bcb52625 fix doc 2 年之前
  Ivan Baidakou c8dc1ab5fe format 2 年之前
  Ivan Baidakou e04486c95e minor fixes 2 年之前
  Ivan Baidakou 623e7b8cf9 minor cleanings 2 年之前
  basiliscos bb49481f11 Update 'README.md' 2 年之前
  basiliscos f937dcf817 Merge branch 'mb' of am/cpp-rotor-light into master 2 年之前
  Ivan Baidakou f8329513d6 fix typos 2 年之前
  Aliaksei Makarau a2c4f5ddd4 A silly Microblaze example with the silly Xilinx 2 年之前
  Ivan Baidakou 30f675e468 fix typos 2 年之前
  Ivan Baidakou a1ddabb276 fix copyrights 2 年之前
  Aliaksei Makarau ae0c4570f1 A silly Microblaze example with the silly 2 年之前
  Ivan Baidakou a92ee43c33 moar docs 2 年之前
  Ivan Baidakou 1b5ce851e2 moar docs 2 年之前
  Ivan Baidakou baa4779164 minor fix on stm nucleo example 2 年之前
  basiliscos 1e52043db7 Merge branch 'nucleoH743ZI_example' of korifey/cpp-rotor-light into master 2 年之前
  korifey 55baa1688c 'debug.jdebug.user' löschen 2 年之前
  korifey 32fbbdc364 'debug.jdebug' löschen 2 年之前
  Andrey Nikolaev 776a6aea1a example ping pong poll with nucleo h743zi board 2 年之前
  Andrey Nikolaev 5a12b08fbd cubemx generated source code files 2 年之前
  Ivan Baidakou 86a967afc3 Allow TimePoint and Duration customization (issue 1) 2 年之前
  Ivan Baidakou b2f6dd4ab5 moar docs 2 年之前
  Ivan Baidakou 03a78a638b Fix warnings 2 年之前
  Ivan Baidakou cc1bf07809 complete the 1st tutorial 2 年之前
  Ivan Baidakou 61a6929667 fix cmake 2 年之前

+ 13 - 3
CMakeLists.txt

@@ -13,6 +13,11 @@ if (NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET AND
   set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
   set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
 endif ()
 endif ()
 
 
+set(ROTOR_LIGHT_DOC OFF CACHE BOOL "generate docs using doxygen")
+set(ROTOR_LIGHT_ACTOR "uint64_t" CACHE STRING "ActorId type [default: uint64_t]")
+set(ROTOR_LIGHT_TIMEPOINT "int64_t" CACHE STRING "TimePoint type [default: int64_t]")
+set(ROTOR_LIGHT_DURATION "int32_t" CACHE STRING "Duration type [default: int32_t]")
+
 include(CMakePrintHelpers)
 include(CMakePrintHelpers)
 include(GenerateExportHeader)
 include(GenerateExportHeader)
 
 
@@ -36,10 +41,15 @@ set_target_properties(rotor_light PROPERTIES
     CXX_STANDARD_REQUIRED YES
     CXX_STANDARD_REQUIRED YES
     CXX_EXTENSIONS NO
     CXX_EXTENSIONS NO
 )
 )
-
-add_subdirectory("examples")
+target_compile_definitions(rotor_light PUBLIC
+    "ROTOR_LIGHT_ACTOR=${ROTOR_LIGHT_ACTOR}"
+    "ROTOR_LIGHT_TIMEPOINT=${ROTOR_LIGHT_TIMEPOINT}"
+    "ROTOR_LIGHT_DURATION=${ROTOR_LIGHT_DURATION}"
+)
 
 
 if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
 if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
+    add_subdirectory("examples")
+
     if ("${TOOLCHAIN_PREFIX}" STREQUAL "")
     if ("${TOOLCHAIN_PREFIX}" STREQUAL "")
         include(CTest)
         include(CTest)
         if (BUILD_TESTING)
         if (BUILD_TESTING)
@@ -48,7 +58,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
     endif()
     endif()
 
 
     find_package(Doxygen)
     find_package(Doxygen)
-    if (DOXYGEN_FOUND)
+    if (DOXYGEN_FOUND AND ${ROTOR_LIGHT_DOC})
         set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
         set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
         set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
         set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
         configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
         configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)

+ 16 - 9
README.md

@@ -13,11 +13,7 @@ care about thread-safety.
 
 
  - erlang-like hierarchical supervisors
  - erlang-like hierarchical supervisors
 
 
- - message priorities
-
- - non-intrusiveness
-
- - asynchornous message passing interface
+ - asynchronous messaging with priorities
 
 
  - plaform-agnostic code (including timers)
  - plaform-agnostic code (including timers)
 
 
@@ -25,20 +21,31 @@ care about thread-safety.
 
 
  - no thread-safety
  - no thread-safety
 
 
+ - no allocations, no exceptions, no RTTI
 
 
-## ping-pong messaging performance & binary size
+ - non-intrusiveness
 
 
+ - compile-time actor hierarchies 
 
 
-|      host (1)                 |  Arduino Uno R3
-|:-----------------------------:|:---------------------------:
-|  ~48.7M messages/second, 26kb |  ~5.2k messages/second, 9kb
+## ping-pong messaging performance & .text section size
 
 
 
 
+|                       | messages/second | binary size
+|:---------------------:|:---------------:|:--------------:
+| host (1)              | ~55.3M          | 13143 bytes
+| STM32-H743ZI          | ~2.3M           | 14330 bytes
+| Arduino Uno R3 (2)    | ~15.8K          | 6546 bytes
+| xilinx microblaze (3) | ~58.8K          | 42868 byes
+
 All examples can be measured with `examples/ping-pong-throughput.cpp`,
 All examples can be measured with `examples/ping-pong-throughput.cpp`,
 compiled with `CMAKE_BUILD_TYPE=MinSizeRel` and the stripped
 compiled with `CMAKE_BUILD_TYPE=MinSizeRel` and the stripped
 
 
 (1) Setup: Intel Core i7-8550U, Void Linux 5.15.
 (1) Setup: Intel Core i7-8550U, Void Linux 5.15.
 
 
+(2) Build with `-DROTOR_LIGHT_ACTOR=uint8_t`
+
+(2) Zynq xc7z020clg400-1, QMTECH development board. Microblaze standard config, hw mul/div instr. enabled, clocking is 50Mhz.
+
 ## license
 ## license
 
 
 MIT
 MIT

+ 1 - 0
docs/Doxyfile.in

@@ -803,6 +803,7 @@ ALPHABETICAL_INDEX = no
 
 
 INPUT                  =  @CMAKE_CURRENT_SOURCE_DIR@/include                \
 INPUT                  =  @CMAKE_CURRENT_SOURCE_DIR@/include                \
                           @CMAKE_CURRENT_SOURCE_DIR@/docs/Introduction.md   \
                           @CMAKE_CURRENT_SOURCE_DIR@/docs/Introduction.md   \
+                          @CMAKE_CURRENT_SOURCE_DIR@/docs/Tutorial.md       \
                           @CMAKE_CURRENT_SOURCE_DIR@/docs/Conception.md
                           @CMAKE_CURRENT_SOURCE_DIR@/docs/Conception.md
 
 
 
 

+ 52 - 21
docs/Introduction.md

@@ -1,32 +1,32 @@
 # Intoduction
 # Intoduction
 
 
-Modelling your applications with [actors](https://en.wikipedia.org/wiki/Actor_model), you can
-get two benefits: **messaging** and **supervising** between *actors*.
+Modeling your applications with [actors](https://en.wikipedia.org/wiki/Actor_model),
+you can get two benefits: **messaging** and **supervising** between *actors*.
 
 
-Actor is a reactive entity with state, which can receive and send messages; actor state is
-usually changed with new messages. Every **message** has destination (one or more actors)
-and user-defined payload. Message is send with *fire-and-forget* approach, i.e. sender
-does not care about further delivery and processing of the message, like with UDP protocol.
+Actor is a reactive entity with a state, which can receive and send messages; actor
+state is usually changed with new messages. Every **message** has destination (one or
+more actors) and user-defined payload. Messaging is send with *fire-and-forget*
+approach, i.e. sender does not care about further delivery and processing of the
+message, like with UDP protocol.
 
 
-The message delivery is *asynchronous*, as the message is put into the queue, and will be
-delivered some time later.
+The message delivery is *asynchronous*, as the message is put into the queue, and
+will be delivered some time later.
 
 
-With all that properties it is possible to assemble [concurrent](https://en.wikipedia.org/wiki/Concurrency_(computer_science))
-programs.
+With all that properties it is possible to assemble [concurrent](https://en.wikipedia.org/wiki/Concurrency_(computer_science)) programs.
 
 
 **Supervising** is more about proper actor initialization order, synchronization and
 **Supervising** is more about proper actor initialization order, synchronization and
-failure handling. *Synchronization* makes it sure that starting sending messages to actors
-is performed when actors are ready. *Failure handling* allows to use different restart
-strategies of a failing actor, or group of actors, or even escalate locally unresolveable
-problem into upper layers... upto restarting the whole board restart.
+failure handling. *Synchronization* makes it sure that starting sending messages to
+actors is performed when actors are ready. *Failure handling* allows to use different
+restart strategies of a failing actor, or group of actors, or even escalate locally
+unresolvable problem into upper layers... upto restarting the whole board restart.
 
 
-`rotor-light` is platform-neutral framework, i.e. it can be build and run on any plaform,
-which conforms the requirements (C++17, basically).
+`rotor-light` is platform-neutral framework, i.e. it can be build and run on any
+plaform, which conforms the requirements (C++17, basically).
 
 
 # rotor and rotor-light
 # rotor and rotor-light
 
 
-There is a successful C++ actor microframework [rotor](github.com/basiliscos/cpp-rotor/),
-when a few colleques of mine aske me to have something similar for embedded systems.
+There is a successful C++ actor microframework [rotor](https://github.com/basiliscos/cpp-rotor),
+when a few colleagues of mine asked me to have something similar for embedded systems.
 
 
 The following table highlight the differences
 The following table highlight the differences
 
 
@@ -52,10 +52,41 @@ The following table highlight the differences
 (1) `rotor-light` needs only "now" function from timer, the time point and
 (1) `rotor-light` needs only "now" function from timer, the time point and
 time interval are abstract notions, have to be defined by end-user of the library.
 time interval are abstract notions, have to be defined by end-user of the library.
 
 
-(2) Non-intrusiveness means, that a framework does not owns execution thread: when all messages
-have been processed, it just exits as there is nothing to do. That way it is possible to integrate
-a framework with other event-loops or enter into power-save mode for MCU.
+(2) Non-intrusiveness means, that a framework does not owns execution thread: when
+all messages have been processed, it just exits as there is nothing to do. That way
+it is possible to integrate a framework with other event-loops or enter into
+power-save mode for MCU or even let it go into sleep and wakeup on external event
+(interrupt).
 
 
 There are a lot of useful patterns and explanations in
 There are a lot of useful patterns and explanations in
 [rotor](github.com/basiliscos/cpp-rotor/) [documentation](https://basiliscos.github.io/cpp-rotor-docs/index.html),
 [rotor](github.com/basiliscos/cpp-rotor/) [documentation](https://basiliscos.github.io/cpp-rotor-docs/index.html),
 it is worth familiarize self with it to get some insights.
 it is worth familiarize self with it to get some insights.
+
+# building
+
+`rotor-light` uses [cmake](https://cmake.org/) build system. It has no dependencies,
+except C++17 core libraries  (e.g. `<tuple>`,`<type_traits>` etc.). Basically, the
+framework can be build via
+
+
+```
+mkdir build
+cd build
+cmake ..
+make -j4
+```
+
+There are few customization options:
+
+ - `ROTOR_LIGHT_TIMEPOINT` defines the type to be used as timepoint. The default
+value is `int64_t`
+
+ - `ROTOR_LIGHT_DURATION` defines the type to be used as time interval. The default
+value is `int32_t`
+
+Usage example:
+
+```
+cmake -DROTOR_LIGHT_TIMEPOINT=int32_t ..
+```
+

+ 445 - 32
docs/Tutorial.md

@@ -3,7 +3,8 @@
 ## blink with LED
 ## blink with LED
 
 
 This is platform-neural tutorial, although hardware implementation refers to
 This is platform-neural tutorial, although hardware implementation refers to
-arduino. The full code can be found at `examples/atmega328p/blink-led.cpp`
+arduino. The full code can be found at `examples/atmega328p/blink-led.cpp`. In
+this example the blinking with LED is performed using `rotor-light`.
 
 
 Let's start from the inclusion of headers:
 Let's start from the inclusion of headers:
 
 
@@ -19,34 +20,30 @@ namespace rl = rotor_light;
 
 
 In this example, there is only single message - `BlinkCommand`. It is good
 In this example, there is only single message - `BlinkCommand`. It is good
 practice to define messages in it's own namespace, as it is much more
 practice to define messages in it's own namespace, as it is much more
-convenient to deail with messages later (handle and send).
+convenient to deal with messages later (handle and send).
 
 
 ```
 ```
 namespace message {
 namespace message {
 
 
-struct BlinkCommand : rl::Message {
-  static constexpr auto type_id = __LINE__;
-  using rl::Message::Message;
-  rl::MessageTypeId get_type_id() const override { return type_id; }
-};
+struct BlinkCommand : rl::Message {            // (1)
+  static constexpr auto type_id = __LINE__;    // (2)
+  using rl::Message::Message;                  // (3)
+  rl::MessageTypeId get_type_id() const override {  // (4)
+    return type_id; }
+  };
 
 
 } // namespace message
 } // namespace message
 ```
 ```
 
 
-The `BlinkCommand` message have to be derived from `rotor_ligth` `Message`,
+The `BlinkCommand` message have to be derived from `rotor_ligth` `Message` (1),
 and as the `BlinkCommand` has no payload, it just reuses its parent
 and as the `BlinkCommand` has no payload, it just reuses its parent
-consturctor (`using rl::Message::Message`).
+constructor (`using rl::Message::Message`, (3)).
 
 
-As the `rotor_ligth` does not uses RTTI it should somehow identify
-message types at runtime. That's why overriden `get_type_id()` method
+As the `rotor_ligth` does not use RTTI it should somehow identify
+message types at runtime. That's why overridden `get_type_id()` method (4)
 should be available on every message type. `rotor_light` does not care
 should be available on every message type. `rotor_light` does not care
 how you, as the user, define your message type, the only requirement is the
 how you, as the user, define your message type, the only requirement is the
-uniqueness of them. The easiest way to achieve that is just have line
-
-```
-  static constexpr auto type_id = __LINE__;
-```
-
+uniqueness of them. The easiest way to achieve that is just have line (2)
 in every message type definition, and group all used messages in a single
 in every message type definition, and group all used messages in a single
 header file.
 header file.
 
 
@@ -54,28 +51,444 @@ Let's move to the blinker actor code; as it is rather small, it is shown
 entirely:
 entirely:
 
 
 ```
 ```
-struct Blinker : rl::Actor<2> {
-using Parent = Actor<2>;
+struct Blinker : rl::Actor<2> {  // (5)
+using Parent = Actor<2>;  // (6)
+
+void initialize() override {  // (7)
+  subscribe(&Blinker::on_blink_command);  // (8)
+  Parent::initialize();                   // (9)
+}
+
+
+  void advance_start() override {  // (10)
+    Parent::advance_start();       // (11)
+    blink();                       // (12)
+  }
+
+  void blink() {
+    toggle_led();                                        // (13)
+    add_event(delay, [](void *data) {                    // (14)
+        auto self = static_cast<Blinker *>(data);        // (15)
+        self->send<message::BlinkCommand>(0, self->id);  // (16)
+    }, this);
+  }
+
+void on_blink_command(message::BlinkCommand &msg) {  // (17)
+  blink();
+}
+
+rl::Duration delay;  // (18)
+};
+```
+
+First, your own actor have to inherit from `rl::Actor` (5). The magic number `2` is
+used for preallocation of space for actor's message handlers: one handler from
+the base class defined by the framework (might be changed in the future), and the
+other one use the user-provided `on_blink_command`. The alias (6) makes it more
+convenient to refer the base class in the future.
+
+To react on the `BlinkCommand` message, the actor should subscribe to it (8). The
+proper place to do that is to override `initialize()` method (7) of the parent class.
+Of course, parent class **must** be initialized too, that's why don't forget to
+invoke its initialization (9).
+
+When actor is ready, its `advance_start()` (10) is invoked; as usually corresponding
+parent method should be called (11) and the initial blink (12) is performed. In
+the implementation of `blink()` the platform-dependent LED toggle is done at (13),
+and then delayed the same method invocation is scheduled (14-16). In (14) the
+capture-less lambda is scheduled to be invoked with `this` as the parameter when
+`delay` (18) amount of time passes. The lambda *must* be capture-less. After casting
+back `void*` to the actor class (15), it sends self a `BlinkCommand` message (16)
+using zero-priority queue. The `id` parameter is the destination actor address;
+which is the `Blinker` (self) address here. The destination address can also be
+multiple actors (i.e. it is mask).
+
+Each time the `BlinkCommand` is received (17), the `blink()` method is invoked, and
+the procedure above repeats again.
+
+Next, the application-wide compile-time configuration should be made:
+
+```
+using Storage = rl::traits::MessageStorage<rl::message::ChangeState,    // (18)
+                                           rl::message::ChangeStateAck,
+                                           message::BlinkCommand>;
+using Queue = rl::Queue<Storage, 5>; /* upto 5 messages in 1 queue */   // (19)
+using Planner = rl::Planner<2>;      /* upto 2 time events */           // (20)
+using Supervisor = rl::Supervisor<                                      // (21)
+                            rl::SupervisorBase::min_handlers_amount,
+                            Blinker>;
+```
+
+`rotor-light` uses queues to store and process messages. There are no dynamic
+allocations, so, all sizes have to be known at compile-time, including the sizes
+of all used messages. The `Storage` helper (18) is used to determine the maximum
+allowed space size per message: here **all messages** in the application
+should be enumerated, i.e. messages from the framework and user-defined.
+
+Next, the application queue (or master queue) should be defined (19). It uses
+`Storage` and the magic number `5`, which pre-allocates space for queue with
+**zero-priority** for 5 messages. All `rotor-light` messages are put into that
+queue, but user-defined messages might use different queues. Queues with
+higher priorities are processed earlier than queues with lower priorities. So,
+to define master queue with two subqueues (priorities 0 and 1) with sizes
+enough to store 15 and 10 messages respectively, the following code should
+be used
+
+```
+using Queue = rl::Queue<Storage, 15, 10>;
+```
+
+What is the recommended queue sizes? Well, it is completely application defined, but
+for `rotor-light` messages there should be enough space to store `N * 2` messages,
+where `N` is the total number of actors (including supervisors).
+
+What happens, if a queue is full? The next send message is simply discarded, you'll
+be notified about that as the `send()` method returns `false`. If the discarded
+message is `rotor-light` message, then *the framework behavior is undefined*. So,
+either do not overload default queue (i.e. with zero-priority), or use different
+queues for user-messages. NB: there is a spike of `rotor-light` messages only
+during (re)starting supervisor; when everything is *operational* there is almost no
+framework messages.
+
+The Planner (20) is used to schedule future events, like it is shown at `add_event`
+(14). The number in angle braces defines planner capacity at compile-time. When event
+time arrives, it is removed from the planner. Please note, that `rotor-light` does
+not schedules any events into the planner, so its capacity and usage is completely
+controlled by the user code.
+
+How the framework interacts with the planner? If the root supervisor is used in
+*poll mode*, then, when there are no more messages left, the current time is
+requested, and all timed out event handlers are executed. Then the special message
+`refresh-time` is send to the supervisor and the procedure repeats endlessly.
+Indirectly, this is similar to the code like:
+
+```
+while (true) {
+  while (has_messages()) {
+    auto& message = get_front_message();
+    process(message);
+    pop_front_message();
+  }
+  // (22)
+  auto now = get_now();
+  while (has_expired_events(now)) {
+    auto& event = get_next_expired_event(now);
+    fire_event(event);
+    pop_event(event);
+  }
+    
+```
+
+If the *await mode* is used, then user code should implement the whole `while` loop
+above and the event awaiting (22) code. This makes it possible to enter into
+power-save mode and wake up on external timer or interrupt. See `*-await.cpp`
+examples.
+
+In the line (21) the used supervisor is defined: the amount of handlers is specified
+(the same as for actor at (5), as each supervisor is an actor) and all its
+child actors (including other supervisors) are enumerated. The only `Blinker`
+child actor is specified in the current example.
+
+Let's move forward. In the following code `rotor-light` global variables are
+allocated:
+
+```
+Queue queue;
+Planner planner;
+rl::Context context{&queue, &planner, &get_now};
+Supervisor sup;
+```
+
+All of them can be allocated on the stack, but for the supervisor it is, probably,
+the bad idea, because most likely you'll need to access actors from some other
+global functions, i.e. ISR, and that can be done only via global supervisor
+instance. That might be not obvious, that the supervisor above allocates space for
+all its child actors, i.e. *supervisor contains and owns child actors*.
+
+The `get_now()` function pointer refers to a function with the following signature:
+
+```
+using TimePoint = int64_t;
+using NowFunction = TimePoint (*)();
+```
+
+It is the only link to the underlying hardware or system. The function should return
+current time; the meaning of "current time" (`TimePoint`) is completely user
+specific (i.e. it can point to seconds, milliseconds, microseconds etc.). The only
+requirement to the function, that it should be **monotonic**, i.e. do not decrease
+nor wrap each next `TimePoint` compared to the previous one. The implementation
+of the function is completely platform- or hardware-specific. For the arduino
+example it returns the number of microseconds passed since board boot.
 
 
-void initialize() override {
-  subscribe(&Blinker::on_blink_command);
-  Parent::initialize();
+The final piece of the current example is:
+
+```
+int main(int, char **) {
+  app_hw_init();
+
+  /* setup */
+  sup.bind(context);                        // (23)
+  auto blinker = sup.get_child<0>();        // (24)
+  blinker->delay = rl::Duration{250000};    // (25)
+  /* let it polls timer */
+  sup.start(true);                          // (26)
+
+  /* main cycle */
+  sup.process();                            // (27)
+  return 0;
 }
 }
 
 
-void advance_start() override {
-  Parent::advance_start();
-  add_event(
-      delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
+```
+
+The context binding (23) let the supervisor know pre-allocated queues, planner and
+the `get_now()` function. During context binding all actors get their unique ids,
+which can be used. After that individual actors can be accessed (24) with zero
+runtime overhead (as it uses `std::get` under the hood) and additional actors
+setup can be performed here (26), i.e. delay 1/4 of seconds between LED toggle.
+
+Please note, that setup phase (23-26) need to be performed **only once** despite
+of possible multiple actors restarts, i.e. actor identities are preserved during
+object lifetimes.
+
+Then, the whole machinery receive initial impulse (26). The `true` value here means
+usage of the "poll mode" (see above), i.e. endlessly send self a `refresh-timer`
+message, get time, and fire the timed-out events.
+
+In the line (27) supervisor actually starts processing messages, and probably never
+exits as soon as everything is going as expected. In the case of *failure
+escalation*, i.e. as it tried all possible restarts of actors to recover the
+failure, there is nothing left to do than exit and the whole board restart.
+
+## ping-pong messaging, poll timer
+
+In this example how to do messaging with `rotor-light` is demonstrated: `ping`
+message is sent from `pinger` actor to `ponger` actor. The `ponger` actor will
+reply back with `pong`  message, then after some delay `pinger` actor repeats
+the same procedure. The full code can be found at
+`examples/atmega328p/ping-pong-poll.cpp`.
+
+First of all used messages should be defined:
+
+```
+namespace message {
+struct Ping : rl::Message {
+  using Message::Message;
+  static constexpr auto type_id = __LINE__;
+  rl::MessageTypeId get_type_id() const override { return type_id; }
+};
+
+struct Pong : rl::Message {
+  using Message::Message;
+  static constexpr auto type_id = __LINE__;
+  rl::MessageTypeId get_type_id() const override { return type_id; }
+};
+} // namespace message
+
+```
+
+The `ping` and `pong` messages are content-less, why there is need of them for all?
+Because there is need to demonstrate how to send and receive messages and
+distinguish them by type.
+
+The `Pinger` actor code is:
+
+```
+struct Pinger : rl::Actor<2> {
+  using Parent = Actor<2>;
+
+  void initialize() override {
+    subscribe(&Pinger::on_pong); // (28)
+    Parent::initialize();
+  }
+
+  void advance_start() override {  // (29)
+    Parent::advance_start();       // (30)
+    ping();                        // (31)
+  }
+
+  void ping() {                         // (32)
+    /* toggle led */
+    PORTB ^= (1 << LED);
+    send<message::Ping>(0, ponger_id);  // (33)
+  }
+
+  void on_pong(message::Pong &) {          // (34)
+    add_event(500000, [](void *data) {     // (35)
+      static_cast<Pinger *>(data)->ping();
+    }, this);
+  }
+
+  rl::ActorId ponger_id;   // (36)
+};
+
+```
+
+As usually, the `initialize()` should be overridden to subscribe on `pong` messages
+(28). The `pinger` actor plays an *active role*, i.e. it sends initial `ping`
+message. This is performed in the overrisden `advance_start()` method (29), which is
+invoked as soon as the actor is ready: the default implementation machinery is
+invoked (30), and for convenience `ping()` (31) method is called. The `ping()` (32)
+method implementation is simple: after LED toggle, it sends the `ping` message (33).
+
+The `send` method parameters are: the message type (`message::Ping`) template
+parameter, message priority (aka destination queue) - `0`, and the destination
+actor(s) - ponger actor id, defined at (36). If there are additional message params,
+specified in the message type constructor, they should go here.
+
+As soon as `pong` reply is received (34), the ping procedure with LED toggle
+is rescheduled after 500000 microseconds (i.e. 0.5 second) at (35).
+
+The `ponger` actor code is rather trivial:
+
+```
+struct Ponger : rl::Actor<2> {
+  using Parent = Actor<2>;
+
+  void initialize() override {
+    subscribe(&Ponger::on_ping);
+    Parent::initialize();
+  }
+  void on_ping(message::Ping &) {
+    send<message::Pong>(0, pinger_id);  // (37)
+  }
+  rl::ActorId pinger_id;   // (38)
+};
+
+```
+
+as soon `ping` message is received, it replies back (37) with `pong` message. Please
+note, while conceptually it "replies back", technically it just sends a new `pong`
+message to pinger address, defined at (38). It is important, because there is no
+request-response pattern, i.e. it "knows" whom to send back the "reply".
+
+Lets move forward.
+
+```
+using Supervisor = rl::Supervisor<
+    rl::SupervisorBase::min_handlers_amount,
+    Pinger,
+    Ponger>;
+    
+using Storage = rl::traits::MessageStorage<rl::message::ChangeState,
+                                           rl::message::ChangeStateAck,
+                                           message::Ping,
+                                           message::Pong>;
+```
+
+The standard supervisor is used; it owns `pinger` and `ponger` actors. The `Storage`
+allocates enough space for `Ping` and `Pong` messages.
+
+
+```
+int main(int, char **) {
+
+  app_hw_init();
+
+  /* setup */
+  sup.bind(context);
+  auto pinger = sup.get_child<0>();      // (39)
+  auto ponger = sup.get_child<1>();      // (40)
+  pinger->ponger_id = ponger->get_id();  // (41)
+  ponger->pinger_id = pinger->get_id();  // (42)
+  /* let it polls timer */
+  sup.start(true);
+
+  /* main cycle */
+  sup.process();   // (43)
+  return 0;
 }
 }
+```
+
+The most interesting part is the setup-phase (39-40). `pinger` and `ponger` actors
+should know each others addresses, and the addresses are available only after
+context binding.
+
+When everything is ready, it enters into main loop (43). In the loop it either
+delivers `rotor-light` messages or waits, until the next event time occurs. It uses
+**busy waiting** by actively polling (querying) timer, whether the next event time
+happend. As usually for busy waiting, it consumes 100% CPU time, which is common
+strategy for embedded/real-time applications.
+
+It is possible however to do something else, instead of endless timer polling, i.e.
+do CPU sleep (and consume less current and do less CO2 emission). See the
+next section.
+
+## ping-pong messaging, await the next event in power-save mode
+
+The code for this example is located at `examples/atmega328p/ping-pong-await.cpp`.
+From user point of view the example does the same as the previous one:
+it sends `ping` message, receives `pong` message, blinks with LED and after some
+delay the procedure repeats.
+
+However, the notable difference is in the delay: in the previous example it
+endlessly polls the timer whether the time arrives for the next event, burning
+CPU cycles, in the current example it does energy-efficient sleeping while
+there is noting to do (i.e. no messages) between events.
 
 
-void blink() {
-  toggle_led();
-  add_event(
-      delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
+As the main logic is the same, the actor-related code is also the same; only
+`main()` function differs. 
+
+```
+int main(int, char **) {
+  app_hw_init();
+
+  /* allocate */
+  Queue queue;
+  Planner planner;
+  rl::Context context{&queue, &planner, &get_now};
+  Supervisor sup;
+
+  /* setup */
+  sup.bind(context);
+  auto pinger = sup.get_child<0>();
+  auto ponger = sup.get_child<1>();
+  pinger->ponger_id = ponger->get_id();
+  ponger->pinger_id = pinger->get_id();
+  /* let it polls timer */
+  sup.start(false);  // (44)
+
+  /* main cycle */
+  while (true) {                                   // (45)
+    sup.process();                                 // (46)
+    auto next_event_time = planner.next_event();   // (47)
+    if (next_event_time) {                         // (48)
+      perform_sleep(next_event_time);              // (49)
+    }
+  }
+  return 0;
 }
 }
 
 
-void on_blink_command(message::BlinkCommand &msg) { blink(); }
+```
+
+Firstly, the timer poll via recursively sending self `refresh-timer` message should
+be disabled (44). Then the infinite loop (45) should be started, as the supervisor
+exits, when it has no more messages to process (46). The first nearby event should
+be extracted from planner (47), if any (48), and then perform platform-specific
+sleep until the event. The event handler will be actually processed upon the next
+loop iteration in (46).
+
+## integration
+
+Lets suppose, that there is a need to read a custom port on idle or perform some
+I/O activity, maybe indirectly via calling some other library on each iteration.
+How can that be done?
+
+One of the ways to accomplish that is shown above: disable timer poll and do
+the needed activity instead of entering into powersave mode, or in addition to
+the powersave mode.
+
+Then second way is to have a custom supervisor with `on_refhesh_timer` method
+overridden, like that:
+
+```
+struct MySupervisor : rl::Supervisor<3, MyActor1, MyActor2, ...> {
+using Parent = rl::Supervisor<3, MyActor1, MyActor2, ...>; 
+
+void on_refhesh_timer(rl::message::RefreshTime &message) override {
+  Parent::on_refhesh_timer(message);
+  third_party_library_poll();
+}
 
 
-rl::Duration delay;
 };
 };
+
 ```
 ```

+ 12 - 1
examples/CMakeLists.txt

@@ -6,10 +6,21 @@ if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AVR")
     add_subdirectory("atmega328p")
     add_subdirectory("atmega328p")
 elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Cortex-M7")
 elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Cortex-M7")
     add_subdirectory("NUCLEO-H743ZI2")
     add_subdirectory("NUCLEO-H743ZI2")
+elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "MICROBLAZE")
+    add_subdirectory("mb")
 elseif ("${TOOLCHAIN_PREFIX}" STREQUAL "")
 elseif ("${TOOLCHAIN_PREFIX}" STREQUAL "")
     add_subdirectory("host")
     add_subdirectory("host")
+    set(CMAKE_SIZE_UTIL size CACHE INTERNAL "size tool")
 endif()
 endif()
 
 
+
 # any platform, as there is no timer
 # any platform, as there is no timer
 add_executable(ping-pong-throughput ping-pong-throughput.cpp)
 add_executable(ping-pong-throughput ping-pong-throughput.cpp)
-target_link_libraries(ping-pong-throughput rotor_light)
+target_link_libraries(ping-pong-throughput board)
+add_custom_command(TARGET ping-pong-throughput
+    POST_BUILD COMMAND ${CMAKE_SIZE_UTIL} ping-pong-throughput)
+
+if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Cortex-M7")
+    set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/NUCLEO-H743ZI2/STM32H743ZITx_FLASH.ld)
+    target_link_options(ping-pong-throughput PUBLIC -T ${LINKER_SCRIPT})
+endif()

+ 61 - 0
examples/NUCLEO-H743ZI2/CMakeLists.txt

@@ -0,0 +1,61 @@
+set(CMAKE_SYSTEM_NAME Generic)
+set(CMAKE_SYSTEM_VERSION 1)
+cmake_minimum_required(VERSION 3.8)
+cmake_policy(SET CMP0069 NEW)
+
+project(ping-pong VERSION 0.1
+        DESCRIPTION "ping-pong with rotor light"
+        LANGUAGES C CXX ASM)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_C_STANDARD 11)
+
+# Enable assembler files preprocessing
+add_compile_options($<$<COMPILE_LANGUAGE:ASM>:-x$<SEMICOLON>assembler-with-cpp>)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-register")
+
+if (NOT ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Cortex-M7" ))
+    message(FATAL_ERROR "wrong system processor ${CMAKE_SYSTEM_PROCESSOR}, expected: Cortex-M7")
+endif()
+
+set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/STM32H743ZITx_FLASH.ld)
+
+add_definitions(-DDEBUG
+        -DUSE_FULL_LL_DRIVER
+        -DHSE_VALUE=8000000
+        -DSTM32H743xx
+        -DUSE_FULL_ASSERT
+        )
+
+add_library(board
+    board.cpp
+    startup_stm32h743xx.s
+    Core/Src/main.c
+    Core/Src/gpio.c
+    Core/Src/stm32h7xx_it.c
+    Core/Src/system_stm32h7xx.c
+    Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_utils.c
+    Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_rcc.c
+    Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_pwr.c
+    Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_gpio.c
+    Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_exti.c
+    ${LINKER_SCRIPT}
+)
+target_include_directories(rotor_light PUBLIC "${LIBSTDCPP_HOME}/include")
+target_include_directories(board PUBLIC
+   "${CMAKE_CURRENT_SOURCE_DIR}"
+    ${CMAKE_CURRENT_SOURCE_DIR}/Core/Inc
+    ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32H7xx_HAL_Driver/Inc
+    ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32H7xx/Include
+    ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/Include
+)
+target_link_libraries(board rotor_light)
+
+add_executable(${PROJECT_NAME} ping-pong-poll.cpp)
+target_link_libraries(${PROJECT_NAME} board)
+
+target_link_options(${PROJECT_NAME} PUBLIC
+        LINKER:-Map=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.map
+        -T ${LINKER_SCRIPT})
+
+set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX .elf)

+ 49 - 0
examples/NUCLEO-H743ZI2/Core/Inc/gpio.h

@@ -0,0 +1,49 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    gpio.h
+  * @brief   This file contains all the function prototypes for
+  *          the gpio.c file
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2022 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef __GPIO_H__
+#define __GPIO_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+
+/* USER CODE BEGIN Includes */
+
+/* USER CODE END Includes */
+
+/* USER CODE BEGIN Private defines */
+
+/* USER CODE END Private defines */
+
+void MX_GPIO_Init(void);
+
+/* USER CODE BEGIN Prototypes */
+
+/* USER CODE END Prototypes */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /*__ GPIO_H__ */
+

+ 106 - 0
examples/NUCLEO-H743ZI2/Core/Inc/main.h

@@ -0,0 +1,106 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file           : main.h
+  * @brief          : Header for main.c file.
+  *                   This file contains the common defines of the application.
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2022 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef __MAIN_H
+#define __MAIN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Includes ------------------------------------------------------------------*/
+
+#include "stm32h7xx_ll_rcc.h"
+#include "stm32h7xx_ll_crs.h"
+#include "stm32h7xx_ll_bus.h"
+#include "stm32h7xx_ll_system.h"
+#include "stm32h7xx_ll_exti.h"
+#include "stm32h7xx_ll_cortex.h"
+#include "stm32h7xx_ll_utils.h"
+#include "stm32h7xx_ll_pwr.h"
+#include "stm32h7xx_ll_dma.h"
+#include "stm32h7xx_ll_gpio.h"
+
+#if defined(USE_FULL_ASSERT)
+#include "stm32_assert.h"
+#endif /* USE_FULL_ASSERT */
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+
+/* USER CODE END Includes */
+
+/* Exported types ------------------------------------------------------------*/
+/* USER CODE BEGIN ET */
+
+/* USER CODE END ET */
+
+/* Exported constants --------------------------------------------------------*/
+/* USER CODE BEGIN EC */
+
+/* USER CODE END EC */
+
+/* Exported macro ------------------------------------------------------------*/
+/* USER CODE BEGIN EM */
+
+/* USER CODE END EM */
+
+/* Exported functions prototypes ---------------------------------------------*/
+void Error_Handler(void);
+
+/* USER CODE BEGIN EFP */
+
+/* USER CODE END EFP */
+
+/* Private defines -----------------------------------------------------------*/
+#define B1_Pin LL_GPIO_PIN_13
+#define B1_GPIO_Port GPIOC
+#define LD1_Pin LL_GPIO_PIN_0
+#define LD1_GPIO_Port GPIOB
+#define LD3_Pin LL_GPIO_PIN_14
+#define LD3_GPIO_Port GPIOB
+#define STLINK_RX_Pin LL_GPIO_PIN_8
+#define STLINK_RX_GPIO_Port GPIOD
+#define STLINK_TX_Pin LL_GPIO_PIN_9
+#define STLINK_TX_GPIO_Port GPIOD
+#define LD2_Pin LL_GPIO_PIN_1
+#define LD2_GPIO_Port GPIOE
+#ifndef NVIC_PRIORITYGROUP_0
+#define NVIC_PRIORITYGROUP_0         ((uint32_t)0x00000007) /*!< 0 bit  for pre-emption priority,
+                                                                 4 bits for subpriority */
+#define NVIC_PRIORITYGROUP_1         ((uint32_t)0x00000006) /*!< 1 bit  for pre-emption priority,
+                                                                 3 bits for subpriority */
+#define NVIC_PRIORITYGROUP_2         ((uint32_t)0x00000005) /*!< 2 bits for pre-emption priority,
+                                                                 2 bits for subpriority */
+#define NVIC_PRIORITYGROUP_3         ((uint32_t)0x00000004) /*!< 3 bits for pre-emption priority,
+                                                                 1 bit  for subpriority */
+#define NVIC_PRIORITYGROUP_4         ((uint32_t)0x00000003) /*!< 4 bits for pre-emption priority,
+                                                                 0 bit  for subpriority */
+#endif
+/* USER CODE BEGIN Private defines */
+//void SystemClock_Config(void);
+/* USER CODE END Private defines */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MAIN_H */

+ 0 - 0
examples/NUCLEO-H743ZI2/Core/Inc/stm32_assert.h


部分文件因文件數量過多而無法顯示