Pull to refresh

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

Reading time 28 min
Views 6.4K
Тесты в предыдущей статье убедительно показали высокую эффективность «автоматной» реализации примера «Дисплей» по сравнению с условно названной «неавтоматной» версией. Вкратце итог: обе реализации автоматные, но разница в эффективности многократна и глубинная причина видится в том, что вариант А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а

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

Таким образом, случай тип 3а сводится к случаю тип 3, с заменой ширины символа $\text{Width}_\text{Символа}$ на значение $\text{Line_width}_\text{Limit}$. Однако, в отличии от «истинных» тип 3, глиф знакогенератора всё же содержит пиксели правее чем $\text{Line_width}$ пикселей, поэтому на правую часть правого байта символа накладывается маска, которая определяется координатой окна вывода 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, у которого «выпадает» и левая и правая часть символа.

Величина $\text{Line_width}_\text{Limit}$ это остаток строки $\text{Line_width}$ на момент когда происходит событие Достигнута граница окна вывода. Как и в случае пары 3/3а, тип 2а сводится к тип 2, с заменой ширины символа $\text{Width}_\text{Символа})$ на значение $\text{Line_width}_\text{Limit}$. Аналогично, в отличии от «истинных» тип 2, глиф знакогенератора всё же содержит пиксели правее чем $\text{Line_width}$ пикселей, поэтому на правую часть правого байта символа накладывается маска, которая определяется координатой окна вывода 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. Он хорош тем, что нагляден и универсален, но это исключительно «лабораторное решение», которое годно лишь для отладки модуля. Следующий этап – переход к реальным ОА. Они будут работать по тому же принципу и с тем же набором параметров как было описано в текущей статье, но в них будет использована иная схема сдвига, которая позволит этим ОА стать по-настоящему эффективными. Поскольку объём этой статьи достиг критической массы, то чтобы ненароком не взорвать мозг тем читателям, которые смогли добраться до финала, следующий этап я опишу в следующей статье. Следующая статья будет посвящена модификациям и усовершенствованиям разработанного в этой статье модуля «Дисплей». Будет продемонстрировано, что автоматно спроектированные программы весьма податливы в этом отношении, и внесением небольших усовершенствований можно добиться потрясающих результатов, и речь идёт не только о повышении эффективности, но и о расширении функционала.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+7
Comments 24
Comments Comments 24

Articles