Автоматная разработка, практикум. Пример «Дисплей». Часть 1

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

Оглавление

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

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

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


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

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


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

c334beb5618546e096e5f59c1b35db15.PNG


а)

1f94d02f9f064cdebe4d662289d71d09.png


б)

88758b0295974107b20932f7cffd08dd.png


в)

Рисунок 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);


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


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

9be130d26dbc447ca2899763d6586395.png


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

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

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

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

f5172d2e27cf448fa46b015f3fd21dbf.PNG


Рисунок 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) & 0×7;

current_byte0 = 0;

current_ shift0 = x_block & 0×7;

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 — номер первой строки (снизу) которая уже не попадает в диапазон перебираемых значений. То есть для символа 6×9 который полностью попадает в буфер 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_width — width;
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 направлен вправо, то с точки зрения рассчёта параметров этот случай является повторением случая

© Habrahabr.ru