36 Revīzijas 93af118363 ... ade4ce9198

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

+ 13 - 3
CMakeLists.txt

@@ -13,6 +13,11 @@ if (NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET AND
   set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
 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(GenerateExportHeader)
 
@@ -36,10 +41,15 @@ set_target_properties(rotor_light PROPERTIES
     CXX_STANDARD_REQUIRED YES
     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)
+    add_subdirectory("examples")
+
     if ("${TOOLCHAIN_PREFIX}" STREQUAL "")
         include(CTest)
         if (BUILD_TESTING)
@@ -48,7 +58,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
     endif()
 
     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_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
         configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)

+ 16 - 9
README.md

@@ -13,11 +13,7 @@ care about thread-safety.
 
  - erlang-like hierarchical supervisors
 
- - message priorities
-
- - non-intrusiveness
-
- - asynchornous message passing interface
+ - asynchronous messaging with priorities
 
  - plaform-agnostic code (including timers)
 
@@ -25,20 +21,31 @@ care about 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`,
 compiled with `CMAKE_BUILD_TYPE=MinSizeRel` and the stripped
 
 (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
 
 MIT

+ 1 - 0
docs/Doxyfile.in

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

+ 52 - 21
docs/Introduction.md

@@ -1,32 +1,32 @@
 # 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
-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
 
-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
 
@@ -52,10 +52,41 @@ The following table highlight the differences
 (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.
 
-(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
 [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.
+
+# 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
 
 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:
 
@@ -19,34 +20,30 @@ namespace rl = rotor_light;
 
 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
-convenient to deail with messages later (handle and send).
+convenient to deal with messages later (handle and send).
 
 ```
 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
 ```
 
-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
-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
 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
 header file.
 
@@ -54,28 +51,444 @@ Let's move to the blinker actor code; as it is rather small, it is shown
 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")
 elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Cortex-M7")
     add_subdirectory("NUCLEO-H743ZI2")
+elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "MICROBLAZE")
+    add_subdirectory("mb")
 elseif ("${TOOLCHAIN_PREFIX}" STREQUAL "")
     add_subdirectory("host")
+    set(CMAKE_SIZE_UTIL size CACHE INTERNAL "size tool")
 endif()
 
+
 # any platform, as there is no timer
 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


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels