День 13. Самостоятельное программирование

День 13. Самостоятельное программирование

Статья о TRON. Постановка задачи. Алгоритм. Кодирование.

Статья о TRON

Сегодня наткнулся на статью о TRON, где автор высказывает довольно любопытные положения.

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

Под этим утверждением сам могу смело подписаться. Сколько раз сталкивался с отсутсвием документации, сопровждающим материалам как к SP-Forth так и к библиотекам, которые включены в дистрибутив.

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

Тут сложно не согласится. Впрочем как и с последней фразой. Вообще, считаю, что и с "белыми ящиками" ситуация аналогична. Когда говорят о самодокументированности кода опускают или подразумевают и другие вещи, как-то, известность постановки задачи читающему, общие принципы ее решения, восприятия подобного стиля описания кода, т.е. все то что приняти называть сопровождающей документацией на программный продукт. В противном случае, это – миф. Если я не понимаю не то что язык программирования реализации, а стиль в котором написан код, то для меня это "черный ящик". И, как показывает моя личная практика, чаще всего библиотеки расматриваются как "черные ящики", независимо от наличия исходников. А самое важное значение имеет описание области применения библиотеки, справочник ее функций и примеров их использования.

Резюмируя эту статью, меня заинтересовал подобный подход – подход создания спецификации, а на основнове специфиикаций создавать совместимые компоненты.

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

Для самостоятельного программирования на Forth решил реализвать симулятор и управляющую программу группой светодилов – индикаторов. Задача была навеяна походей из стандарта DPANS94

Есть группа из 16 светодиодов (индикаторв). Кажый светодиод имеет управляется отдельно, т.е. имеет свой адрес. Но его состояние не фиксируется. Иными словами светодиод горит, только когда к нему обращаются, в противном случае гаснет. Чтобы обеспечить видимоть горения нужных светодидов к ним с большой частотой периодически подключаются.

Нужно создать управляющую программу для управляния вкл/выкл, скорость мигания каждым светодиодом независимо от остальных.

Нужно написать программу для отдельного регулирвоания их скоростью мигания и

Алгоритм

Понятно что у нас будет несколько блоков:

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

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

Частота опроса всей матрицы задается директивно во время разработки. Она же определяет цикл мигания индикатора. Чем выше частота опроса, тем быстрее мигания.

Объекты программы

Кодирование

Эммулятор Его назначение эммулировать горение указанного светодиода из все группы. Светодиод определяется порядковым номером. Он горит, пока не произойдет смена адреса на другой. Есть отдельная команда – погасить индикатор.

Горение светодиода эммулируется наличием символа в определенной позиции, отсутсвие символа – индикатор погашен.

Все остальное, уже ясно по листингу

   \ Управление группой индикаторов верс.0.1

REQUIRE AT-XY ~day\common\console.f

: ПозИндикаторов 14 7 TEXT-ATTR \ Погашенные индикаторы 5 3 AT-XY ." . . . . . . . . . . . . . . . . " ;

: ПолеВводаКоманд 10 0 TEXT-ATTR \ Командная строка 1 5 AT-XY ." >"

;

: НарисоватьТабло 4 8 TEXT-ATTR CLS \ Номера индикаторов 5 2 AT-XY ." 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 " ПозИндикаторов ПолеВводаКоманд ;

\ Команды пользователя : ВклИндикатор ( номер – ) НарисоватьТабло 14 14 TEXT-ATTR 3 * 2+ 3 AT-XY SPACE ПолеВводаКоманд ;

: ВыклВсеИндикаторы НарисоватьТабло ПолеВводаКоманд ;

\ Testing \ В целях упрощения ввода, даю синонимы командам : o ВклИндикатор ; : c ВыклВсеИндикаторы ;

ВыклВсеИндикаторы

Идея понятна – рисую что-то вроде индикаторв с номера, чуть ниже даю строку для ввода команд, причем курсор перевожу принудительно комнадой AT-XY И тут же ловлю баг, интерпретатор читает посимвольно ввод! Т.е. я не могу ввести даже двузначное число. Нажимаю цифру и хлоп получаю его тут же на стеке и Ok. Полез в исходники библиотеки C:\spf\devel\~day\common\console.f , сама она простая всего 959 строк, но легче мне от этого не стало. Этоеще раз возвращает нас к тезису к тому, что исходные коды это не ДОСТАТОЧНОЕ условие для работы с ПО сторонним человеком. Для меня библиотека сейчас – "черный ящик".

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

   \ Управление группой индикаторов верс.0.2
 REQUIRE AT-XY ~day\common\console.f

: ПозИндикаторов 14 7 TEXT-ATTR \ Погашенные индикаторы 5 3 AT-XY ." . . . . . . . . . . . . . . . . " ;

: ПолеВводаКоманд 10 0 TEXT-ATTR \ Командная строка 1 5 AT-XY 40 SPACES \ очищаю остатки предыдущего ввода 1 6 AT-XY 40 SPACES 1 7 AT-XY 40 SPACES 1 8 AT-XY 40 SPACES 1 5 AT-XY ." >" ;

: НарисоватьТабло 4 8 TEXT-ATTR CLS \ Номера индикаторов 5 2 AT-XY ." 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 " ПозИндикаторов ПолеВводаКоманд ;

\ ------ Команды пользователя ----------- : ВклИндикатор ( номер – ) DEPTH 0 > IF DEPTH 1 > IF SWAP 10 * + THEN \ Если на стеке более одного числа, то \ внизу старшая часть числа DUP 1 17 WITHIN IF НарисоватьТабло 14 14 TEXT-ATTR 3 * 2+ 3 AT-XY SPACE ELSE DROP THEN THEN ПолеВводаКоманд ;

: ВыклВсеИндикаторы НарисоватьТабло ПолеВводаКоманд ;

\ Testing \ В целях упрощения ввода, даю синонимы командам : o ВклИндикатор ; : c ВыклВсеИндикаторы ;

ВыклВсеИндикаторы \ Запуск

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

Очевидно, что здесь должно быть два независимых процесса – один отображает индикаторы (точнее, частоту мигания), а второй принимает команды от пользователя и устанавливает параметры индикаторов. Т.е. тут нужно разбираться с организацией подпроцессов, их взаимодействия. Или придумывать искусственное решения по организации псевдомногозадачности. Очевидно, что первый подход менее трудоемок, но с ним нужно еще разбираться. Поэтому пока выставляются параметры при инициализации, затем запускается процес инициализации. Выход по Ctrl+Break. Есть конечно недоработка по освобождению памяти, но пока и этого достаточно.

   \ Управление группой индикаторов верс.0.3
 REQUIRE AT-XY ~day\common\console.f

: ПозИндикаторов 14 7 TEXT-ATTR \ Погашенные индикаторы 5 3 AT-XY ." . . . . . . . . . . . . . . . . " ; : ПолеВводаКоманд 10 0 TEXT-ATTR \ Командная строка 1 5 AT-XY 40 SPACES \ очищаю остатки предыдущего ввода 1 6 AT-XY 40 SPACES 1 7 AT-XY 40 SPACES 1 8 AT-XY 40 SPACES 1 5 AT-XY ." >" ; : НарисоватьТабло 4 8 TEXT-ATTR CLS \ Номера индикаторов 5 2 AT-XY ." 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 " ПозИндикаторов ;

\ ------ Команды пользователя ----------- : ВклИндикатор ( номер – ) НарисоватьТабло 14 14 TEXT-ATTR 3 * 2+ 3 AT-XY SPACE 14 7 TEXT-ATTR \ Погашенные индикаторы ; : ВыклВсеИндикаторы НарисоватьТабло ПолеВводаКоманд ;

\ ------------------ Индикатор -------------- 0 VALUE ЧастотыИндикаторов : Init 16 ALLOCATE DROP \ Выделил память 15 0 DO \ Обнуляю массив DUP I CELLS + \ Очередная ячейка 0 SWAP ! \ записываю ее LOOP TO ЧастотыИндикаторов \ сохранил адрес области памяти ; : ЧастотаИндикатора ( частота номер – ) DUP 0 17 WITHIN IF 1- CELLS ЧастотыИндикаторов + ! ELSE 2DROP THEN ; : ПоказатьИндикаторы ( НомКадра ) НарисоватьТабло 15 0 DO \ прохожу по индикаторам DUP I CELLS ЧастотыИндикаторов + @ < IF \ включаю если нужно I 1+ ВклИндикатор THEN 1 PAUSE \ делаю задержку LOOP ; : ЦиклПоказа 8 1 DO I ПоказатьИндикаторы LOOP ПолеВводаКоманд ; \ ------------ Testing --------------- : test Init 8 2 ЧастотаИндикатора 4 12 ЧастотаИндикатора 7 5 ЧастотаИндикатора

10000 1 DO ЦиклПоказа LOOP ; test

Здесь словил следующий момент, по стандарту, и по help MS есть специальное слово MS которое должно организовывать задержки. Реально в SP-Forth используется слово PAUSE.

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

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

Столкнулся еще с такой проблемой - курсор-то я спрятал командой HIDE-CURSOR а как его восстановить?

   \ Управление группой индикаторов верс.0.4
 REQUIRE AT-XY ~day\common\console.f

: ПозИндикаторов 14 7 TEXT-ATTR \ Погашенные индикаторы 5 3 AT-XY ." " ; : ПолеВводаКоманд 10 0 TEXT-ATTR \ Командная строка 1 5 AT-XY 40 SPACES \ очищаю остатки предыдущего ввода 1 6 AT-XY 40 SPACES 1 7 AT-XY 40 SPACES 1 8 AT-XY 40 SPACES 1 5 AT-XY ." >" ; : НарисоватьТабло 4 8 TEXT-ATTR CLS \ Номера индикаторов 5 2 AT-XY ." 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 " ПозИндикаторов ; \ ------ Команды пользователя ----------- : ВклИндикатор ( номер – ) \ НарисоватьТабло 14 14 TEXT-ATTR 3 * 2+ 3 AT-XY SPACE 14 7 TEXT-ATTR \ Погашенные индикаторы ; : ВыклВсеИндикаторы НарисоватьТабло ПолеВводаКоманд ; \ ------------------ Индикатор -------------- 0 VALUE ЧастотыИндикаторов : Init 16 ALLOCATE DROP \ Выделил память 15 0 DO \ Обнуляю массив DUP I CELLS + \ Очередная ячейка 0 SWAP ! \ записываю ее LOOP TO ЧастотыИндикаторов \ сохранил адрес области памяти ; : ЧастотаИндикатора ( частота номер – ) DUP 0 17 WITHIN IF 1- CELLS ЧастотыИндикаторов + ! ELSE 2DROP THEN ; : ПоказатьИндикаторы ( НомКадра ) ПозИндикаторов 15 0 DO \ прохожу по индикаторам DUP I CELLS ЧастотыИндикаторов + @ < IF \ включаю если нужно I 1+ ВклИндикатор THEN LOOP ; : ЦиклПоказа 8 1 DO I ПоказатьИндикаторы 5 PAUSE \ делаю задержку LOOP ; \ ------------ Testing --------------- : test Init 11 1 DO I I ЧастотаИндикатора LOOP

HIDE-CURSOR НарисоватьТабло 10000 1 DO ЦиклПоказа LOOP ; test

Пока все. Ясно что нужно разбираться с многозадачностью.

Многозадачность

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

Итак, некоторые слова по работе с многозадачностью собраны в C:\SPF\src\win\spf_win_mtask.f Нужно заметить, что слова там вкратце описаны. Но комментарии не спасают когда нужно начать что-то делать.

В качестве примера использовал C:\SPF\devel\~nn\lib\test\mutex.f В итоге получил следующий работающий пример.

Здесь же нашел интерсеный пример организации бесконечного цикла – конструкция BEGIN ... AGAIN

  \ Тестирование многозадачности верс. 0.1

:NONAME \ именно через это ключевое слово BEGIN \ бесконечный цикл ." task1" CR 500 PAUSE AGAIN ; TASK: t1 \ имя образу (?) задачи

:NONAME BEGIN ." task2" CR 700 PAUSE AGAIN ; TASK: t2

0 t1 START \ На стеке ИД потока 0 t2 START

Самые большие грабли где я здесь словил – это необходимость использования при задании потока исключительно ключевого слова :NONAME Хорошо, что пример был под рукой :)

Самое интерсеное, что процессы запускаются отдельно и работают параллеьно основному потоку, что позволяет вводить команды.

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

   \ Управление группой индикаторов верс.0.5 Многозадачность
 REQUIRE AT-XY ~day\common\console.f

: ПозИндикаторов 14 7 TEXT-ATTR \ Погашенные индикаторы 5 3 AT-XY ." " ; : НарисоватьТабло 4 8 TEXT-ATTR CLS \ Номера индикаторов 5 2 AT-XY ." 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 " ПозИндикаторов ; \ ------------------------ Команды пользователя -----------

: ВклИндикатор ( номер – ) \ НарисоватьТабло 14 14 TEXT-ATTR 3 * 2+ 3 AT-XY SPACE 14 7 TEXT-ATTR \ Погашенные индикаторы ;

\ ---------------------------- Индикатор ------------------- 0 VALUE ЧастотыИндикаторов : Init 16 ALLOCATE DROP \ Выделил память 15 0 DO \ Обнуляю массив DUP I CELLS + \ Очередная ячейка 0 SWAP ! \ записываю ее LOOP TO ЧастотыИндикаторов \ сохранил адрес области памяти ; : ЧастотаИндикатора ( частота номер – ) DUP 0 17 WITHIN IF 1- CELLS ЧастотыИндикаторов + ! ELSE 2DROP THEN ; : ПоказатьИндикаторы ( НомКадра ) ПозИндикаторов 15 0 DO \ прохожу по индикаторам DUP I CELLS ЧастотыИндикаторов + @ < IF \ включаю если нужно I 1+ ВклИндикатор THEN LOOP ; : ЦиклПоказа 8 1 DO I ПоказатьИндикаторы 5 PAUSE \ делаю задержку LOOP ; \ ----------------- Многозадачность ----------------------- 0 VALUE ИДТабло \ Храню ИД потока, рисующего табло

:NONAME \ HIDE-CURSOR НарисоватьТабло BEGIN ЦиклПоказа AGAIN ; TASK: Табло

: s \ команда останова работы и выхода из программы ИДТабло STOP TERMINATE ;

\ ------------------------- Testing ------------------------- :NONAME \ запуск тестового примера Init 11 1 DO I I ЧастотаИндикатора LOOP

0 Табло START TO ИДТабло

; EXECUTE

Здесь столкнулся с такой проблемой – куда будет относится вход? Вторая, управление курсором – курсор единственный и он перемещается каждым потоком. Проблема его управления изящно решается выделением каждому потоку по окну – отдельной области ввода/вывода. Третья проблема, которая уже обговаривалась, это то что каждый симол тут же обрабатывается! Поэтому в этой реализации от командной реализации отказались.

Здесь же опробовал интересную конструкцию для запсука главной программы :NONAME ... ; EXECUTE Что избавляет от придумывания имен и от потенциальных конфликтов.

<<< Предыдущий Начало   Следующий >>>
Copyright © Alex Furashev 2004

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

Hosted by uCoz