С++ для микроконтроллеров - «Декоратор»

Есть два способа создания дизайна программы. Один из них, это сделать его настолько простым, что в нем, очевидно, не будет недостатков. Другой способ — сделать его настолько запутанным, что в нем не будет очевидных недостатков. — C.A. R. Hoare

«Приемы объектно-ориентированного проектирования. Паттерны проектирования» — эпохальная книга 1994 года об инженерии программного обеспечения, описывающая решения некоторых частых проблем в проектировании ПО. Коллектив авторов Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес также известен как «Банда четырёх», Gang of Four, GoF.

GoF даёт следующее классическое определение: декоратор (англ. Decorator) — паттерн, структурирующий объекты. Динамически добавляет объекту новые обязанности. Является гибкой альтернативой порождению подклассов с целью расширения функциональности.

Объект, который предполагается использовать (в нашем случае слой для лампочек), выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность (анимации), которая будет выполняться до, после или даже вместо основной функциональности объекта.

screenshot

Поскольку анимации и их последовательность заранее известны на этапе компиляции, декоратор легко реализуется на С++ шаблонах. Такая реализация позволяет не использовать виртуальные функции и будет занимать больше памяти ROM (code bloat), но работать должна шустрее - хотя для мигающих лампочек это не критично. Ещё использование шаблонов даёт удобную возможность делать простые проверки static_assert() на этапе компиляции. Нечто подобное можно конечно же соорудить и на Си с помощью макросов, но в С++ static_assert() это уже встроенный и весьма полезный механизм так что не приходится заново изобретать велосипед.

Все классы для создания анимаций реализуют интерфейс Декоратора - методы void draw(RgbStripLayer& layer); и void set_color(const auto& co). Работая в цепочке каждый последовательно добавляет (декорирует) к слою RgbStripLayer новые свойства. Эти классы взаимозаменяемы, их последовательность определяет конкретную анимацию. В прилагаемых исходниках добавлено больше анимаций плюс пример использования стандартной библиотекой шаблонов (STL) в С++ - алгоритм std::rotate для массивов.

elf | MSP430.js | исходники

app.cpp

#include "animation.h"

namespace app {

    using namespace animation;

    constexpr auto SZ = RgbStripLayer::STRIP_SZ;

    int run() {

        RgbStripLayer l1;

        for (;;) {
            EachColor< DynFill< 0, SZ - 1, Flush< Sleep< 55555, Nop >>>>().draw(l1);
            EachColor< FillCW< Sleep< 4444, Flush<> >>>().draw(l1);
            EachColor< FillCCW< Sleep< 4444, Flush<> >>>().draw(l1);
        }

        return 0;
    }
}

animation.h

#include "rgb.h"

namespace animation {

    using namespace rgb;

    struct Nop : NonCopyable {
        void draw(RgbStripLayer& layer) {}
    };

    template<class A = Nop>
    class Flush : NonCopyable {
        A m_stream;
    public:
        void draw(RgbStripLayer& layer) {
            layer.flush();
            m_stream.draw(layer);
        }
    };

    template<const std::int32_t cycles, class A>
    class Sleep : NonCopyable {
        A m_stream;
    public:
        void draw(RgbStripLayer& layer) {
            static_assert(cycles > 0);
            __delay_cycles(cycles);
            m_stream.draw(layer);
        }
    };

    template<class A, const RgbStripLayer::RGB& c = RgbStripLayer::RED>
    class FillCW : NonCopyable /* clockwise */ {
        A m_stream;
        RgbStripLayer::RGB m_color = c;
    public:
        void draw(RgbStripLayer& layer) {
            for (auto& m : layer.mem) {
                m = m_color;
                m_stream.draw(layer);
            }
        }

        void set_color(const auto& co) {
            m_color = co;
        }
    };

    template<class A, const RgbStripLayer::RGB& c = RgbStripLayer::RED>
    class FillCCW : NonCopyable /* counterclockwise */ {
        A m_stream;
        RgbStripLayer::RGB m_color = c;
    public:
        void draw(RgbStripLayer& layer) {
            // while a pointer is a form of iterator,
            // not all iterators have the same functionality of pointer
            for (auto it = layer.mem.rbegin(); it != layer.mem.crend(); it++) {
                *it = m_color;
                m_stream.draw(layer);
            }
        }

        void set_color(const auto& co) {
            m_color = co;
        }
    };

    template<class A>
    class EachColor : NonCopyable {
        A m_stream;
        static constexpr auto colors = {
            RgbStripLayer::RED,    RgbStripLayer::GREEN,
            RgbStripLayer::BLUE,   RgbStripLayer::YELLOW,
            RgbStripLayer::VIOLET, RgbStripLayer::CYAN,
            RgbStripLayer::WHITE,  
        };

    public:
        void draw(RgbStripLayer& layer) {
            for (const auto& c : colors) {
                m_stream.set_color(c);
                m_stream.draw(layer);
            }
        }
    };

    template<const std::int8_t f, const std::int8_t l, class A>
    class DynFill : NonCopyable {
        A m_stream;
        RgbStripLayer::RGB m_color = RgbStripLayer::RED;
    public:
        void draw(RgbStripLayer& layer) {
            static_assert(f >= 0);
            static_assert(l > f);
            static_assert(l < layer.mem.size());
            for (auto i = f; i <= l; i++) {
                layer.mem[i] = m_color;
            }
            m_stream.draw(layer);
        }

        void set_color(const auto& co) {
            m_color = co;
        }
    };
}

Применение паттернов и ООП в микроконтроллерах имеет свою специфику, накладываемую в основном моделью управления памятью. Неважно на чём писать — Си или С++ для встраиваемых систем предпочтительней использовать память в стеке т.к. она проверяется компилятором. В свою очередь динамическое выделение памяти на куче не проверяется компилятором и как следствие её наличие в нужный момент времени выполнения программы не гарантируется. ООП же больше любит динамическое выделение памяти — операторы new, malloc, умные указатели. Не стоит этого всего использовать в микроконтроллерах без ясного понимания возможных последствий.

Далее игра «Змейка» где работа с памятью будет описана более подробно.

links

social