Автоматный практикум — 1. Пример «Дисплей», разработка ОА и УА

    Тесты в предыдущей статье убедительно показали высокую эффективность «автоматной» реализации примера «Дисплей» по сравнению с условно названной «неавтоматной» версией. Вкратце итог: обе реализации автоматные, но разница в эффективности многократна и глубинная причина видится в том, что вариант А1 («автоматный») изначально проектировался как автомат, а вариант А2 («неавтоматный») нет. Не столько автоматная реализация, сколько автоматное проектирование является основой высокой эффективности. Для простых алгоритмов автоматные реализации получаются сами собой. Есть смысл говорить о том, что автоматное программирование, это не столько реализация программы в виде конечного автомата, сколько автоматное проектирование, фундаментом которого является конструктивная декомпозиция. Я несколько раз касался темы автоматного проектирования и конструктивной декомпозиции, но чтобы раскрыть эту тему нужны практические примеры. В этой и следующих нескольких статьях я проведу практикум, покажу процесс автоматного проектирования, пытаясь по возможности приводить ход рассуждений присущих автоматному проектированию.

    Оглавление

    Предыдущая статья

    В статье 1 обзорно рассматривалось внутреннее строение примера «Дисплей» (лабораторной версии). Сегодняшняя статья описывает процесс автоматной разработки рабочего варианта, делая акцент на том, как производился выбор тех или иных технических решений. Я не стану повторять содержимое упомянутой статьи, ограничившись кратко постановкой задачи.

    Постановка задачи


    Имеется ч/б графический дисплей. Его видеопамять имеет стандартную побайтную организацию, в которой каждый бит представляет один пиксель. Вывод данных идёт потоковым побайтным заполнением видеопамяти дисплея через параллельный интерфейс или SPI по протоколу типа:

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

    Вывод текста необходимо осуществлять разными, не моноширинными шрифтами.


    а)


    б)


    в)

    Рисунок 1. Требования к модулю вывода на дисплей

    Все символы в шрифте одной высоты, но шрифт может быть поменян «на лету», в процессе вывода одной и той же строки. Аналогично могут быть поменяны атрибуты – жирный, курсив, подчёркивание. Для управления параметрами используются esc-последовательности, к которым относится управляющий символ '\n', перевод строки, т.е. текст одной строки может быть выведен на несколько строк на дисплее. Например текст:

    "Text 1 \033[7m Text 2  \033[27m  \033[1m Text 3 \033[21m \n Text 42"

    будет отображаться так, как это показано на иллюстрации рис.1 (б)

    Текст выводится в область, ограниченную прямоугольником рис.1 (в), и может иметь смещение x_shift, y_shift. Координаты области вывода задаются в пикселях, координаты могут быть отрицательными, что означает выход за пределы области вывода. Текст, выходящий за границы области вывода, отсекается, рис.1 (в).

    Пользовательский интерфейс — функция с прототипом

    void Out_text(int x0, int y0, int x1, int y1, int x_shift, int y_shift, char * Text);

    Начало разработки, получение требований к автомату вывода текстовых блоков


    Составление автомата начинается с декомпозиции на операционный и управляющий и может повторяться рекурсивно — на каждом этапе ОА может быть разбит на пару ОАнижнего_уровня и его УА.


    Рисунок 2. Рекурсивная декомпозиция автомата на операционный и управляющий

    Декомпозиция должна быть естественной для решаемой задачи, а для этого нужно попытаться изобразить задачу наглядно, то есть взглянуть на задачу предметно. Часто грамотное изображение процесса, который требуется осуществить, равносильно получению решения задачи в общем виде, это чуть ли не половина успеха при выборе ОА.

    Поясню о чём я. Как следует из условия задачи, исходная последовательность символов в общем случае выглядит как: Текст1 упр1 Текст2 упр2 Текст3 упр3 Текст4 упр4 Текст5 \0
    где упрN управляющие esc-последовательности, символы перевода строки, табуляции.

    Не требуется никаких дополнительных усилий чтобы увидеть в этой схеме, что есть монолитные текстовые блоки, разделённые управляющими последовательностями. Следовательно, общая задача делится на этап разбиения исходной последовательности на блоки и вывод этих блоков.


    Рисунок 3. Первоначальное разбиение

    Это самый простой случай, такое разбиение исходной задачи напрашивалось само собой, в других случаях это бывает не так очевидно, поэтому в цикле «Практикум» я буду обращать внимание на предметный взгляд на исходную задачу, давая советы на примерах.

    Важное место занимает коммуникация между частями, если её можно сделать такой, что переделка одного из уровней не будет сказываться на других уровнях, это будет огромный плюс. Каждый текстовый блок характеризуется координатами текстового блока x_block, y_block (относительно окна вывода). Поскольку шрифт и атрибуты (инверсный или нет, мигающий, жирный, курсив, подчёркивание и так далее) могут оставаться неизменными, не стоит снабжать каждый текстовый блок лишней информацией, но сделать отдельный канал (метод автомата вывода текстовых блоков) управления атрибутами. Такая схема разбиения и такая коммуникация между частями позволяет реализовать ОА автомата вывода текстовых блоков наиболее удобным образом, не касаясь остальных частей, что и было продемонстрировано: и для схемы ОА «А1» и для схемы ОА «А2» общая схема разбиения остаётся одна и та же.

    Таким образом, требования к автомату вывода текстовых блоков следующие.

    Автомат вывода текстовых блоков


    Требования к автомату вывода текстовых блоков можно изобразить графически.

    image

    Рисунок 4. Требования к автомату вывода текстовых блоков

    Каждый из текстовых блоков уже не содержит внутри себя управляющих последовательностей и отображается с неизменными атрибутами с позиции x_block, y_block и до конца блока или до конца экрана. Значения x_shift, y_shift влияют на расположение всей группы блоков и при отображении отдельного блока уже cкомпенсированы в значении x_block, y_block. Верхний правый угол текстового блока отсчитывается относительно верхнего правого угла окна вывода, высота равняется высоте шрифта, а ширина равна ширине текста или окна вывода, если текст не помещается целиком.

    Автоматное проектирование как процесс


    Показанная на рис. 2 рекурсивность декомпозиции не продолжается бесконечно, в нашем случае это два уровня разбиения. Для многих задач будет та же пара уровней, а то и один. Большая многоступенчатость характерна не для модулей, а для систем.

    После этого происходит детальная проработка неделимой пары ОА+УА, пары нижнего уровня и её программная реализация, которая происходят снизу вверх, от ОА к УА. Это продиктовано тем, что обычно самый нижний уровень непосредственно завязан на исполнительные механизмы, что ставит нас в определённые рамки и ограничивает возможность «манёвра» и одновременно определяет степень эффективности, в то время как более высокие уровни являются в этом отношении более гибкими, и рамки для них вытекают из реализации нижележащих автоматов. Упомянутые «рамки», бывает, нащупываются уже в процессе разработки, а во время первоначального анализа и разбиения они скрыты «туманом войны». Это иллюстрирует важность коммуникаций между частями, о которой я говорил выше, которые играют роль «водонепроницаемых переборок», позволяя ограничиваться изменением только отдельно взятых модулей, а не всего стека автоматов.

    Процесс автоматной разработки неделимой пары ОА+УА итерационный:

    image

    Рисунок 5. Итерационный процесс при автоматной разработке

    Итерационным способом разрабатываются большие проекты, над которыми трудятся коллективы, и эта же форма организации труда годится для индивидуального применения. В этом случае речь идёт не о формальностях. Индивидуальный итерационный процесс проектирования не подразумевает запланированных встреч участников, которым нужно обменяться результатами и отчитаться, разработчик-то один. Итерация это просто ход следования мыслей. Индивидуальный итерационный процесс проектирования, это такой процесс проектирования ОА и УА, при котором каждую итерацию «допиливается» ОА и «подтягивается» УА. Как неоднократно упоминалось, эффективное проектирование это в первую очередь эффективный ОА, поэтому разработка начинается с него.

    Выбор модели ОА


    Проработка ОА начинается с самого нижнего уровня. Поскольку вывод данных в дисплей идёт потоковым побайтным заполнением видеопамяти через канал связи, то предположительно следует воспользоваться схемой с промежуточным строчным буфером, в который помещается «отпечаток» каждого символа (глиф, glyth), а после вывода всего текстового блока содержимое строчного буфера скидывается в дисплей потоками байтов, горизонтальными линиями во всю ширину текста и один пиксель высотой.

    image

    Рисунок 6. Длинные линии потокового вывода из строчного буфера в видеопамять.

    Общая схема вывода выглядит как:

    image

    Рисунок 7. Общая модель вывода

    В реализации А2 (напомню, что общая модель вывода та же самая, рис. 3 ) операционный автомат вывода в строчный буфер это очень универсальный, но в силу этого низкоэффективный ОА произвольного пиксельного переноса. Циклопотребление такого варианта О(W*H), т.е. пропорционально площади символа.

    image

    Рисунок 8. Операционный автомат произвольного пиксельного переноса.

    Но как получить высокоэффективный ОА? Естественное решение — переносить биты сразу группами. Лучше всего было бы переносить символы операцией, разом копирующей весь прямоугольный массив отпечатка из знакогенератора в строчный буфер, циклопотребление такого варианта пропорционально О(1). Однако, таких команд нет. Копировать символ в строчный буфер по строкам, это наиболее эффективный из доступных способов.

    image

    Рисунок 9. Оптимальная схема переноса, лежащая в основе ОА для А1

    Построчный вывод символов из знакогенератора это техническое решение №1, оно обеспечивает фундамент будущей эффективности и уже можно дать первые оценки: циклопотребление такого варианта будет пропорционально количеству линий в символе O(H), что, кстати, показано в предыдущей статье, рис. 14.

    Следует учесть, что ширина символов в пикселях не пропорциональна ширине байта, и сам текстовый блок может начинаться не с границы байта. В таком случае невозможно скопировать содержимое знакогенератора байт в байт, требуется предварительный сдвиг каждой линии символа.

    image

    Рисунок 10. Пояснение необходимости сдвига глифа перед выводом в строчный буфер

    Из иллюстрации рис. 10 видно, что в каждый байт строчного буфера может попасть несколько символов. Для того, чтобы несколько символов попадая в один байт не затирали друг друга, данные сдвигового регистра не копируются, а накладываются по или. Однако эта операция требует предварительной очистки буфера, что выливается в накладные расходы, которые в общем случае будут пропорциональны размеру строчного буфера в байтах, а его размер пропорционален количеству линий в шрифте. Рис. 11 подтверждает эту зависимость

    image

    Рисунок 11. Зависимость Общих накладных расходов от количества линий в шрифте, для варианта с чисткой строчного буфера

    Подводя промежуточный итог, рабочая схема следующая:

    • данные из знакогенератора копируются в регистр сдвига – одна линия символа за итерацию.
    • там они сдвигаются на Current_shift пикселей
    • уже сдвинутое содержимое регистра накладываются по или на определённый участок строчного буфера, который адресуется указателем Current_byte.
    • строчный буфер должен быть заранее очищен.
    • это повторяется для всех линий символа, после чего и Current_shift и Current_byte увеличиваются по формулам.


    Формула 1

    
       Current_byte = (Current_byte + (Current_shift + Width)) >> 3;                   
       Current_shift = (Current_shift + Width) & 0x7; 

    image

    Рисунок 12. Базовая модель ОА вывода текстового блока

    Из формулы (1) в частности следует, что 7 позиций для переменной Current_shift (величина сдвига) это максимальное значение, любой сдвиг на большую величину ограничивается по модулю 8. Например, 12 битов превращаются в сдвиг на 4 пикселя и выгрузку сдвигового регистра на 1 байт правее.

    image

    Рисунок 13. Ограничение сдвига по модулю 7

    Это была подготовительная итерация, выбор ключевых, стратегических решений. В этом варианте решение которое является рабочим, само напрашивалось, но бывают случаи, когда предметное изображение условий задачи даже не намекает, каким операционным автоматом их обрабатывать. В этом случае условия задачи можно подвергнуть математической обработке, и решать не исходную задачу, а математически тождественную, но более удобную (в дальнейших статьях я поясню это на примерах). Для серьёзных задач, возможно придётся даже рассмотреть ряд решений и произвести технические прикидки быстродействия и прочих ресурсозатрат, для чего может быть даже потребуется пройти дальше вдоль по описанной инструкции и выполнить одну–две последующие итерации. Во всяком случае, такой подход даёт возможность выбрать оптимальное решение и за приемлемое время.

    На основе выбранного стратегического решения разрабатывается ОА. В ОА, напомню, идёт всё, что связано с непосредственным переносом и трансформацией данных, а в УА то, что будет нужно для работы ОА.

    Итерация 1. Исходный ОА


    Операционный автомат это полуфабрикат, он оптимально выполняет требуемую операцию с любыми допустимыми параметрами. В таком случае, УА это параметризация и запуск ОА. Такая структура даёт высокую гибкость и означает в частности, что сам ОА не меняет содержимого Current_shift и Current_byte. Он получает готовый набор параметров для выполнения всех перечисленных выше операций. Чтобы узнать, какие ещё потребуются параметры, рассмотрим как именно будет работать ОА. Выше я уже писал о важности наглядного изображения того явления, которое моделируется. Следует добавить, что важно изображать не только идеальный случай, но и «не удобные» варианты, которые связаны с граничными условиями. Это позволит избежать неверных решений, с последующей переделкой, возможно, всего ранее наработанного. Я акцентирую на этом внимание, потому что это психологически сложный момент — изображать моделируемое явление с самой трудной стороны, как бы усложняя себе задачу. Однако, сложность решаемой задачи вещь объективная, и не зависит от того закрываете вы глаза на неудобные моменты или нет, и единственное на что вы можете повлиять, это сложность решения данной задачи, и она будет минимально возможной, если искать решение изначально понимая с какими сложными моментами придётся столкнуться. Такой подход смещает ресурсозатраты разработки программы с этапа отладки (когда имеешь дело с кучей реализованных модулей) на этап проектирования (когда имеешь дело с чистым холстом).

    Все символы разной ширины, поэтому нужен параметр – количество загружаемых байтов для каждой строки символа:

    image

    Рисунок 14. Параметр bytes_For_load

    После загрузки каждую линию символа нужно сдвинуть. Надо отметить, что сдвиг происходит для разных микроконтроллеров по-разному. ARM-ы имеют эффективный механизм побитового сдвига на ширину до 32х битов за один цикл, и такой сдвиг можно просто добавить в качестве опции к другим командам, например арифметическим. В случае msp430 самая лучшая команда сдвига: 16 бит на 1 позицию. Однако, общая схема от этого не претерпевает изменений, только лишь замечу, для процессоров со сдвигом на 1 позицию (msp430) можно использовать более эффективную схему сдвига через ассемблерные вставки, команда «сдвиг через флаг переноса». Как крайний вариант (это относится и не только к Дисплею а вообще к разным ОА) можно писать ОА на макроассемблере, который развернёт сравнительно несложный код в массивы ассемблерных инструкций. Возможно, ассемблерная реализация не понадобится, я упомянул это чтобы указать, что есть дополнительный «козырь», пути для улучшения модуля «Дисплей», если это понадобится. При этом не потребуется переделка УА, потому что это часть концепции семейств ОА и УА, описанной в предыдущей статье.

    image

    Рисунок 15. Семейства ОА и УА

    Со временем семейства операционных автоматов пополнятся множеством автоматов, что позволит применять эффективный полуавтоматизированный поиск среди них, что станет удобным инструментом разработки. Но, впрочем, для нашего примера, для «ручного» автоматного проектирования не нужны семейства ОА и УА, поэтому сосредоточусь на одном, выбранном варианте.

    Сдвигать нужно все байты входящие в линию. Направление сдвига логично брать таким, чтобы количество позиций для сдвига было минимальным, однако, практический опыт говорит, что единообразие при осуществлении операций имеет немалую ценность, поэтому будет рассматриваться вариант сдвига вправо, а уже его можно реализовать так, чтобы он фактически выполнялся в более короткую сторону.

    При некоторой величине сдвига количество байтов которые необходимо сдвигать может увеличиваться ещё на байт по сравнению с тем количеством байтов, которые нужно загружать.

    image

    Рисунок 16. Параметр bytes_For_shift

    После сдвига содержимое сдвигового регистра скидывается в строчный буфер. На первый взгляд, количество байтов для вывода должно совпадать с количеством байт для сдвига, однако если представить, что крайний левый пиксель этого символа выступает слева за край экрана, то количество байтов для вывода не будет совпадать с количеством байтов для сдвига.

    image

    Рисунок 17. Параметр bytes_For_out

    Общая схема ОА, таким образом, будет приблизительно следующей:

    
       class tShift_buffer
       {
         public:
         void  Load (u1x *Source, int Bytes_amount);
         void  Shift(int Bits_amount, int Bytes_amount);
         void  Out  (u1x *Destination, int Bytes_amount);   
       };
    
       ////////////////////////////////////////////////////////////////////////////
       // ОА
       void Out_symbol()
       {
    
         // ВЕРТИКАЛЬНЫЙ ЦИКЛ
         for(int y = 0; y < Height; y++)
         {
    
           u1x * Glyph_line = Current_font->Image_for(* Text) + y * bytes_Width;
           Shift_buffer.In  (Glyph_line, bytes_For_load);
           Shift_buffer.Shift (Current_shift, bytes_For_shift);
           Shift_buffer.Out (&String_buffer [y][Current_byte], bytes_For_out);
    
         }// for(int y = y0; y < Height; y++)
    
       }// void Out_symbol()

    Очевидно, что эффективность этого ОА напрямую зависит от того, как реализованы операции со сдвиговым регистром. Об этом далее, пока же представляется, что нет ничего сложного в том, чтобы реализовать их эффективно.

    Теперь можно приступать к составлению модели управляющего автомата. Дальнейшая детализация ОА сейчас может завести в тупик. Это связано с тем, что выбор чрезвычайно эффективного ОА может привести к непомерным накладным расходам в УА и, соответственно, к неэффективности всей схемы. Кроме того, разрабатывая готовую схему ОА и не имея на руках даже модели УА, придётся держать эту модель в уме, что может привести просто к противоречивым требованиям, которые нельзя будет воплотить на практике. И, наконец, самое главное: чтобы понять насколько годная модель ОА с предыдущего листинга, нужно взглянуть на весь процесс с высоты, так сказать, птичьего полёта.

    Итерация 1. Исходный УА


    Итак, требуется исходная реализация УА, которая позволит детализировать ОА, прикидывая по ходу детализации, во что это выльется с точки зрения УА. Разработка УА неотделима от разработки ОА в силу того, что первый управляет параметрами второго. Разрабатывая УА, мы не прекращаем обсуждать ОА и его параметры, а наоборот, добавляем вычисление этих параметров и их начальных значений.

    image

    Рисунок 18. Тесная взаимосвязь ОА и УА

    Соответственно, будет неплохо вести учёт параметров, для того чтобы никакие из них не оказались непроинициализированными или без управления со стороны УА, а так же чтобы их количество не росло сверх меры.

    Таблица 1. Параметры для работы ОА

    переменная параметр

    формула

    width

    загрузка из знакогенератора

    current_byte, current_shift

    current_byte = (current_byte + (current_shift + width)) >> 3;

    current_shift = (current_shift + width) & 0x7;

    current_byte0 = 0;

    current_ shift0 = x_block & 0x7;

    bytes_for_load, bytes_for_shift, bytes_for_out

    определены далее


    Такая таблица строится по мере составления автомата на черновике, как вспомогательная. Однако, возвращаясь к составлению некоторого «обобщённого, универсального автомата-шаблона», описанного в главе "Взгляд в будущее" предыдущей статьи, такая таблица может использоваться для описания УА и ОА в виде взаимных export/public объявлений с возможностью соединения тех из них, у которых совпадают наборы параметров, что будет использовано в системах автоматизированного проектирования.

    Пробная версия УА.

    Если бы требовалось выводить бесконечный текст, на бесконечно длинный строчный буфер, начиная с позиции 0, то УА был бы следующим

    
       uchar * Text;
    
       uchar String_buffer[y_string][x_max /* 0.Infinity */ ];
    
       ////////////////////////////////////////////////////////////////////////////
       // УА
       void Out_text_block(uchar * Text)
       {
         
         Current_shift = Current_byte = 0; 
     
         while(1)
         {
    
           Witdh = Current_font->Width_for(* Text);
    
           bytes_For_load  = (Width + 7) >> 3;
           bytes_For_shift = (Width + Current_shift + 7) >> 3;
           bytes_For_out   = bytes_For_shift;
    
           Out_symbol();
    
           Current_shift = (Current_shift + Width); 
           Current_byte  = (Current_byte + Current_shift) >> 3;
           Current_shift &= 0x7;
    
           Text++; 
    
         }
    
       }// void Out_text_block(uchar * Text)
    

    Однако мы имеем дело с реальным текстом конечной длины, более того, имеющим начальный сдвиг относительно окна вывода, и этот текст может не поместиться в окно вывода как сверху и снизу, так и слева и справа.

    Случай, когда текст выступает за край окна вывода сверху или снизу.

    Учёт ограничений снизу и сверху проще, чем справа и слева, поэтому начну с него. Может оказаться, что высота символа шире, чем полоса вывода, или начальный сдвиг текста может приводить к тому, что часть текста выступает сверху, или снизу, или сразу сверху и снизу.

    image

    Рисунок 19. Варианты негабаритного текста по вертикали

    С точки зрения ОА такое поведение реализуется просто. Достаточно модифицировать показанный на листинге «ВЕРТИКАЛЬНЫЙ ЦИКЛ», который перебирает линии от 0 до Height, чтобы он перебирал линии от Start_line до End_line, где Start_line — номер верхней строки глифа которая попадает в буфер, End_line – номер первой строки (снизу) которая уже не попадает в диапазон перебираемых значений. То есть для символа 6x9 который полностью попадает в буфер End_line = 9.

    Сначала определяется Start_line

    
    if(y_block < y0)
    {
    Start_line = y0 - y_block;
    }
    else
    {
    Start_line = 0;
    }
    
    if( Start_line >= Height)
      // Текст не попал в область вывода, не выводим
    

    End_line ищется исходя из значения Start_line.

    
    if( (y1 – y_block) >= Height)
    {
      End_line = Height;
    }
    else
    {
      End_line = y1 – y_block;
    }

    Случай, когда текст выступает за край окна вывода справа или слева.

    По горизонтали дело обстоит сложнее в силу байтового группирования столбцов. Координата начала блока — x_block. Величина x_shift скомпенсирована, о ней можно не вспоминать.

    image

    Рисунок 20. Пояснение положения начала текста относительно окна вывода

    Если x_block > x0, то есть текст смещён вправо от левого края окна вывода, этот случай не требует особой обработки, обрабатывается так, как будто этого отступа нет – вычисляются значения описанных выше параметров и вперёд. Смещение влево x_block < x0 обрабатывается по-другому, потому что часть текста не попадает на экран. Иллюстрация рис. 20, демонстрирует оба случая смещения. Работа только с положительными значениями проще для психики, поэтому Left_shift рассчитывается таким образом, что это положительная величина.

    Лучше всего отражает картину «с высоты птичьего полёта» рис. 21.

    image

    Рисунок 21. Классификация символов по типам.

    Значение Left_shift может оказаться таким, что первый символ или несколько первых символов не помещаются на экран (тип 1). Такие символы нужно пропустить.

    Обязательно будет первый символ, попавший в строку целиком или частично (тип 2). Его стоит отличать от последующих символов (тип 3), потому что для символов тип 2 требуется (помимо возможного отсечения слева) начальная инициализация переменных, а для символов тип 3 результат каждой итерации это исходные данные для следующей. Вводить отдельный тип для первого символа который попал на экран целиком и для символа который частично вышел за границу не имеет смысла, потому что первый вариант — частный случай второго. Следует избегать плодить сущности потому что это усложняет общую картину, а это не способствует ни эффективности, ни надёжности.

    Кроме этого, любой из символов тип 2 и тип 3 может оказаться последним в строке и даже выходить за край экрана справа.

    image

    Рисунок 22. Выступающие справа символы

    Поскольку символы не кратны байтам, то символы типов 2, 2’, 3 и 3’ могут содержать пересекающиеся байты. Это байты в которых часть содержимого старая, из видеопамяти, а часть относится к выводимому тексту. Для вывода такого байта его содержимое загружается из видеопамяти, маскируется и накладывается на крайние байты строчного буфера. Хочу обратить внимание, что пересекающиеся байты могут появиться даже если символ помещается целиком. Пересекающиеся байты это способ согласования содержимого строчного буфера (данные в котором в общем случае не выровнены на границу байтов) и видеопамяти.

    Таблица 1. Параметры для работы ОА. Продолжение

    переменная параметр

    формула

    left_shift

    if(x_block>= x0)
    left_shift = 0;
    else{
    left_shift = x0 – x_block;
    x_block = x0;
    }



    Приняв описанную концепцию типов символов, можно определить модель работы управляющего автомата, описываемую диаграммой состояний на рис. 23

    image

    Рисунок 23. Модель работы управляющего автомата

    Как я уже отмечал ранее, диаграмма состояний помимо описания автомата, одновременно является схемой описания алгоритма, альтернатива граф-схемам. По ней элементарно можно составить программную реализацию. Цветами выделен текст программы, соответствующий состояниям.

    Листинг 1. Каркас УА.
    image



    Параметр Line_width, показывает достигнут конца окна вывода или нет

    Таблица 1. Параметры для работы ОА. Продолжение

    переменная параметр

    формула

    line_width

    line_width= line_widthwidth;
    line_width0 = x1 – x_block;



    Делаем коммит

    Это ещё не рабочее приложение, вместо ОА используются заглушки, которые отображают в отладочном Memo: символ, ширина, тип по нашей классификации(2,2а,3,3а). Для символов тип 2/2a дополнительно отображается величина сдвига Left_shift.

    Ситуация в общем понятная. Следующая задача — получить в первом приближении рабочий модуль ОА.

    Итерация 2. ОА


    Имеются символы трёх типов 1, 2 и 3 и двух подтипов 2’ и 3’. Логично использовать для их вывода специализированные на каждый тип ОА.

    Символы полностью не попадающие в окно вывода.

    Те символы, которые из-за сдвига влево полностью не попали в область отображения, на рисунке показаны цифрой 1, их обрабатывать проще всего, достаточно «прокрутить» все символы, не попадающие в область отображения.

    
      // Контроль конца строки, 
      while(Text < Text_end)
      {
    
        Width = Current_font->Width_for(*Text);
          
        // Прокручиваем символы пока не попадём в область отображения
        if(Left_shift >= Width)
        {
          Left_shift  -= Width;
          Text++;
        }
        else
    
          goto Type_2;
    
      }// while(Text < Text_end)
    

    Символы помещающиеся в окно вывода целиком.

    Тип 3 это основной тип символов, который уже был описан ранее. Для его обработки нужны параметры Current_byte (известен от предыдущего символа), Current_shift (известен от предыдущего символа), bytes_For_load, bytes_For_shift, bytes_For_out.

    image

    Рисунок 24. Параметры символов тип 3

    Поскольку фактически вычисления происходят в алгебраическом кольце, не избежать краевых эффектов.

    image

    Рисунок 25. Пояснение к вычислению параметров для тип 3

    Пользуясь иллюстрацией рис. 25 несложно составить формулы вычисления параметров.

           bytes_For_load = (Width + 7) >> 3;
           bytes_For_shift = (Width + Current_shift + 7) >> 3;
           bytes_For_out  = bytes_For_shift;
    
           Current_shift_next = (Current_shift + Width) & 0x7;
           Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 ); 

    Символы, выступающие только справа.

    image

    а)

    image

    б)
    Рисунок 26.Параметры символов тип 3а

    Величина $inline$\text{Line_width}_\text{Limit}$inline$ это остаток строки $inline$\text{Line_width}$inline$ на момент когда происходит событие Достигнута граница окна вывода, то есть символ тип 3а не помещается на экран целиком $inline$(\text{Line_width} < \text{Width}_\text{Символа})$inline$. «Выпадает» правая часть символа.

    Таким образом, случай тип 3а сводится к случаю тип 3, с заменой ширины символа $\text{Width}_\text{Символа}$ на значение $inline$\text{Line_width}_\text{Limit}$inline$. Однако, в отличии от «истинных» тип 3, глиф знакогенератора всё же содержит пиксели правее чем $inline$\text{Line_width}$inline$ пикселей, поэтому на правую часть правого байта символа накладывается маска, которая определяется координатой окна вывода x1. И как и в прошлом случае, имеем дело с алгебраическим кольцом. Поскольку этот случай сводится к предыдущему, иллюстрация ниже, в общем, не нужна, но с её помощью можно избежать неожиданных нестыковок.

    image

    Рисунок 27.Пояснение к вычислению параметров для тип 3а

    
           Width = Line_width;
           bytes_For_load  = (Width + 7) >> 3;
           bytes_For_shift = (Width + Current_shift + 7) >> 3;
           bytes_For_out   = bytes_For_shift;
           Right_mask      = sb_Right_mask[ (Current_shift + Width) & 0x7];
    
           Current_shift_next = (Current_shift + Width) & 0x7;
           Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 ); 
    

    Символы выступающие слева.

    Тип 2, может частично выходить за границу окна отображения, левая часть может не попадать на дисплей.
    image

    Рисунок 28. Символы тип 2

    Моделирование выхода символа влево за границу окна вывода можно реализовать, сдвинув свежезагруженный символ на Left_shift пикселей влево, после чего сдвинуть на Current_shift вправо.

    image

    Рисунок 29. Схема сдвига для тип 2. Вариант лабораторный

    Но вместо того, чтобы выполнять два противоположно направленных сдвига, можно заменить их одним, результирующим. При этом придётся наложить маску (в предыдущем случае ненужные пиксели удалялись сами, «вываливанием» из сдвигового регистра).

    Result_shift = Current_shift - Left_shift;

    На результат сдвига накладывается маска, соответствующая Current_shift

    image

    Рисунок 30. Схема сдвига для тип 2. Вариант рабочий

    Конкретно для данного примера вместо сдвига на 8 позиций (5+3) получается сдвиг на 2 позиции (5-3) для микроконтроллеров с однопозиционным сдвигом. Это актуально для микроконтроллеров msp430. Для ARM7 вместо двух операций сдвига (влево и вправо) появляется одна сдвига и одна наложения маски, что в общем равнозначно.

    Таблица 1. Параметры для работы ОА. Продолжение

    переменная параметр

    формула

    result_shift

    result_shift = current_shift — left_shift;



    image

    Рисунок 31.Параметры для символов тип 2

    Если сдвиг Result_shift направлен вправо, то с точки зрения рассчёта параметров этот случай является повторением случая тип 3, только роль переменной Current_shift играет Result_shift, плюс нужно накладывать маску, отсекающую слева Current_shift пикселей.

    Если результирующий сдвиг направлен влево, это другой случай. Иллюстрация рис. 32 даёт представление о том и другом случаях.

    image

    Рисунок 32. Пояснение к расчёту параметров для символов тип 2

    Хочу обратить внимание, что значение Left_shift, в отличие от Current_shift может превышать 7 пикселей (но в силу алгоритма оно всегда будет меньше чем ширина символа Width). Соответственно, это может приводить к тому, что один или несколько байтов строки символа полностью выпадают (слева) из сдвигового буфера, как например для LS = 10..12, то есть их можно вообще не загружать, экономя. Учитывая маску Left_mask получается, что в случае сдвига LS = 8,9 байт, который содержит пиксели 0..7, хоть и не выпадает из сдвигового регистра полностью, но ни один его пиксель не попадает на экран, поэтому его тоже можно не загружать. Для реализации такого поведения требуется ввести переменную First_byte_gen – индекс первого загружаемого байта из знакогенератора, то есть, какой по счёту байт строки знакогенератора пойдёт в нулевой байт сдвигового регистра. С каждым проигнорированным байтом значение Left_shift уменьшается по модулю на 8. При таком подходе значение Left_shift не превысит 7 пикселей.

    image

    Рисунок 33. Пояснение к ограничению величины Left_shift.

    Главный недостаток такого подхода, что сдвиг Result_shift направлен либо вправо, либо влево, что противоречит упомянутой выше концепции о предпочтительности единообразия технических приёмов. Сдвиг влево (до 7 позиций) можно заменить сдвигом вправо если загружать данные для сдвига байтом левее. Это проиллюстрировано рис. 34, красная линия показывает первый байт в строчном буфере, начиная с которого загружаются строки глифа.

    image

    Рисунок 34. Замена сдвига влево на сдвиг вправо.

    От предыдущего случая этот вариант отличает то, что в случае отрицательного Result_shift вместо сдвига влево используется загрузка на байт левее и сдвиг вправо. Выгрузка данных из регистра сдвига в строчный буфер, всегда начинается с байта индекс 1.

    На первый взгляд, кажется, что количество позиций для сдвига увеличилось: вместо сдвига -2 получился сдвиг 6. Однако в то же время, вместо сдвига – 5 получился сдвиг на 2 позиции. Если брать предельный случай Current_shift = 0, такая замена даст

    Таблица 2. Зависимость фактического сдвига от величины Left_shift при разных схемах организации сдвига

    left_shift & 0x7

    result_shift

    по рис 32

    по рис 33

    0

    0

    0

    1

    -1

    7

    2

    -2

    6

    3

    -3

    5

    4

    -4

    4

    5

    -5

    3

    6

    -6

    2

    7

    -7

    1



    Учитывая, что значения величин Left_shift & 0x7 и Current_shift равновероятны в диапазоне 0..7 я посчитал распределение вероятностей абсолютного значения величины Result_shift (которая определяет фактический побитовый сдвиг)

    Таблица 3. Оценка стоимости — величины пропорциональной удельному циклопотреблению символов тип 2

    по рис. 33.

    по рис. 34.

    |RSi|

    вероятность p

    RSi

    вероятность p

    0

    0.125

    0

    0.125

    1

    0.21875

    1

    0.125

    2

    0.1875

    2

    0.125

    3

    0.15625

    3

    0.125

    4

    0.125

    4

    0.125

    5

    0.09375

    5

    0.125

    6

    0.0625

    6

    0.125

    7

    0.03125

    7

    0.125

    стоимость

    Pi * |RSi|

    3.625

    стоимость

    Pi * RSi

    4.5


    В нижней строке таблицы приведён параметр Стоимость, который пропорционален циклопотреблению операции сдвига первого символа строки при бесконечной эксплуатации. Вариант показанный на рис. 34 проигрывает на 25%, но это не в разы и поскольку циклопотребление именно этой операции невелико в общем циклопотреблении, будет выбран второй вариант, поскольку соответствует концепции единообразия.

    Для чего тогда эта таблица? При реальной разработке она не понадобилась бы, и здесь я привёл её чтобы рассеять сомнения в неэффективности преобразования проиллюстрированного рис. 34. Кроме того, поскольку статья учебная, это пример проектирования в инженерном смысле этого слова, я хочу продемонстрировать, как можно оценивать эффективность тех или иных решений.

    В итоге получились формулы для определения параметров:

         
           Current_shift   = x_block & 0x7;
           First_byte_gen  = Left_shift  >> 3;
           int Corrected_Left_shift = Left_shift & 0x7;
           Left_mask       = sb_Left_mask[Current_shift];
           // Вместо Result_shift используется переменная Current_shift
    
    
           if(Corrected_Left_shift <= Current_shift ) // Result_shift > 0
           {
    
             Current_shift -= Corrected_Left_shift;
             First_byte_reg = 1;
    
             bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
             bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
             bytes_For_out     = bytes_For_shift;
    
             // Поскольку часть символа оказалась за пределами экрана, нужна коррекция, чтобы правильно учитывать при уменьшении Line_width  
             Width -= Left_shift;
             
           }
           else 
           {
               
             Current_shift = Current_shift - Corrected_Left_shift + 8;
             First_byte_reg = 0;
          
             bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
             bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
             bytes_For_out     = bytes_For_shift - 1;
           
          }
    
          
           Current_shift_next = (Current_shift + Width) & 0x7;
           Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 ) - First_byte_gen; 
    
    

    Символы выступающие и слева и справа

    Самый комплексный случай. Он как бы совмещает в себе случай 2 и 3а.

    image

    а)
    image

    б)

    Рисунок 35. Параметры для символа тип 2а,

    Символ тип 2а, является модифицированным случаем тип 2, у которого «выпадает» и левая и правая часть символа.

    Величина $inline$\text{Line_width}_\text{Limit}$inline$ это остаток строки $inline$\text{Line_width}$inline$ на момент когда происходит событие Достигнута граница окна вывода. Как и в случае пары 3/3а, тип 2а сводится к тип 2, с заменой ширины символа $\text{Width}_\text{Символа})$ на значение $inline$\text{Line_width}_\text{Limit}$inline$. Аналогично, в отличии от «истинных» тип 2, глиф знакогенератора всё же содержит пиксели правее чем $inline$\text{Line_width}$inline$ пикселей, поэтому на правую часть правого байта символа накладывается маска, которая определяется координатой окна вывода x1. И так же как и во всех предыдущих случаях, имеем дело с алгебраическим кольцом. Иллюстрация рис. 36 демонстрирует все аспекты расчёта параметров.

    image

    Рисунок 36. Пояснение к расчёту параметров для символов тип 2а

    Это реализуется следующим образом

    
           Width = Line_width;
           Current_shift   = x_block & 0x7;
           First_byte_gen  = Left_shift  >> 3;
           int Corrected_Left_shift = Left_shift & 0x7;
           Left_mask       = sb_Left_mask[Current_shift];
           Right_mask      = sb_Right_mask[ (Current_shift + Width) & 0x7];
    
    
           if(Corrected_Left_shift <= Current_shift ) // Result_shift > 0
           {
    
             Current_shift -= Corrected_Left_shift;
             First_byte_reg = 1;
    
             bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
             bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
             bytes_For_out     = bytes_For_shift;
    
             // Поскольку часть символа оказалась за пределами экрана, нужна коррекция, чтобы правильно учитывать при уменьшении Line_width  
             Width -= Left_shift;
             
           }
           else 
           {
               
             Current_shift = Current_shift - Corrected_Left_shift + 8;
             First_byte_reg = 0;
          
             bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
             bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
             bytes_For_out     = bytes_For_shift - 1;
           
          }
    
          Current_shift_next = (Current_shift + Width) & 0x7;
          Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 ) - First_byte_gen; 
    

    Вывод строчного буфера в видеопамять.

    После того, как сформировано изображение в строчном буфере, оно скидывается в видеопамять. Для этого требуется определить, соответствие между байтами видеопамяти и байтами строчного буфера, см. рис. 37.

    image

    Рисунок 37. Соответствие координат байтов видеопамяти и строчного буфера

    Горизонтальный индекс байтов левого края окна в видеопамяти определяется по формуле

    Left_screen_byte = x_block >> 3; 

    В строчном буфере этому байту соответствует байт начальное значение Current_byte:

    Current_byte = 0;

    Адрес начального байта каждой пиксельной строки видеопамяти вычисляется по формуле

    vm_Left_screen_byte_for_line =  y_vm * x_max_bytes + Left_screen_byte;

    Адрес начального байта каждой пиксельной строки строчного буфера вычисляется по формуле

    sb_Left_screen_byte_for_line =  y_sb * x_max_bytes;

    Ширина x_max_bytes видеопамяти и строчного буфера совпадает.

    Текст может закончиться прежде, чем будет достигнута граница окна вывода, поэтому правый байт рассчитывается на основании величины Current_byte на момент окончания строки, а маска для правого байта рассчитываются не на основе координаты x1, а на основе фактического значения Currnt_shift на момент окончания строки.

    Вопросы маскирования поясняет рисунок.

    image

    Рисунок 38. Пояснение к маскированию крайних байтов при выводе в видеопамять

    Накладывать содержимое видеопамяти на пересекающиеся байты можно не на стадии вывода в строчный буфер, а уже скидывая строчный буфер в видеопамять. В общем случае процедура вывода из строчного буфера в видеопамять будет следующая:

    image

    Рисунок 39. Диаграмма состояний для процесса вывода строчного буфера в видеопамять

    Теперь имеется достаточно информации, для того, чтобы построить работающий УА на основе созданного в прошлой итерации каркаса.

    Итерация 2. УА соберём всё вместе


    Требования к УА не поменялись, но определены параметры которые нужно вычислять на этапе инициализации. Напомню, что величина x_shift к этому моменту компенсирована

    Управляющий автомат для Автомата вывода текстовых блоков
    void tDisplay_A1::Out_text_block ()
    {
    
      x_block = x0 + x_rel + x_shift;
      y_block = y0 + y_rel + y_shift;
    
      Text = Text_begin;
    
      if(x_block < x0)
      {
        Left_shift = x0-x_block;
        x_block = x0;
      }
      else
    
        Left_shift = 0;
    
      Line_width = x1 - x_block;
    
      if(Line_width <= 0)
    
        return;
    
    ////////////////////////////////////////
    Type_1:
    
      // Пока не конец строки
      while(Text < Text_end)
      {
    
        Width = Current_font->Width_for(*Text);
          
        // Прокручиваем символы пока не попадём в область отображения
        if(Left_shift >= Width)
        {
          Left_shift  -= Width;
          Text++;
        }
        else
    
          goto Type_2;
    
      }// while(Text < Text_end)
    
      // Конец строки
      return;
    
    ////////////////////////////////////////
    Type_2:
    
      // Достигли границы?
      if(Line_width <= Width)
      {
        Width = Line_width;
        Current_shift   = x_block & 0x7;
        First_byte_gen  = Left_shift  >> 3;
        int Corrected_Left_shift = Left_shift & 0x7;
        Left_mask       = sb_Left_mask[Current_shift];
        // Вместо Result_shift используется переменная Current_shift
    
        if(Corrected_Left_shift <= Current_shift ) // Result_shift > 0
        {
    
          Current_shift -= Corrected_Left_shift;
          First_byte_reg = 1;
    
          bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
          bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
          bytes_For_out     = bytes_For_shift;
    
          // Поскольку часть символа оказалась за пределами экрана, нужна коррекция, чтобы правильно учитывать при уменьшении Line_width
          Width -= Left_shift;
             
        }
        else
        {
               
          Current_shift = Current_shift - Corrected_Left_shift + 8;
          First_byte_reg = 0;
    
          bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
          bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
          bytes_For_out     = bytes_For_shift - 1;
    
        }
    
        Current_shift_next = (Current_shift + Width) & 0x7;
        Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 ) - First_byte_gen;
    
        Right_mask      = sb_Right_mask[ Current_shift_next ];
    
        Symbol_t2a();
        goto Finalize;
    
      }
    
      Line_width -= Width;
    
      Current_shift   = x_block & 0x7;
      First_byte_gen  = Left_shift  >> 3;
      int Corrected_Left_shift = Left_shift & 0x7;
      Left_mask       = sb_Left_mask[Current_shift];
      // Вместо Result_shift используется переменная Current_shift
    
    
      if(Corrected_Left_shift <= Current_shift ) // Result_shift > 0
      {
    
        Current_shift -= Corrected_Left_shift;
        First_byte_reg = 1;
    
        bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
        bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
        bytes_For_out     = bytes_For_shift;
    
        // Поскольку часть символа оказалась за пределами экрана, нужна коррекция, чтобы правильно учитывать при уменьшении Line_width
        Width -= Left_shift;
             
      }
      else
      {
               
        Current_shift = Current_shift - Corrected_Left_shift + 8;
        First_byte_reg = 0;
          
        bytes_For_load    = (Width + 7) >> 3 - First_byte_gen;
        bytes_For_shift   = (Width + Current_shift + 7) >> 3 - First_byte_gen;
        bytes_For_out     = bytes_For_shift - 1;
    
      }
    
          
      Current_shift_next = (Current_shift + Width) & 0x7;
      Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 ) - First_byte_gen;
    
    
      //////////////////////////////////
      //ОА
      //////////////////////////////////
      Symbol_t2();
    
      // Любой следующий символ
      Text++;
    
    ////////////////////////////////////////
    Type_3:
    
      // Конец строки?
      while(Text < Text_end)
      {
    
        Width = Current_font->Width_for(*Text);
        
        // Достигли границы?
        if(Line_width <= Width)
        {
          Width = Line_width;
          Line_width = 0;
          bytes_For_load  = (Width + 7) >> 3;
          bytes_For_shift = (Width + Current_shift + 7) >> 3;
          bytes_For_out   = bytes_For_shift;
    
          Current_shift_next = (Current_shift + Width) & 0x7;
          Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 );
    
          Right_mask      = sb_Right_mask[ Current_shift_next ];
    
          Symbol_t3a();
          goto Finalize;
    
        }
    
        Line_width -= Width;
    
        //////////////////////////////////
        //ОА
        //////////////////////////////////
        Symbol_t3();
    
        bytes_For_load = (Width + 7) >> 3;
        bytes_For_shift = (Width + Current_shift + 7) >> 3;
        bytes_For_out  = bytes_For_shift;
    
        Current_shift_next = (Current_shift + Width) & 0x7;
        Current_byte_next  = Current_byte + ( (Current_shift + Width) >> 3 );
    
        Text++;
    
      }// while(Text < Text_end)
    
      Finalize:
      // Вывод строчного буфера в видеопамять
     
     return;
    
    }// void tDisplay_A1::Out_text_block ()


    Итерация 3. ОА


    Требования к ОА всех типов вполне определись в итерации 2. Теперь можно сделать функции реализующие эти ОА. Дополнительных комментариев не требуется, поскольку приведённое ранее описание всех типов ОА исчерпывающее.

    Исходник
       class tShift_buffer;
       // void tShift_buffer::Load  (u1x * Source, u1x Destination_index, u1x bytes_Width);
       // void tShift_buffer::Shift (u1x First_byte_index , u1x Bytes_amount , u1x Bits);
       // void tShift_buffer::Out   (u1x Source_index, u1x * Destination, u1x bytes_Width);
       // Скидывает данные из буфера в указанное место наложением по или
       // void tShift_buffer::Out_by_or (u1x Source_index, u1x * Destination, u1x bytes_Width);
    
       // u1x & tShift_buffer::operator [](u1x Index_of_element);
    
       ////////////////////////////////////////////////////////////////////////////
       // ОА
       void Out_symbol_t2()
       {
    
         // ВЕРТИКАЛЬНЫЙ ЦИКЛ
         for(int y = 0; y < Height; y++)
         {
    
           uchar * Glyph_line = Current_font->Image_for(* Text) + y * bytes_Width;
           Shift_buffer.In  (Glyph_line, bytes_For_load);
           Shift_buffer.Shift (Current_shift, bytes_For_shift);
           Shift_buffer. Out_by_or (&String_buffer [y][Current_byte + i], bytes_For_out);
    
         }// for(int y = y0; y < Height; y++)
    
       }// void Out_symbol_t2()
    
    ////////////////////////////////////////////////////////////////////////////
       // ОА
       void Out_symbol_t2a()
       {
    
         // ВЕРТИКАЛЬНЫЙ ЦИКЛ
         for(int y = 0; y < Height; y++)
         {
    
           uchar * Glyph_line = Current_font->Image_for(* Text) + y * bytes_Width;
           Shift_buffer.In  (Glyph_line, bytes_For_load);
           Shift_buffer.Shift (Current_shift, bytes_For_shift);
           Shift_buffer.Out (&String_buffer [y][Current_byte + i], bytes_For_out);
    
         }// for(int y = y0; y < Height; y++)
    
       }// void Out_symbol_t2a()
    
    
    ////////////////////////////////////////////////////////////////////////////
       // ОА
       void Out_symbol_t3()
       {
    
         // ВЕРТИКАЛЬНЫЙ ЦИКЛ
         for(int y = 0; y < Height; y++)
         {
    
           uchar * Glyph_line = Current_font->Image_for(* Text) + y * bytes_Width;
           Shift_buffer.In  (Glyph_line, bytes_For_load);
           Shift_buffer.Shift (Current_shift, bytes_For_shift);
           Shift_buffer.Out (&String_buffer [y][Current_byte + i], bytes_For_out);
    
         }// for(int y = y0; y < Height; y++)
    
       }// void Out_symbol_t3()
    
    ////////////////////////////////////////////////////////////////////////////
       // ОА
       void Out_symbol_t3a()
       {
    
         // ВЕРТИКАЛЬНЫЙ ЦИКЛ
         for(int y = 0; y < Height; y++)
         {
    
           uchar * Glyph_line = Current_font->Image_for(* Text) + y * bytes_Width;
           Shift_buffer.In  (Glyph_line, bytes_For_load);
           Shift_buffer.Shift (Current_shift, bytes_For_shift);
           Shift_buffer.Out (&String_buffer [y][Current_byte + i], bytes_For_out);
    
         }// for(int y = y0; y < Height; y++)
    
       }// void Out_symbol_t3a()


    Итерация 3. Отладка


    Для отладки я использовал простую форму, которая позволяет менять исходные параметры функции Out_text. В правой части формы выводятся интересующие параметры, наблюдая их легко фиксить баги.

    image

    Рисунок 40. Форма для отладки

    Не буду описывать ход отладки, ограничусь баг-листом.

    -вместо ширины экрана bytes_X_size использовалась ширина символа bytes_Width.
    -новые значения next не становились текущими
    -не было очистки сдвигового регистра
    -во всех выражениях вида (Width + Current_shift + 7) >> 3 - First_byte_gen; опечатка с забытой скобкой ( (Width + Current_shift + 7) >> 3 ) - First_byte_gen;
    -маскирование правого крайнего байта поправлены индексы.

    Результат налицо

    Отладка не была трудной. Таблицы рис. 25, 27, 34, 36 не подвели. Я акцентирую внимание на таблицах, потому что составление вспомогательных таблиц и прочих иллюстраций это часть предметного взгляда на задачу, это отличное подспорье при программировании, я обязательно буду демонстрировать это в дальнейшем при разборе примеров.

    Пора подвести итоги первой части. Разработанный модуль вывода текстовых блоков, функционально соответствует ТЗ. Он может выводить текст любой длины шрифтом любого размера в окно любого размера, хоть 1х1, с любым смещением. Как было сказано ранее, эффективность решения связана с тем, насколько эффективно удастся реализовать операции сдвига, а в этом варианте выбрано вероятно самое неэффективное решение из возможных – класс tShift_register. Он хорош тем, что нагляден и универсален, но это исключительно «лабораторное решение», которое годно лишь для отладки модуля. Следующий этап – переход к реальным ОА. Они будут работать по тому же принципу и с тем же набором параметров как было описано в текущей статье, но в них будет использована иная схема сдвига, которая позволит этим ОА стать по-настоящему эффективными. Поскольку объём этой статьи достиг критической массы, то чтобы ненароком не взорвать мозг тем читателям, которые смогли добраться до финала, следующий этап я опишу в следующей статье. Следующая статья будет посвящена модификациям и усовершенствованиям разработанного в этой статье модуля «Дисплей». Будет продемонстрировано, что автоматно спроектированные программы весьма податливы в этом отношении, и внесением небольших усовершенствований можно добиться потрясающих результатов, и речь идёт не только о повышении эффективности, но и о расширении функционала.

    Поделиться публикацией

    Комментарии 24

      0
      а каково практическое применение всего этого?
        0
        ответ в ветке ниже
        0
        Вообще — в результате получаются программные модули, и ваш вопрос эквивалентен вопросу «какое практическое применение программного модуля»? Отвечая на этот вопрос — Модуль «Дисплей» я использую в своих программах для микроконтроллера. Это системный уровень, и хорошая эффективность этого модуля позволяет мне делать пользовательский интерфейс исходя из потребностей, а не ограничений программно-аппаратной части. Помимо этого модуля, автоматное проектирование подходит для решения любых задач.
          0
          ОК, понял.
          Сразу же вопрос, в чем преимущество Вашего модуля перед графическими
          библиотеками, например перед EmWin
            0
            например перед EmWin

            Абсолютное преимущество в быстродействии. Дело в том, что GUI_DisplayString построены по тому же принципу что и вариант A2 рассмотернный в предыдущей статье. Однако если А2 уступал варианту А1 в скорости в 4-8 раз, то в случае GUI_DisplayString в силу более мелкопопиленного на «каждый-чих-функции» кода, речь может идти о гораздо большей разнице.
            Кроме того, благодаря параметрам x_shift y_shift предложенный мной вариант обладает большим функционалом -бегущие строки. GUI_DisplayString имеет много разновидностей в том числе и функцию wrap text, которая позволяет вывести текст в окне так, что он из одной строки автоматически перенося слова распределится по всему окну. Это одна из функций с которой я собираюсь познакомить читателя, это одно из того самого обещанного расширения функционала о котором я написал в конце статьи. Так что продолжайте следить за циклом.

            — И наконец самое главное, хочу акцентировать ваше внимание, что я представляю не просто модуль работы с графикой, это всего лишь иллюстрация к автоматному проектированию. Речь в цикле идёт об автоматном проектировании как таковом, иными словами процессу от анализа тз до получения эффективного решения и дам этому разные примеры, но в силу своей занятости, прошу читателей запастись терпением
              0
              Спасибо за развернутый ответ.
              Еще вопрос — а Ваши решения применимы к выводу графики (bmp, jpg)?
                0
                Символы тип 2а ни что иное как битмапы любых размеров выводимые в окно любых размеров.
                над выводом jpg я не задумывался в силу прагматических причин, отсутствия необходимости, но, думаю ситуация решаемая, а вообще, на основе этой схемы вывода данных удалось построить графическую библиотечку, и реализовать просмотр графиков
                image с довольно приличным быстродействием.
                Поскольку всё познаётся в сравнении, расскажу, что изначально мой коллега пробовал для своей задачи но той же схемы uC+дисплей одну из стандартных библиотек, и стало ясно, что обеспечить просмотр графиков с этой библиотекой просто нереально, потому что видно как прорисовывается изображение, «переливатся». Это может приемлемо если график выведен и всё, но если его нужно на ходу масштабировать, то мучение сплошное, иначе не скажешь.
                  +1

                  Здесь мой внутренний перфекционист взвыл… Сдвинуть всего лишь на 1 пиксель…
                    +1
                    Соглашусь, в приложении-то я поправил.
                    0
                    с довольно приличным быстродействием

                    а конкретно, сколько fps?
                    и можно ли эту библиотечку использовать для создания осциллографа?
                      0
                      если брать проц побыстрее, вполне возможно, arm7 не для осцила.
                      с довольно приличным быстродействием

                      Речь идёт о том, что организован просмотр графиков с масштабированием (ближе/дальше) и прокрутка вправо/влево, и это происходит без раздражающих эффектов когда вы буквально видите как происходит последовательная прорисовка этого самого графика. Ведь с одной из широко распространённых библиотек, использованных моим коллегой так и было, что послужило причиной разработки собственной библиотеки
                        0
                        а если не секрет, что за
                        широко распространённая библиотека

                          0
                          Честно скажу, не помню, это было уже почти 10 лет назад, мой коллега скачал её по рекомендации на сахаре или телесистемах и запилил на ней свой прибор. Поскольку она меня впечатлила тем насколько она не годилась для моих целей, я больше не интересовался ей. А вы с какой целью интересуетесь?
                            0
                            интересуюсь в связи с тем, что в данный момент делаю систему отображения для мониторинга и управления эндоскопического хирургического инструментария на STM32F769 (24 bit RGB 32 bit SDRAM)…
                            ну, просто подумал, что речь идет о EmWin
                            … хотя 10 лет назад вряд ли можно было найти embedded ориентированную библиотеку для вывода графики на дисплеи
                            кстати с помощью EmWin неплохо выводятся графики в реальном масштабе с прокруткой
                            … и кстати где можно посмотреть Вашу графическую библиотеку с помощью которой выводятся такие красивые графики нарисованные выше в коментариях
                              0
                              Не раньше чем будут решены организационные вопросы. Речь ведь пойдёт о перспективах и дальнейших путях развития не только графической библиотеки, но и автоматной оболочки, которая связана с автоматным подходом к проектированию. Если просто опубликовать на неё datasheet, боюсь что она упадёт на неподготовленную почву, потому что автоматное программирование — экзотика, и сейчас требуется не просто показать, что моя библиотека лучше, а о то, что сам автоматный подход — прекрасное подспорье при разработке, а в чём то даже для начала, что это не чудище рыкающее аки лев. Поэтому я пока не веду речь о библиотеке как таковой.
                              То есть, возвращаясь к вашим потребностям в графике, этот цикл не о моей библиотеке а об автоматном подходе. Указанный модуль «Дисплей» я взял как хороший пример для автоматного проектирования, не хотелось брать пример уровня «пусть у нас есть кнопка и светодиод».
                                0
                                Но для вывода текста можете взять этот модуль, я его опубликую в следующей статье, рабочую версию, он не будет конфликтовать с той библиотекой, насколько я оценил. Я предоставил этот модуль в безвозмездное пользование программерам, только не забудьте указать ссылку на мой сайт, и можете сами добавить его в закладки, сейчас там немного нового, но я его буду постепенно обновлять и там со временем выложу эту библиотеку.
                                  +1
                                  спасибо за ответ, но простите, ссылку на bitbucket проект вижу, а на Ваш сайт не вижу.
                                  … и еще об автоматном программировании вообще и о его применении для создания обработчиков пунктов меню. У Texas Instrument есть хорошая реализация автоматного подхода к этому вопросу, которую я несколько раз применял в своих проектах.
                                  Возможно Вас заинтересует
                                  www.ti.com/lit/an/slaa402a/slaa402a.pdf

                                    0
                                    сайт dashingresearches.wordpress.com, он в исходниках в лиценизии написан. Такое условие лицензии — его упоминать при использовании модуля Дисплей, чтобы пользователи модуля могли найти новинки по этой же теме. Там ещё переводная литература по автоматам, «Структура и анализ систем и сигналов.» лекции Беркли, качественные лекции, в том числе и по недетерминированным автоматам и композициям автоматов
                                    За ссылку спасибо
                +1
                Я в свое время, тоже походил по граблям, когда реализовывал BitBlt однобитный, с произвольным выводом. Помню — то там отрежет кусок, то там… В итоге построил в экселе все возможные сдвиги и нашел багу в алгоритме :)
                  0
                  предметный взгляд на задачу не только помогает при отладке, но и помогает найти решение там, где иначе потребовался бы если не полный перебор, то во всяком случае глубокая рекурсия. У меня есть впечатляющие примеры, и они запланированы в практикум.
                  0
                  Применима ли Ваша методика, если используется в одной строке символы различных наборов шрифтов?
                    0
                    Читайте ТЗ, там об этом как раз
                    0
                    А кроме примера «Дисплей» нет ли примера «Меню»?
                      0
                      Есть, но все примеры я буду стараться давать так, чтобы они укладывались в некую логику развития идей. «Меню» связано не столько с графикой сколько с организацией сообществ автоматов, и оно будет рассмотрено где-то там (махнул рукой в неопределённом направлении)

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое