From 01e522ef84a7371630554edc4d9cb7add85cd608 Mon Sep 17 00:00:00 2001
From: Terraneo Federico <fede.tft@miosix.org>
Date: Sat, 18 Aug 2018 15:25:18 +0200
Subject: [PATCH] Added logger example

---
 .gitmodules                                   |   3 +
 miosix/_examples/datalogger/ExampleData.h     |  55 +++
 miosix/_examples/datalogger/LogStats.h        |  72 ++++
 miosix/_examples/datalogger/Logger.cpp        | 327 ++++++++++++++++++
 miosix/_examples/datalogger/Logger.h          | 208 +++++++++++
 miosix/_examples/datalogger/Makefile          | 108 ++++++
 miosix/_examples/datalogger/Readme.txt        |  30 ++
 .../_examples/datalogger/logdecoder/Makefile  |   6 +
 .../datalogger/logdecoder/logdecoder.cpp      |  59 ++++
 miosix/_examples/datalogger/main.cpp          |  72 ++++
 miosix/_examples/datalogger/tscpp             |   1 +
 11 files changed, 941 insertions(+)
 create mode 100644 .gitmodules
 create mode 100644 miosix/_examples/datalogger/ExampleData.h
 create mode 100644 miosix/_examples/datalogger/LogStats.h
 create mode 100644 miosix/_examples/datalogger/Logger.cpp
 create mode 100644 miosix/_examples/datalogger/Logger.h
 create mode 100644 miosix/_examples/datalogger/Makefile
 create mode 100644 miosix/_examples/datalogger/Readme.txt
 create mode 100644 miosix/_examples/datalogger/logdecoder/Makefile
 create mode 100644 miosix/_examples/datalogger/logdecoder/logdecoder.cpp
 create mode 100644 miosix/_examples/datalogger/main.cpp
 create mode 160000 miosix/_examples/datalogger/tscpp

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..7c6898ac
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "miosix/_examples/datalogger/tscpp"]
+	path = miosix/_examples/datalogger/tscpp
+	url = https://github.com/fedetft/tscpp.git
diff --git a/miosix/_examples/datalogger/ExampleData.h b/miosix/_examples/datalogger/ExampleData.h
new file mode 100644
index 00000000..c70f7d3d
--- /dev/null
+++ b/miosix/_examples/datalogger/ExampleData.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ *   Copyright (C) 2018 by Terraneo Federico                               *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   As a special exception, if other files instantiate templates or use   *
+ *   macros or inline functions from this file, or you compile this file   *
+ *   and link it with other works to produce a work based on this file,    *
+ *   this file does not by itself cause the resulting work to be covered   *
+ *   by the GNU General Public License. However the source code for this   *
+ *   file must still be made available in accordance with the GNU General  *
+ *   Public License. This exception does not invalidate any other reasons  *
+ *   why a work based on this file might be covered by the GNU General     *
+ *   Public License.                                                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
+ ***************************************************************************/ 
+
+#pragma once
+
+#include <ostream>
+
+/**
+ * Example class for data to be logged.
+ * Start from this class to define classes with the information you want to log.
+ */
+class ExampleData
+{
+public:
+    ExampleData() {}
+    
+    ExampleData(int a, int b, long long timestamp) : a(a), b(b), timestamp(timestamp) {}
+
+    /**
+     * Print the class fields to an ostream.
+     * Used by the program that decodes the logged data.
+     * \param os ostream where to print the class fields
+     */
+    void print(std::ostream& os) const
+    {
+        os << "timestamp=" << timestamp << " a=" << a << " b=" << b;
+    }
+
+    int a, b;
+    long long timestamp;
+};
diff --git a/miosix/_examples/datalogger/LogStats.h b/miosix/_examples/datalogger/LogStats.h
new file mode 100644
index 00000000..5c897af4
--- /dev/null
+++ b/miosix/_examples/datalogger/LogStats.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ *   Copyright (C) 2018 by Terraneo Federico                               *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   As a special exception, if other files instantiate templates or use   *
+ *   macros or inline functions from this file, or you compile this file   *
+ *   and link it with other works to produce a work based on this file,    *
+ *   this file does not by itself cause the resulting work to be covered   *
+ *   by the GNU General Public License. However the source code for this   *
+ *   file must still be made available in accordance with the GNU General  *
+ *   Public License. This exception does not invalidate any other reasons  *
+ *   why a work based on this file might be covered by the GNU General     *
+ *   Public License.                                                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
+ ***************************************************************************/ 
+
+#pragma once
+
+#include <ostream>
+
+/**
+ * Statistics for the logger
+ */
+class LogStats
+{
+public:
+    /**
+     * Constructor
+     */
+    LogStats() {}
+    
+    /**
+     * Set timestamp for this class
+     * \param timestamp timestamp
+     */
+    void setTimestamp(long long timestamp) { this->timestamp = timestamp; }
+
+    /**
+     * Print the class fields to an ostream.
+     * Used by the program that decodes the logged data.
+     * \param os ostream where to print the class fields
+     */
+    void print(std::ostream& os) const
+    {
+        os << "timestamp=" << timestamp
+           << " ls=" << statTooLargeSamples << " ds=" << statDroppedSamples
+           << " qs=" << statQueuedSamples << " bf=" << statBufferFilled
+           << " bw=" << statBufferWritten << " wf=" << statWriteFailed
+           << " wt=" << statWriteTime << " mwt=" << statMaxWriteTime;
+    }
+
+    long long timestamp; ///< Timestamp
+    int statTooLargeSamples = 0;  ///< Number of dropped samples because too large
+    int statDroppedSamples  = 0;  ///< Number of dropped samples due to fifo full
+    int statQueuedSamples   = 0;  ///< Number of samples written to buffer
+    int statBufferFilled    = 0;  ///< Number of buffers filled
+    int statBufferWritten   = 0;  ///< Number of buffers written to disk
+    int statWriteFailed     = 0;  ///< Number of fwrite() that failed
+    int statWriteTime       = 0;  ///< Time to perform an fwrite() of a buffer
+    int statMaxWriteTime    = 0;  ///< Max time to perform an fwrite() of a buffer
+};
diff --git a/miosix/_examples/datalogger/Logger.cpp b/miosix/_examples/datalogger/Logger.cpp
new file mode 100644
index 00000000..a8036bb0
--- /dev/null
+++ b/miosix/_examples/datalogger/Logger.cpp
@@ -0,0 +1,327 @@
+/***************************************************************************
+ *   Copyright (C) 2018 by Terraneo Federico                               *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   As a special exception, if other files instantiate templates or use   *
+ *   macros or inline functions from this file, or you compile this file   *
+ *   and link it with other works to produce a work based on this file,    *
+ *   this file does not by itself cause the resulting work to be covered   *
+ *   by the GNU General Public License. However the source code for this   *
+ *   file must still be made available in accordance with the GNU General  *
+ *   Public License. This exception does not invalidate any other reasons  *
+ *   why a work based on this file might be covered by the GNU General     *
+ *   Public License.                                                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
+ ***************************************************************************/ 
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdexcept>
+#include <interfaces/atomic_ops.h>
+#include <tscpp/buffer.h>
+#include "Logger.h"
+
+using namespace std;
+using namespace miosix;
+using namespace tscpp;
+
+//
+// class Logger
+//
+
+Logger& Logger::instance()
+{
+    static Logger logger;
+    return logger;
+}
+
+void Logger::start()
+{
+    if (started)
+        return;
+
+    char filename[32];
+    for (unsigned int i = 0; i < filenameMaxRetry; i++)
+    {
+        sprintf(filename, "/sd/%02d.dat", i);
+        struct stat st;
+        if (stat(filename, &st) != 0)
+            break;
+        // File exists
+        if (i == filenameMaxRetry - 1)
+            puts("Too many files, appending to last");
+    }
+
+    file = fopen(filename, "ab");
+    if (file == NULL)
+        throw runtime_error("Error opening log file");
+    setbuf(file, NULL);
+
+    // The boring part, start threads one by one and if they fail, undo
+    // Perhaps excessive defensive programming as thread creation failure is
+    // highly unlikely (only if ram is full)
+
+    packT = Thread::create(packThreadLauncher, 1536, 1, this, Thread::JOINABLE);
+    if (!packT)
+    {
+        fclose(file);
+        throw runtime_error("Error creating pack thread");
+    }
+
+    writeT =
+        Thread::create(writeThreadLauncher, 2048, 1, this, Thread::JOINABLE);
+    if (!writeT)
+    {
+        fullQueue.put(nullptr);  // Signal packThread to stop
+        packT->join();
+        // packThread has pushed a buffer and a nullptr to writeThread, remove
+        // it
+        while (fullList.front() != nullptr)
+        {
+            emptyList.push(fullList.front());
+            fullList.pop();
+        }
+        fullList.pop();  // Remove nullptr
+        fclose(file);
+        throw runtime_error("Error creating write thread");
+    }
+    if(logStatsEnabled)
+    {
+        statsT =
+            Thread::create(statsThreadLauncher, 1536, 1, this, Thread::JOINABLE);
+        if (!statsT)
+        {
+            fullQueue.put(nullptr);  // Signal packThread to stop
+            packT->join();
+            writeT->join();
+            fclose(file);
+            throw runtime_error("Error creating stats thread");
+        }
+    }
+    started = true;
+}
+
+void Logger::stop()
+{
+    if(started == false) return;
+    if(logStatsEnabled) logStats();
+    started = false;
+    fullQueue.put(nullptr);  // Signal packThread to stop
+    packT->join();
+    writeT->join();
+    if(logStatsEnabled) statsT->join();
+    fclose(file);
+}
+
+Logger::Logger()
+{
+    // Allocate buffers and put them in the empty list
+    for (unsigned int i = 0; i < numBuffers; i++)
+        emptyList.push(new Buffer);
+    for (unsigned int i = 0; i < numRecords; i++)
+        emptyQueue.put(new Record);
+}
+
+void Logger::packThreadLauncher(void* argv)
+{
+    reinterpret_cast<Logger*>(argv)->packThread();
+}
+
+void Logger::writeThreadLauncher(void* argv)
+{
+    reinterpret_cast<Logger*>(argv)->writeThread();
+}
+
+void Logger::statsThreadLauncher(void* argv)
+{
+    reinterpret_cast<Logger*>(argv)->statsThread();
+}
+
+LogResult Logger::logImpl(const char* name, const void* data, unsigned int size)
+{
+    if (started == false)
+        return LogResult::Ignored;
+
+    Record* record = nullptr;
+    {
+        FastInterruptDisableLock dLock;
+        // We disable interrupts because IRQget() is nonblocking, unlike get()
+        if (emptyQueue.IRQget(record) == false)
+        {
+            s.statDroppedSamples++;
+            return LogResult::Dropped;
+        }
+    }
+    
+    auto result = serializeImpl(record->data, maxRecordSize, name, data, size);
+    if (result == BufferTooSmall)
+    {
+        emptyQueue.put(record);
+        atomicAdd(&s.statTooLargeSamples, 1);
+        return LogResult::TooLarge;
+    }
+    
+    record->size = result;
+    fullQueue.put(record);
+    atomicAdd(&s.statQueuedSamples, 1);
+    return LogResult::Queued;
+}
+
+void Logger::packThread()
+{
+    /*
+     * The first implementation of this class had the log() function write
+     * directly the serialized data to the buffers. So, no Records nor
+     * packThread existed. However, to be able to call log() concurrently
+     * without messing up the buffer, a mutex was needed. Thus, if many
+     * threads call log(), contention on that mutex would occur, serializing
+     * accesses and slowing down the (potentially real-time) callers. For this
+     * reason Records and the pack thread were added.
+     * Now each log() works independently on its own Record, and log() accesses
+     * can proceed in parallel.
+     */
+    try
+    {
+        for (;;)
+        {
+            Buffer* buffer = nullptr;
+            {
+                Lock<FastMutex> l(mutex);
+                // Get an empty buffer, wait if none is available
+                while (emptyList.empty())
+                    cond.wait(l);
+                buffer = emptyList.front();
+                emptyList.pop();
+                buffer->size = 0;
+            }
+
+            do
+            {
+                Record* record = nullptr;
+                fullQueue.get(record);
+
+                // When stop() is called, it pushes a nullptr signaling to stop
+                if (record == nullptr)
+                {
+                    Lock<FastMutex> l(mutex);
+                    fullList.push(buffer);   // Don't lose the buffer
+                    fullList.push(nullptr);  // Signal writeThread to stop
+                    cond.broadcast();
+                    s.statBufferFilled++;
+                    return;
+                }
+
+                memcpy(buffer->data + buffer->size, record->data, record->size);
+                buffer->size += record->size;
+                emptyQueue.put(record);
+            } while (bufferSize - buffer->size >= maxRecordSize);
+
+            {
+                Lock<FastMutex> l(mutex);
+                // Put back full buffer
+                fullList.push(buffer);
+                cond.broadcast();
+                s.statBufferFilled++;
+            }
+        }
+    }
+    catch (exception& e)
+    {
+        printf("Error: packThread failed due to an exception: %s\n", e.what());
+    }
+}
+
+void Logger::writeThread()
+{
+    try
+    {
+        for (;;)
+        {
+            Buffer* buffer = nullptr;
+            {
+                Lock<FastMutex> l(mutex);
+                // Get a full buffer, wait if none is available
+                while (fullList.empty())
+                    cond.wait(l);
+                buffer = fullList.front();
+                fullList.pop();
+            }
+
+            // When packThread stops, it pushes a nullptr signaling to stop
+            if (buffer == nullptr)
+                return;
+
+            // Write data to disk
+            Timer timer;
+            timer.start();
+//             ledOn();
+
+            size_t result = fwrite(buffer->data, 1, buffer->size, file);
+            if (result != buffer->size)
+            {
+                // If this fails and your board uses SDRAM,
+                // define and increase OVERRIDE_SD_CLOCK_DIVIDER_MAX
+                // perror("fwrite");
+                s.statWriteFailed++;
+            }
+            else
+                s.statBufferWritten++;
+
+//             ledOff();
+            timer.stop();
+            s.statWriteTime    = timer.interval();
+            s.statMaxWriteTime = max(s.statMaxWriteTime, s.statWriteTime);
+
+            {
+                Lock<FastMutex> l(mutex);
+                // Put back empty buffer
+                emptyList.push(buffer);
+                cond.broadcast();
+            }
+        }
+    }
+    catch (exception& e)
+    {
+        printf("Error: writeThread failed due to an exception: %s\n", e.what());
+    }
+}
+
+/**
+ * This thread prints stats
+ */
+void Logger::statsThread()
+{
+    if(logStatsEnabled)
+    {
+        try
+        {
+            for (;;)
+            {
+                Thread::sleep(1000);
+                if (started == false)
+                    return;
+                logStats();
+//                 printf("ls:%d ds:%d qs:%d bf:%d bw:%d wf:%d wt:%d mwt:%d\n",
+//                        s.statTooLargeSamples, s.statDroppedSamples,
+//                        s.statQueuedSamples, s.statBufferFilled, s.statBufferWritten,
+//                        s.statWriteFailed, s.statWriteTime, s.statMaxWriteTime);
+            }
+        }
+        catch (exception& e)
+        {
+            printf("Error: statsThread failed due to an exception: %s\n", e.what());
+        }
+    }
+}
diff --git a/miosix/_examples/datalogger/Logger.h b/miosix/_examples/datalogger/Logger.h
new file mode 100644
index 00000000..af882136
--- /dev/null
+++ b/miosix/_examples/datalogger/Logger.h
@@ -0,0 +1,208 @@
+/***************************************************************************
+ *   Copyright (C) 2018 by Terraneo Federico                               *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   As a special exception, if other files instantiate templates or use   *
+ *   macros or inline functions from this file, or you compile this file   *
+ *   and link it with other works to produce a work based on this file,    *
+ *   this file does not by itself cause the resulting work to be covered   *
+ *   by the GNU General Public License. However the source code for this   *
+ *   file must still be made available in accordance with the GNU General  *
+ *   Public License. This exception does not invalidate any other reasons  *
+ *   why a work based on this file might be covered by the GNU General     *
+ *   Public License.                                                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
+ ***************************************************************************/ 
+
+#pragma once
+
+#include <miosix.h>
+#include <cstdio>
+#include <list>
+#include <queue>
+#include <type_traits>
+#include "LogStats.h"
+
+/**
+ * Possible outcomes of Logger::log()
+ */
+enum class LogResult
+{
+    Queued,   ///< Data has been accepted by the logger and will be written
+    Dropped,  ///< Buffers are currently full, data will not be written. Sorry
+    Ignored,  ///< Logger is currently stopped, data will not be written
+    TooLarge  ///< Data is too large to be logged. Increase maxRecordSize
+};
+
+/**
+ * Buffered logger. Needs to be started before it can be used.
+ */
+class Logger
+{
+public:
+    /**
+     * \return an instance to the logger
+     */
+    static Logger &instance();
+
+    /**
+     * Blocking call. May take a long time.
+     *
+     * Call this function to start the logger.
+     * When this function returns, the logger is started, and subsequent calls
+     * to log will actually log the data.
+     * 
+     * Do not call concurrently from multiple threads.
+     *
+     * \throws runtime_error if the log could not be opened
+     */
+    void start();
+
+    /**
+     * Blocking call. May take a very long time (seconds).
+     *
+     * Call this function to stop the logger.
+     * When this function returns, all log buffers have been flushed to disk,
+     * and it is safe to power down the board without losing log data or
+     * corrupting the filesystem.
+     * 
+     * Do not call concurrently from multiple threads.
+     */
+    void stop();
+
+    /**
+     * Nonblocking call. Safe to be called concurrently from multiple threads.
+     *
+     * \return true if the logger is started and ready to accept data.
+     */
+    bool isStarted() const { return started; }
+
+    /**
+     * Nonblocking call. Safe to be called concurrently from multiple threads.
+     * 
+     * Call this function to log a class.
+     * \param t the class to be logged. This class has the following
+     * requirements:
+     * - it must be trivially_copyable, so no pointers or references inside
+     *   the class, no stl containers, no virtual functions, no base classes.
+     * - it must have a "void print(std::ostream& os) const" member function
+     *   that prints all its data fields in text form (this is not used by the
+     *   logger, but by the log decoder program)
+     * \return whether the class has been logged
+     */
+    template<typename T>
+    LogResult log(const T& t)
+    {
+        //static_assert(std::is_trivially_copyable<T>::value,"");
+        return logImpl(typeid(t).name(),&t,sizeof(t));
+    }
+    
+    /**
+     * \return logger stats.
+     * Only safe to be called when the logger is stopped.
+     */
+    LogStats getStats() const { return s; }
+
+private:
+    Logger();
+    Logger(const Logger &) = delete;
+    Logger &operator=(const Logger &) = delete;
+
+    static void packThreadLauncher(void *argv);
+    static void writeThreadLauncher(void *argv);
+    static void statsThreadLauncher(void *argv);
+    
+    /**
+     * Non-template dependente part of log
+     * \param name class anem
+     * \param data pointer to class data
+     * \param size class size
+     */
+    LogResult logImpl(const char *name, const void *data, unsigned int size);
+
+    /**
+     * This thread packs logged data into buffers
+     */
+    void packThread();
+
+    /**
+     * This thread writes packed buffers to disk
+     */
+    void writeThread();
+
+    /**
+     * This thread prints stats
+     */
+    void statsThread();
+
+    /**
+     * Log logger stats using the logger itself
+     */
+    void logStats()
+    {
+        s.setTimestamp(miosix::getTick());
+        log(s);
+    }
+
+    static const unsigned int filenameMaxRetry = 100; ///< Limit on new filename
+    static const unsigned int maxRecordSize    = 128; ///< Limit on logged data
+    static const unsigned int numRecords       = 128; ///< Size of record queues
+    static const unsigned int bufferSize       = 4096;///< Size of each buffer
+    static const unsigned int numBuffers       = 4;   ///< Number of buffers
+    static constexpr bool logStatsEnabled      = true;///< Log logger stats?
+
+    /**
+     * A record is a single serialized logged class. Records are used to
+     * make log() lock-free. Since each call to log() works on its independent
+     * Record, calls to log do not need a global mutex which could block
+     * threads calling log() concurrently
+     */
+    class Record
+    {
+    public:
+        Record() : size(0) {}
+        char data[maxRecordSize];
+        unsigned int size;
+    };
+
+    /**
+     * A buffer is what is written on disk. It is filled by packing records.
+     * The reason why we don't write records directly is that they are too
+     * small to efficiently use disk bandwidth. SD cards are much faster when
+     * data is written in large chunks.
+     */
+    class Buffer
+    {
+    public:
+        Buffer() : size(0) {}
+        char data[bufferSize];
+        unsigned int size;
+    };
+
+    miosix::Queue<Record *, numRecords> fullQueue;        ///< Full records
+    miosix::Queue<Record *, numRecords> emptyQueue;       ///< Empty Records
+    std::queue<Buffer *, std::list<Buffer *>> fullList;   ///< Full buffers
+    std::queue<Buffer *, std::list<Buffer *>> emptyList;  ///< Empty buffers
+    miosix::FastMutex mutex;  ///< To allow concurrent access to the queues
+    miosix::ConditionVariable cond;  ///< To lock when buffers are all empty
+
+    miosix::Thread *packT;   ///< Thread packing logged data
+    miosix::Thread *writeT;  ///< Thread writing data to disk
+    miosix::Thread *statsT;  ///< Thred printing stats
+
+    volatile bool started = false;  ///< Logger is started and accepting data
+
+    FILE *file;  ///< Log file
+    LogStats s;  ///< Logger stats
+};
diff --git a/miosix/_examples/datalogger/Makefile b/miosix/_examples/datalogger/Makefile
new file mode 100644
index 00000000..1e70f8f9
--- /dev/null
+++ b/miosix/_examples/datalogger/Makefile
@@ -0,0 +1,108 @@
+##
+## Makefile for Miosix embedded OS
+##
+MAKEFILE_VERSION := 1.07
+## Path to kernel directory (edited by init_project_out_of_git_repo.pl)
+KPATH := miosix
+## Path to config directory (edited by init_project_out_of_git_repo.pl)
+CONFPATH := $(KPATH)
+include $(CONFPATH)/config/Makefile.inc
+
+##
+## List here subdirectories which contains makefiles
+##
+SUBDIRS := $(KPATH)
+
+##
+## List here your source files (both .s, .c and .cpp)
+##
+SRC :=                                  \
+main.cpp Logger.cpp tscpp/buffer.cpp
+
+##
+## List here additional static libraries with relative path
+##
+LIBS :=
+
+##
+## List here additional include directories (in the form -Iinclude_dir)
+##
+INCLUDE_DIRS :=
+
+##############################################################################
+## You should not need to modify anything below                             ##
+##############################################################################
+
+ifeq ("$(VERBOSE)","1")
+Q := 
+ECHO := @true
+else
+Q := @
+ECHO := @echo
+endif
+
+## Replaces both "foo.cpp"-->"foo.o" and "foo.c"-->"foo.o"
+OBJ := $(addsuffix .o, $(basename $(SRC)))
+
+## Includes the miosix base directory for C/C++
+## Always include CONFPATH first, as it overrides the config file location
+CXXFLAGS := $(CXXFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC)  \
+            -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC)      \
+            -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS)
+CFLAGS   := $(CFLAGS_BASE)   -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC)  \
+            -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC)      \
+            -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS)
+AFLAGS   := $(AFLAGS_BASE)
+LFLAGS   := $(LFLAGS_BASE)
+DFLAGS   := -MMD -MP
+
+LINK_LIBS := $(LIBS) -L$(KPATH) -Wl,--start-group -lmiosix -lstdc++ -lc \
+             -lm -lgcc -Wl,--end-group
+
+all: all-recursive main
+
+clean: clean-recursive clean-topdir
+
+program:
+	$(PROGRAM_CMDLINE)
+
+all-recursive:
+	$(foreach i,$(SUBDIRS),$(MAKE) -C $(i)                               \
+	  KPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(KPATH))       \
+	  CONFPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(CONFPATH)) \
+	  || exit 1;)
+
+clean-recursive:
+	$(foreach i,$(SUBDIRS),$(MAKE) -C $(i)                               \
+	  KPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(KPATH))       \
+	  CONFPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(CONFPATH)) \
+	  clean || exit 1;)
+
+clean-topdir:
+	-rm -f $(OBJ) main.elf main.hex main.bin main.map $(OBJ:.o=.d)
+
+main: main.elf
+	$(ECHO) "[CP  ] main.hex"
+	$(Q)$(CP) -O ihex   main.elf main.hex
+	$(ECHO) "[CP  ] main.bin"
+	$(Q)$(CP) -O binary main.elf main.bin
+	$(Q)$(SZ) main.elf
+
+main.elf: $(OBJ) all-recursive
+	$(ECHO) "[LD  ] main.elf"
+	$(Q)$(CXX) $(LFLAGS) -o main.elf $(OBJ) $(KPATH)/$(BOOT_FILE) $(LINK_LIBS)
+
+%.o: %.s
+	$(ECHO) "[AS  ] $<"
+	$(Q)$(AS)  $(AFLAGS) $< -o $@
+
+%.o : %.c
+	$(ECHO) "[CC  ] $<"
+	$(Q)$(CC)  $(DFLAGS) $(CFLAGS) $< -o $@
+
+%.o : %.cpp
+	$(ECHO) "[CXX ] $<"
+	$(Q)$(CXX) $(DFLAGS) $(CXXFLAGS) $< -o $@
+
+#pull in dependecy info for existing .o files
+-include $(OBJ:.o=.d)
diff --git a/miosix/_examples/datalogger/Readme.txt b/miosix/_examples/datalogger/Readme.txt
new file mode 100644
index 00000000..29c092e3
--- /dev/null
+++ b/miosix/_examples/datalogger/Readme.txt
@@ -0,0 +1,30 @@
+High performance logging for Miosix
+
+Some applications - possibly real-time - require to log data, and do so from
+multiple threads. While a simple library that opens a file and writes to is
+sufficient for simple applications, there are some problems:
+- the Miosix filesystem uses typically an SD card as storage, and SD and other
+  FLASH based storage devices may pause to do wear leveling. SD cards, even good
+  quality class 10 ones, may pause for up to 1 second.
+- the Miosix OS, contrary to Linux, does not do much buffering at the filesystem
+  layer. This is desirable, since when an OS runs on as little as a few tens of
+  KB of RAM, the last thing you want is an OS that uses an unquatifiable amount
+  of memory.
+
+This example code shows a high-performance logging class that
+- has a nonblocking log() member function, which can be called concurrently from
+  multiple threads, to log a user-defined class or struct.
+  Tscpp (https://github.com/fedetft/tscpp) is used to serialize data.
+  Being nonblocking, it can be called also in real-time threads of your codebase
+  with confidence.
+- buffers data to compensate for the delays of the storage medium
+
+To configure the logger for your application, to trade off buffer space vs write
+data rate, uoy can edit Logger.h
+
+    static const unsigned int filenameMaxRetry = 100; ///< Limit on new filename
+    static const unsigned int maxRecordSize    = 128; ///< Limit on logged data
+    static const unsigned int numRecords       = 128; ///< Size of record queues
+    static const unsigned int bufferSize       = 4096;///< Size of each buffer
+    static const unsigned int numBuffers       = 4;   ///< Number of buffers
+    static constexpr bool logStatsEnabled      = true;///< Log logger stats?
diff --git a/miosix/_examples/datalogger/logdecoder/Makefile b/miosix/_examples/datalogger/logdecoder/Makefile
new file mode 100644
index 00000000..88443dde
--- /dev/null
+++ b/miosix/_examples/datalogger/logdecoder/Makefile
@@ -0,0 +1,6 @@
+
+all:
+	g++ -std=c++11 -O2 -o logdecoder logdecoder.cpp ../tscpp/stream.cpp -I ..
+
+clean:
+	rm logdecoder
diff --git a/miosix/_examples/datalogger/logdecoder/logdecoder.cpp b/miosix/_examples/datalogger/logdecoder/logdecoder.cpp
new file mode 100644
index 00000000..412465bb
--- /dev/null
+++ b/miosix/_examples/datalogger/logdecoder/logdecoder.cpp
@@ -0,0 +1,59 @@
+/***************************************************************************
+ *   Copyright (C) 2018 by Terraneo Federico                               *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   As a special exception, if other files instantiate templates or use   *
+ *   macros or inline functions from this file, or you compile this file   *
+ *   and link it with other works to produce a work based on this file,    *
+ *   this file does not by itself cause the resulting work to be covered   *
+ *   by the GNU General Public License. However the source code for this   *
+ *   file must still be made available in accordance with the GNU General  *
+ *   Public License. This exception does not invalidate any other reasons  *
+ *   why a work based on this file might be covered by the GNU General     *
+ *   Public License.                                                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
+ ***************************************************************************/ 
+
+/*
+ * This is a stub program for the program that will decode the logged data.
+ * Fill in the TODO to make it work. 
+ */
+
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+#include <tscpp/stream.h>
+
+//TODO: add here include files of serialized classes
+#include "../LogStats.h"
+#include "../ExampleData.h"
+
+using namespace std;
+using namespace tscpp;
+
+int main(int argc, char *argv[])
+try {
+    TypePoolStream tp;
+    //TODO: Register the serialized classes
+    tp.registerType<LogStats>([](LogStats& t){ t.print(cout); cout<<'\n'; });
+    tp.registerType<ExampleData>([](ExampleData& t){ t.print(cout); cout<<'\n'; });
+
+    if(argc!=2) return 1;
+    ifstream in(argv[1]);
+    in.exceptions(ios::eofbit);
+    UnknownInputArchive ia(in,tp);
+    for(;;) ia.unserialize();
+} catch(exception&) {
+    return 0;
+}
diff --git a/miosix/_examples/datalogger/main.cpp b/miosix/_examples/datalogger/main.cpp
new file mode 100644
index 00000000..b44d9c7c
--- /dev/null
+++ b/miosix/_examples/datalogger/main.cpp
@@ -0,0 +1,72 @@
+
+#include <cstdio>
+#include <miosix.h>
+#include "Logger.h"
+#include "ExampleData.h"
+
+using namespace std;
+using namespace miosix;
+
+volatile bool stop=false;
+
+void loggerDemo(void*)
+{
+    /*
+     * Logger is configured as:
+     * maxRecordSize    = 128
+     * numRecords       = 128
+     * bufferSize       = 4096
+     * numBuffers       = 4
+     * 
+     * Serialized ExampleData is 30 bytes
+     * There are 128 entries in the record queue, and (4-1)=3 4096 buffers for
+     * buffering (the fourth buffer is the one being written).
+     * Thus, the buffering system can hold 4096*3/30+128=537 ExampleData before
+     * filling. Considering the rule of thumb that a high quality SD card may
+     * block for up to 1s, the maximum data rate is 537Hz.
+     * 
+     * An estimate of the memory occupied by the logger is:
+     * buffers       4*(4096+4)+128*(128+4)=33296
+     * thread stacks 1536+1536+2048=5120
+     * so a total of 38KB. The actual memory occupied will be a bit larger
+     * due to unaccounted variables and overheads.
+     * 
+     * Note: although this demo is simple, the logger allows to:
+     * - log data from multiple threads while being nonblocking
+     * - log different classes/structs in any order, provided their serialized
+     *   size is less than maxRecordSize and that they meet the requirements
+     *   to be serialized with tscpp (github.com/fedetft/tscpp)
+     */
+    auto& logger=Logger::instance();
+    logger.start();
+    
+    int a=0,b=0;
+    auto period=2; //2ms, 500Hz
+    for(auto t=getTick();;)
+    {
+        if(stop) break;
+        auto tNew=t+period;
+        Thread::sleepUntil(tNew);
+        t=getTick();
+        if(t>tNew) b++; //Deadline miss
+        ExampleData ed(a++,b,t);
+        logger.log(ed);
+    }
+    
+    logger.stop();
+    
+    iprintf("Lost %d samples, missed %d deadlines\n",
+            logger.getStats().statDroppedSamples,b);
+}
+
+int main()
+{
+    puts("Type enter to start logger demo");
+    getchar();
+    auto t=Thread::create(loggerDemo,2048,PRIORITY_MAX-1,nullptr,Thread::JOINABLE);
+    puts("Type enter to stop logger demo");
+    getchar();
+    stop=true;
+    t->join();
+    puts("Bye");
+}
diff --git a/miosix/_examples/datalogger/tscpp b/miosix/_examples/datalogger/tscpp
new file mode 160000
index 00000000..4a5f5407
--- /dev/null
+++ b/miosix/_examples/datalogger/tscpp
@@ -0,0 +1 @@
+Subproject commit 4a5f54076ea924dacb5414c0f6183a669efc1eff
-- 
GitLab