Главная » Bascom-8051, Basic, Железо » Некоторые советы по созданию больших программ BASCOM-8051

0

Как любой язык высокого уровня, Bascom ориентирован на получение как можно  больше  действий в исполняемом коде при минимальном объеме исходного текста. И на самом деле, Bascom является подходящим инструментом  для  создания  больших  программ.  Теперь  определим  понятие  «большая  программа».  Таковой следует  считать  программу,  использующую  большое  количество  ресурсов  (памяти,  встроенной  и  внешней периферии),   работающей с  большим количеством программных и физических объектов, имеющей несколько независимых  состояний или функций и выполняющая множество действий. Большая программа, как правило, отличается размером кода, не помещающегося в память команд стандартных моделей микроконтроллеров (8051 и 8052). Ниже будут приведены советы по созданию оптимального  кода  «больших программ», оптимального с точки зрения экономии ресурсом микроконтроллера. Эти советы будут не менее полезны и при разработке кода для «маленьких» процессоров, например AT89C2051.

Приблизительный  размер  кода  можно  определить  по  объему  исходного  текста.  Начиная  с  третьего- четвертого килобайта каждые 50 строк исходного текста (около 70 операторов), не считая пустых строк и строк с метками и комментариями, дают один килобайт исполняемого кода.

Первый совет (о аппаратной части).

1.1       Приступая  к  разработке  большой  программы  необходимо  оценить  возможность  создания  такой

программы   в   рамках   имеющихся   аппаратных   ресурсов.   Может   быть,   нужно   создать   резерв   памяти   – предусмотреть  возможность  использования  «старшей»  модели   микроконтроллера  с  большей  программной памятью или дополнительной памятью данных.

1.2      Не  используйте  уникальных  моделей  микроконтроллеров  или их  уникальных  свойств. Переделка большой программы под другой процессор никому не нужна.

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

1.4   Подумайте как будете отлаживать – без эмулятора не  обойтись. Может быть схема не позволит

применить имеющийся эмулятор – лучше изменить схему.

Второй совет (о экономии времени).  Оцените, сколько времени понадобится на программирование и отладку программу, может быть, вы им не обладаете. Когда объем исходного текста «вылезет» за 1000 строк (это 6 – 8 кБ кода) работа начнет замедляться. Таким образом средняя скорость разработки программы 1 – 1.5 кБ в месяц  может  стать  реальной.  Для  ускорения  работы  программу  можно  разделить  на  независимые  модули (большие программы делить намного проще) и писать их параллельно.

Третий совет (о заделах). Используйте в большой программе отработанные модули других программ или разрабатываете и отлаживайте их заранее. Разработать большую программу в разумные (или планируемые) сроки без заделов НЕВОЗМОЖНО! Это правило можно  сформулировать иначе – если все написанное вами ранее не превышает утроенного объема того, что вы собираетесь написать, то, скорее всего, вы с работой не справитесь.

Четвертый совет (об оформлении).

4.1       Пишите  как  можно  проще.  Поменьше  вычурных  конструкций  и  хитростей.  Главное  надежность работы, а сложностей и так хватит.

4.2   Тщательно и подробно комментируйте программу, а оформляйте аккуратно. Это, в итоге, экономит

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

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

4.3  Делите исходный текст программы на части как только он достигнет 1000 строк. Так удобней работать

- можно в разных окнах просматривать одну часть, а редактировать другую. При этом не нужно  листать текст программы назад-вперед, чтобы что-то в ней посмотреть. Применяйте функциональное деление текста. Например, главная программа со всеми объявлениями и  прерываниями + подключаемые программы, каждая отдельная по исполняемой функции  (клавиатуры, индикации, измерений, интерфейса и т.п.). Так закончив работу над одной функцией,  вы  не  рискуете  ее  испортить,  например,  случайным  нажатием  кнопки  при  перелистывании.  Этот фрагмент уже не нужно открывать редактором.

4.4      Меньше используйте ассемблерные вставки. Они не столь эффективны (для сокращения кода) как в маленьких программах.

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

возникнет в этом же месте снова.

Пятый  совет  (когда  думать).  Перед  началом  работы  тщательно  продумайте,  что  и  когда  писать. Разработайте структуру программы и план использования ресурсов. Иначе можно  долго работать на корзину. Если нет видения общей картины, начинайте с модулей программы, которые наверняка понадобятся (сейчас или, в крайнем случае, когда-нибудь).

Шестой совет (совместимость с AVR). Поменьше используйте уникальных возможностей ядра 8051 и “Bascom-8051”. Не увлекайтесь без необходимости ассемблерными  вставками. Это, в итоге, не дает большого выигрыша.  Может  быть  программу  придется  перенести  на  AVR-ядро  и  компилировать  “BasAVR”,  который совместим на 95 % с “Bascom-8051”, но работает более чем на порядок быстрее.

Седьмой совет (о структуре программы).

7.1     Чем больше программа, тем лучше она должна быть структурирована.

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

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

7.4      Строго выполняйте принцип – каждое действие или использование единицы  аппаратного ресурса выполняется только в одном месте программы или только одним программным модулем. Только таким образом

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

устройства или правильное выполнение операции означает правильную работу программы, отвечающую за это.

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

‘————————————-

‘ Взять номинальное значение уровня калибровки в Ub

‘ Ub(Single) – принимает значение, Ranga(Byte) – нумерация шагов калибровки

Get_nom_sc:

Ub = Lookup(ranga , Tab_nom_sc) : Return ‘————————————-

‘таблица номинального значения напряжения на шагах калибровки в вольтах

Tab_nom_sc:

Data 0.0003!                          ‘0 – предел 0.2mv Data 0.0005!                          ‘1 – предел 0.5mv Data 0.001!                           ‘2 – предел 1mv Data 0.003!                           ‘3 – предел 2mv Data 0.005!                           ‘4 – предел 5mv Data 0.01!                            ‘5 – предел 10mv Data 0.03!                            ‘6 – предел 20mv Data 0.05!                            ‘7 – предел 50mv Data 0.1!                             ‘8 – предел 100mv Data 0.3!                             ‘9 – предел 200mv Data 0.5!                             ’10 – предел 500mv Data 1!                               ’11 – предел 1v

‘————————————-

Следующий  пример  программы организация  табличных  переходов. Для этого в  наибольшей  степени подходит оператор ON var GOTO … , … , … . Мы видим, что в  зависимости от значения переменной Ranga происходит переход к метке одного из вариантов действий. Главная особенность этой конструкции по сравнению с похожей конструкцией SELECT  CASE var … … … , которая также может быть использована для создания таблицы переходов, является возможность получения очень компактного кода. А запись длинных строк, начиная с версии   1.20,  перестала  быть  непреодолимым  препятствием.  Теперь  длинные  строки  с   метками  прекрасно переносятся с помощью символа “_”.

‘————————————-

‘программа записи константы в зависимости от шага калибровки из Flda Wr_c_sc:

On Ranga Goto Ws0 , Ws1 , Ws2 , Ws3 , Ws4 , Ws5 , Ws6 , Ws7 , _ Ws8 , Ws9 , Ws10 , Ws11 , Ws12 , Ws13 , Ws14

‘установить адрес записи и подготовить данные для записи

Ws0:   Wadr = Varptr(dc  2mv) : Goto Wsend   ‘масштаб предела 0.2 мВ Ws1:   Wadr = Varptr(dc  5mv) : Goto Wsend   ‘масштаб предела 0.5 мВ Ws2:   Wadr = Varptr(dc_1mv) : Goto Wsend    ‘масштаб предела 1 мВ Ws3:   Wadr = Varptr(dc_2mv) : Goto Wsend    ‘масштаб предела 2 мВ Ws4:   Wadr = Varptr(dc_5mv) : Goto Wsend    ‘масштаб предела 5 мВ Ws5:   Wadr = Varptr(dc_10mv) : Goto Wsend   ‘масштаб предела 10 мВ Ws6:   Wadr = Varptr(dc_20mv) : Goto Wsend   ‘масштаб предела 20 мВ Ws7:   Wadr = Varptr(dc_50mv) : Goto Wsend   ‘масштаб предела 50 мВ

Ws8:   Wadr = Varptr(dc_100mv) : Goto Wsend  ‘масштаб предела 100 мВ Ws9:   Wadr = Varptr(dc_200mv) : Goto Wsend  ‘масштаб предела 200 мВ Ws10:  Wadr = Varptr(dc_500mv) : Goto Wsend  ‘масштаб предела 500 мВ Ws11:  Wadr = Varptr(dc_1v)                  ‘масштаб предела 1 В Wsend: Flda = Rscl : Goto Wr_cal_c

‘————————————-

Другой особенностью приведенного примера является то, что он показывает каким  образом записать в таблицу  и  использовать  в  программе  адрес переменной  определенной  Bascom.  Переменной  Wadr  передается значение адреса другой переменной с помощью оператора  Varptr(). Попытка это же действие записать в более привычном виде (пример ниже) будет  неудачной, так как Bascom не может помещать в поля данных значения символьных определений.

‘————————————- ‘ это не работающая программа!!!

Wadr = Lookup(ranga , Tab) Tab:

Data Dc_2mv%      ‘здесь компилятор записывает 0000h Data Dc_5mv%

Data Dc_10mv%

Data Dc_20mv% Data Dc_50mv% Data Dc_100mv% Data Dc_200mv% Data Dc_500mv% Data Dc_1v%

7.6      Конструкция ON var GOTO … , … , … предпочтительней конструкции SELECT CASE var … … … (дает  более  компактный  код)  даже  если  вы  используйте  всего  два-три  из  шестнадцати-двадцати  возможных значений  тестируемой  переменной  var.  Конструкцию  SELECT  CASE  нужно  применять  тогда,  когда  выбор действительно  велик  или  число   значений,  принимаемое  переменной,  не  ограничено.  Ниже  дается  пример использования  конструкции ON … GOTO при обработке кнопок. В тех случаях, когда кнопку обрабатывать  не требуется, указан переход на метку-«заглушку» (“Mcz_mes”). Еще раз напомним, в данном примере переменная Buf_kl может иметь значения только от 0 до 16.

‘————————————- ‘обработка кнопок при калибровке нуля Comzkl:

On Buf_kl Goto Mcz_mes , Mcz_mes , To_next_step , To_prev_step , End_of_c , _

Mcz_mes

,

Mcz_mes

,

Mcz_mes

,

Mcz_mes , _

Mcz_mes

,

Mcz_mes

,

Mcz_mes

,

Mcz_mes , _

Mcz_mes

,

Mcz_mes

,

Mcz_mes

,

Mcz_mes

‘————————————-

7.7  Упаковывайте в подпрограммы все повторяющиеся фрагменты даже если это всего одна операции. В

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

Ua = Ub + Uc или Ua = Ua + 10.123

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

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

Восьмой совет (переменные).

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

дополнительной памятью – сегодня их очень много – это все модели ряда 8xC51Rx с ОЗУ от 512 до 1280 байт.

8.2     Если в системе имеется расширенная память, располагайте в ней переменные, используемые реже или те, которые всегда адресуются с помощью индексного регистра (строковые и массивы). Переменные типа Single также можно располагать во внешней памяти – это не отразится на производительности (все равно их обработка занимает много времени).  Однако нужно быть внимательным при использовании этих переменных – наверно, имеются операторы не работающие (или неправильно работающие) с данными во внешней памяти. Переменные типа Byte, Word, Integer и Long нужно располагать в основной памяти и ниже 7fh.

Девятый совет (о стеке).

9.1     Оставляйте больше места для стека. Большие программы всегда предполагают большую  вложенность

подпрограмм. Минимальный размер стека 32 байта, а безопасный составляет 48 байт (когда стек  используется

обычным образом). Если применены прерывания, добавьте еще. Проверяйте после компиляции, сколько осталось после размещения всех переменных.

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

следующий маленький фрагмент:

Clr_mem:

$asm

Mov R0 , #&hff

Mov @r0 , Sp           ‘запомнить значение стека в самой верхней ячейке

Dec R0

Clrmem:

Mov @r0 , #0           ‘очистка памяти

Djnz R0 , Clrmem

$end Asm

Теперь в нужном месте всегда можно восстановить значение стека, назначенное компилятором:

$asm

Mov R0 , #&hff

Mov Sp , @r0                          ‘восстановить значение стека

$end Asm

Обе программы написаны на ассемблере, чтобы полностью контролировать этот процесс.

Десятый совет (о проверке условий).

10.1      Не применяйте сложные конструкции проверки условий. Во-первых, это не сокращает размер кода

(напротив, увеличивает по сравнению с простыми проверками). Рекомендуемый максимум –  конструкция If … Then … Else … End If. Лучше пусть будет больше простых  операторов проверки условий – будет меньше ошибок ваших и компилятора.

10.2     Не применяйте вложенные конструкции проверки условий. Это также не сокращает размер кода, а только загружает стек. Если очень нужно, применяйте конструкцию If … Then … Elseif … Then … End If).

10.3  Пишите программу так, чтобы все проверяемое было по возможности переменными типа Bit, Byte и

Single.

10.4  Анализ строковых переменных сводите к проверке значения одного символа.

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

а потом проверяйте.

10.6      Никогда не проверяйте на равенство значение числа записанного в формате с  плавающей точкой (типа Single). Числа в этом формате записаны приближенно. Поэтому два одинаковых числа, полученных разным способом, скорее всего не равны.

Одиннадцатый совет (о сложных конструкциях).

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

Бейсика, так как сокращают размер исходного текста. В современной версии все специальные конструкции легко

строятся с помощью операторов проверки условий и перехода. Простые конструкции занимают  больше места только  в  исходном  тексте,  а  в  коде  они  короче.  Кроме  того,  меньше  используется  стек  и  дополнительные внутренние переменные, которые занимает компилятор. Имеются в виду конструкции типа: Do … Loop Until … , While … Wend и даже For … Next. Громоздкой код дает конструкция Select Case … , практически не уступающий цепочки конструкций типа If … Then … .

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

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

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

работы.

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

Двенадцатый совет (о передачи значений адресов).  Используйте оператор Restore и функцию Varptr()

для считывания значений адресов переменных и меток полученных при компиляции.

При программировании с использованием Bascom возникают трудности записи в поля непосредственных данных адресов, определяемых при компиляции. При записи метки или  символьного имени переменной в поле

данных (например, при создании таблиц) компилятор возвращает в коде нулевые значения. Предлагаемый способ передачи программе значений адресов, полученных при компиляции, демонстрирует ниже следующая программа. Dim X As Byte , Y As Byte , W As Word

$Ramstart = &H1000

Dim Var1 As XRAM Single

W = Varptr(Var1) ‘считаем адрес переменной

Print W          ‘ “4096” – адрес переменной

M1:

Restore M1       ‘считаем адрес метки

Gosub Read_restore

Print W          ‘ “240” – адрес метки M1

End

‘———–

‘подпрограмма считывания адреса, записанного оператором Restore Read_restore:

X = Peek(&H47) : Y = Peek(&H48)    ‘&h47 = (SP)-2, &H48 = (SP)-1

W = Makeint(x , Y)

Return

Оператор  Restore  записывает  значение  указанной  метки  во  внутреннюю  переменную  (типа  Word), расположение которой нужно определить в отладчике. В компиляторе версии 2.хх она располагается под стеком, вычисленным компилятором (ее адрес = (SP) – 2 ). Это значение можно посмотреть в файле отчета о компиляции (вызывается кнопками “Ctrl+W”). Можно попробовать применять и следующую конструкцию.

‘вариант подпрограммы считывания адреса, записанного оператором Restore

$Asm

Pop Acc            ‘взять из-под стека

Mov {W + 1} , A Pop Acc

Mov {W} , A Inc SP

Inc SP

$End Asm

Функция Varptr() сразу записывает адрес переменной. Объем получаемого кода от  подобных действий очень мал (6-20 байт), поэтому их можно применять в программе много раз и, даже создавать таблицы их этих операторов.

Источник: М.Л.Кулиш, СПРАВОЧНИК ПО ПРОГРАММИРОВАНИЮ BASCOM-8051, Краснодар 2001

По теме:

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