Глава 7 Принципы создания программ для микроконтроллеров

We use cookies. Read the Privacy and Cookie Policy

В предыдущих главах были рассмотрены основы построения микропроцессорных систем, подробно описано внутреннее устройство самого распространенного семейства микроконтроллеров — MCS-51. Однако одновременно выяснилось, что для разработки устройства на основе микроконтроллера создания одной только принципиальной схемы недостаточно. Кроме того, требуется разработать и занести во внутреннюю память микроконтроллера программу. Именно она вместе с аппаратурой микроконтроллера и обеспечивает функционирование проектируемого устройства.

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

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

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

Все современные программы пишутся на каких-либо языках программирования, поэтому начнем главу с обзора существующих языков программирования и программ-трансляторов этих языков.

Языки программирования для микроконтроллеров

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

— языки программирования «низкого» уровня;

— языки программирования «высокого» уровня.

Классификация языков программирования приведена на рис. 7.1.

Рис. 7.1. Классификация языков программирования

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

Языки программирования «высокого» уровня позволяют заменять один оператор несколькими машинными командами. Это увеличивает производительность труда программистов. Кроме того, языки «высокого» уровня позволяют писать программы, которые могут выполняться на различных микропроцессорах. (Естественно, что при этом необходимо использовать программы-трансляторы для соответствующего процессора.) В настоящее время для микроконтроллеров широко используются такие языки программирования высокого уровня, как С и PLM.

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

В тех случаях, когда объем ОЗУ и ПЗУ мал (в районе нескольких килобайтов), альтернативы ассемблеру нет. Именно этот язык программирования позволяет получать самый короткий и самый быстродействующий код программы (при прочих равных условиях, т. к. испортить можно все!).

Языки программирования высокого уровня позволяют значительно сократить время создания программы, но при этом увеличивается ее размер, поэтому использование такого языка программирования для микропроцессорных систем требует достаточно большого объема памяти программ (несколько десятков килобайтов). Увеличение объема программы связано с несколькими факторами:

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

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

3. Программист не использует подпрограммы там, где они могли бы сократить объем программы, т. к. на языке программирования высокого уровня это всего один или несколько операторов.

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

Виды программ-трансляторов

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

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

Применение интерпретатора может обеспечить выигрыш только в случае его разработки для языка программирования «высокого» уровня. В этом случае может быть сэкономлена внутренняя память программ, а также облегчен процесс отладки (при применении языка программирования BASIC) или облегчен перенос программ с одного типа процессора на другой (в случае использования языка программирования Java).

При программировании на ассемблере применение интерпретатора приводит к проигрышу по всем параметрам, поэтому для языков программирования низкого уровня применяются только программы-компиляторы.

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

Виды компиляторов

Программы-компиляторы бывают оценочные и профессиональные.

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

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

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

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

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

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

Оценочные компиляторы обычно предлагаются бесплатно фирмами — производителями микроконтроллеров.

Профессиональные компиляторы разрабатываются и продаются независимыми фирмами — разработчиками программного обеспечения. Для микроконтроллеров семейства MCS-51 получили известность продукты таких фирм, как FRANKLIN, IAR, KEIL и др.

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

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

Применение подпрограмм

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

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

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

Описанную ситуацию иллюстрирует рис. 7.2. На нем изображено адресное пространство микроконтроллера. Младшие адреса адресного пространства находятся в нижней части рисунка.

Рис. 7.2. Вызов подпрограммы и возврат к выполнению основной программы

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

Для обращения к подпрограмме и возврата из нее в систему команд микропроцессоров вводят специальные команды. В микроконтроллерах семейства MCS-51 это команды LCALL, ACALL для вызова подпрограммы и команда RET для возврата из подпрограммы. Команды вызова не только осуществляют передачу управления на указанный адрес, но и запоминают адрес команды, следующей за вызовом подпрограммы — адрес возврата. Команда возврата из подпрограммы передает управление по адресу возврата, которой был запомнен при вызове подпрограммы.

Пример вызова подпрограммы и ее реализации на языке программирования ASM-51 приведен на листинге 7.1.

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

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

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

Стек, его организация и структура

Адреса возврата из подпрограмм хранятся в области памяти, называемой стеком. Логически доступ к этим ячейкам памяти организован так, чтобы считывание последнего записанного адреса возврата производилось первым, а первого — последним. Логическая организация стека пояснена на рис. 7.3. Адресация ячеек стека осуществляется с использованием специального регистра, называемого указателем стека, SP. Ячейка памяти, в которую в данный момент может быть записан адрес возврата из подпрограммы, называется вершиной стека. Ее адрес всегда хранится в указателе стека. Количество ячеек памяти, выделенных для стека, называется глубиной стека. Последняя ячейка стека, в которую можно производить запись, называется дном стека.

Рис. 7.3. Организация стека в памяти данных микропроцессора

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

Например, в микроконтроллерах семейства MCS-51 при занесении информации в стек содержимое указателя стека увеличивается (стек растет вверх), поэтому стек размещается в самой верхней части памяти данных.

Для того чтобы установить глубину стека 28 байт, необходимо вычесть из адреса максимальной ячейки внутренней памяти микроконтроллера глубину стека и записать полученное значение в указатель стека SP:

DnoSteka EQU 127            ; для микроконтроллера АТ89с51 размер внутренней памяти равен 128 байтам

MOV SP, #DnoSteka — 28   ;Установить глубину стека 28 байт

Кроме значения программного счетчика, часто требуется запоминать содержимое внутренних регистров и флагов процессора, локальных переменных подпрограммы. Стек оказался удобным средством и для решения этих задач. Сохранение локальных переменных в стеке позволило осуществлять вызов подпрограммы самой из себя (реализовывать рекурсивные алгоритмы). Для работы со стеком в систему команд микропроцессоров введены специальные команды. У микроконтроллеров семейства MCS-51 это команды PUSH и POP. Их использование показано в листинге 7.2.

Подпрограммы-процедуры и подпрограммы-функции

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

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

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

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

Параметры подпрограммы — это локальные переменные подпрограммы, начальные значения которым присваиваются в вызывающей программе или подпрограмме. В алгоритмическом языке С-51 параметры подпрограммы записываются в скобках после ее имени. Пример вызова такой подпрограммы представлен в листинге 7.4.

Сравните с программой, использующей глобальные переменные (см. листинг 7.3). Как, по-вашему, какая из программ обладает большей наглядностью? В подпрограмму можно передавать и значительные объемы данных, например, строки или массивы данных:

PeredatStroky("Напечатать строку");

Естественно, что в этом случае сама подпрограмма PeredatStroky должна быть написана несколько иначе, чем в примерах на листингах 7.3 или 7.4. Здесь потребуется применение переменных-указателей, которые будут рассмотрены позднее.

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

Подпрограмма-функция — это подпрограмма, которая возвращает вычисленное значение. Пример использования подпрограммы-функции:

Y = sin (x);

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

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

Применение комментариев

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

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

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

Пример использования комментариев приведен в листинге 7.5.

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

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

Структурное программирование

Программирование для универсальных компьютеров начиналось с использования машинных кодов, затем появились языки высокого уровня. Позже были развиты сначала принципы структурного программирования, а потом — объектно-ориентированное программирование. В настоящее время активно развивается визуальное программирование.

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

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

Применение структурного программирования позволяет увеличить скорость написания программ и облегчить их отладку за счет сокращения количества доступных программных конструкций. Поэтому в начале семидесятых годов был разработан ряд языков высокого уровня, ориентированных на структурное программирование. Это такие языки, как С, PASCAL, ADA. Однако не надо думать, что структурное программирование возможно только на этих языках, для этого пригодны и такие языки, как ассемблер или FORTRAN, где не предусмотрено структурных операторов.

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

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

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

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

Выполняемое алгоритмическое действие обычно отображается в названии подпрограммы. Это значительно повышает наглядность программы и тем самым скорость ее написания и отладки. Пример такого подхода показан в листинге 7.6.

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

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

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

Например, для микроконтроллера at89c5l можно назначить флагу завершения приема по последовательному порту R1 имя BytePeredan. Тогда участок программы на языке программирования С-51, соответствующий ожиданию завершения приема байта последовательным портом, будет выглядеть следующим образом:

if (BytePeredan) SBUF = SledujushchiyByte;

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

Применение подпрограмм является не только средством структурирования программы, но и существенно сокращает размер машинного кода, если подпрограммы многократно используются. Однако необходимо отметить, что при использовании подпрограмм несколько уменьшается быстродействие устройства. В случае если для разрабатываемого устройства это недопустимо, вместо подпрограмм можно использовать макросы, в названии которых точно так же будет отображаться выполняемое действие. Макрос, в отличие от подпрограммы, не передает управление в отдельную область памяти с последующим возвратом к следующему за вызовом подпрограммы оператору, а вызывает повторную подстановку одних и тех же команд столько раз, сколько в тексте программы встретилось имя макроса. Для образования макроса в программе на языке программирования С-51 достаточно объявить подпрограмму с префиксом inline.

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

Линейная цепочка операторов

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

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

Блок-схема стандартной структурной конструкции управления «линейная цепочка операторов» приведена на рис. 7.4. Поясним процесс преобразования этой блок-схемы в исходный текст программы.

Рис. 7.4. Блок-схема конструкции управления «линейная цепочка операторов»

На момент написания алгоритма (и программы) верхнего уровня нам не известны детали их реализации, поэтому вместо настоящих подпрограмм, соответствующих элементам блок-схемы «Действие 1» и «Действие 2», поставим подпрограммы-заглушки, которые ничего не делают! Однако в именах подпрограмм отразим те алгоритмические действия, которые они должны осуществлять в дальнейшем. Для отладки программы (и алгоритма) того уровня, который пишется в данный момент, неважно, что подпрограммы ничего не делают. Главное, что мы можем проверить, каков порядок вызовов подпрограмм, и соответствует ли он ожидаемому. На этом же этапе можно отладить взаимодействие между программами.

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

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

Пример использования подпрограмм-заглушек для реализации конструкции управления «линейная цепочка операторов» на языке программирования С-51 приведен в листинге 7.7, а на языке программирования ASM-51 — в листинге 7.8.

Как видно из приведенного в листинге 7.7 исходного текста программы, для организации подпрограммы-заглушки на языке программирования С-51 достаточно определить подпрограмму, не помещая в ее тело ни одного оператора. При этом объявление подпрограммы-заглушки должно быть точно таким же, как у подпрограммы в готовой написанной и отлаженной программе.

На языке программирования ASM-51 имя подпрограммы совпадает с меткой в начале подпрограммы. Однако, в отличие от языков программирования высокого уровня, для организации подпрограммы-заглушки один исполняемый оператор все-таки нужен. Это оператор возвращения из подпрограммы ret. И еще одно замечание. При программировании на языке ассемблера первый написанный оператор и выполняется первым.

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

Условное выполнение операторов

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

Рис. 7.5. Блок-схема конструкции управления «условное выполнение операторов»

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

В структурированных языках программирования второе плечо условного оператора записывается после зарезервированного слова «else».

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

Пример реализации конструкции управления «условное выполнение операторов» на языке программирования ASM-51 приведен в листинге 7.10. Язык программирования ASM-51 не является структурированным, поэтому для реализации конструкции условного выполнения операторов приходится использовать несколько команд микропроцессора (операторов языка программирования ассемблер).

Для реализации блока «логическое выражение», изображенного на рис. 7.5 в виде ромба, можно использовать любую команду условного перехода, входящую в систему команд микропроцессора. При этом в простейшем случае логическое выражение сводится к анализу сигналов на выводах микроконтроллера или его внутренних флагов. Но в отличие от двумерной блок-схемы, память программ микропроцессора одномерна, т. е. все команды располагаются друг за другом. Для того чтобы выполнялся только один из операторов («Действие 1» или «Действие 2»), необходимо после выполнения одного из них обойти другой. Это можно осуществить при помощи команды безусловного перехода.

Размещение различных частей конструкции управления «условное выполнение операторов» в памяти программ микропроцессора приведены на рис. 7.6. Направления возможных переходов при ее выполнении показаны на этом же рисунке. При использовании такой конструкции будет выполнены только один из операторов. Какой — зависит от результатов выполнения условного выражения.

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

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

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

Блок-схема и примеры реализации конструкции управления условным выполнением одного оператора на языках программирования С-51 и ASM-51 приведены на рис. 7.7 и в листингах 7.11 и 7.12. Каждый из исходных текстов содержит очень подробные комментарии, поэтому дополнительные пояснения не понадобятся.

Рис. 7.7. Блок-схема конструкции управления условным выполнением одного оператора

Конструкция управления циклическим выполнением оператора с проверкой условия после тела цикла

Третья конструкция управления — это циклическое выполнение оператора с проверкой условия после тела цикла. Оператор (или операторы), который должен повторяться в процессе выполнения этой конструкции, называется телом цикла. В процессе выполнения этих операторов обычно модифицируется некоторая переменная, значение которой влияет на завершение цикла. Эта переменная получила название «параметр цикла».

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

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

Рис. 7.8. Блок-схема конструкции управления циклическим выполнением оператора с проверкой условия после тела цикла

На языках программирования высокого уровня такая конструкция входит в состав языка (оператор do… while в языке программирования С или оператор repeat… until в языке программирования PASCAL). Пример использования циклического выполнения оператора с проверкой условия после тела цикла на языке программирования С приведен в листинге 7.13. Обратите внимание, что в приведенном примере в качестве логического выражения использована константа — единица. Это приводит к бесконечному циклу, эквивалентному безусловной передаче управления на начало тела цикла при помощи ассемблерной команды jmp.

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

Отличие заключается в том, что передача управления в команде условного перехода осуществляется не вперед, как при условном выполнении оператора, а назад, на начало цикла. В системе команд микроконтроллеров семейства MCS-51 для реализации цикла с проверкой условия после тела цикла введена специальная команда — djnz. Она позволяет реализовать сразу два алгоритмических действия: вычитания единицы из параметра цикла и проверку содержимого параметра цикла на равенство нулю. Для обозначения начала тела цикла в программе на языке программирования ассемблер применяется метка.

Пример реализации оператора цикла с проверкой условия после тела цикла на языке программирования ASM-51 приведен в листинге 7.14. В этом примере предполагается опрос бита завершения приема байта последовательным портом RI, который объявлен где-то в тексте программы как переменная PrinjatByte.

Структурная конструкция циклического выполнения оператора с проверкой условия до тела цикла

Четвертая структурная конструкция управления — это цикл с проверкой условия до тела цикла. В отличие от предыдущего оператора тело цикла в этом операторе может ни разу не выполниться, если условие завершения цикла сразу же выполнено. Блок-схема цикла с проверкой условия до тела цикла приведена на рис. 7.9.

Рис. 7.9. Блок-схема структурной конструкции управления «циклическое выполнение оператора с проверкой условия до тела цикла»

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

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

Пример реализации цикла на языке программирования ASM-51 приведен в листинге 7.16. Эту конструкцию, как и условное выполнение операторов, невозможно реализовать при помощи одной машинной команды, поэтому реализуем его при помощи команд условного и безусловного перехода. На этот раз команда безусловного перехода помещается в конец конструкции и осуществляет переход на команду проверки условия, т. е. на начало конструкции. Команда безусловного перехода sjmp передает управление на начало цикла после выполнения его тела. Расположенная за командой безусловного перехода метка Koncykia обозначает команду, на которую передается управление, когда прекращается выполнение цикла.

Понятие многофайлового и многомодульного программирования

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

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

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

Многофайловые программы

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

В языке программирования ASM-51 это директива include. Точно так же, только буквами нижнего регистра записывается эта директива и в языке программирования С.

При использовании директивы include в исходный текст программы добавляется содержимое включаемого файла, и только после этого производится трансляция исходного текста в исполняемый код программы. Иными словами, содержимое главного файла программы и включаемых в него файлов объединяются препроцессором транслятора во временном файле, и только после этого производится трансляция полученного временного файла в исполняемый код микроконтроллера. Пример использования директивы включения текстового файла в программе на языке программирования С-51 приведен в листинге 7.17.

В отдельные файлы выделяются, как правило, описания внутренних регистров микроконтроллера и переменных, связанных с выводами микросхемы микроконтроллера. Рассмотрим для примера содержимое включаемых файлов, имена которых использованы в примере, приведенном в листинге 7.17. Фрагменты исходных текстов этих файлов приведены в листингах 7.18 и 7.19[1].

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

В файле REG51.h объявляются переменные, связанные с регистрами специального назначения микроконтроллера 89с51. Они должны использоваться в любой программе, работающей с микроконтроллером 89с51. Не имеет смысла повторять эти объявления в каждой программе — их можно выделить в отдельный файл, что и сделано в приведенном в листинге 7.19 примере. Более того, файлы объявления регистров специальных функций для различных типов микроконтроллеров обычно поставляются разработчиками компиляторов для исключения ошибок.

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

В результате этой предварительной обработки программы транслятор формирует временный файл. Например, в результате выполнения участка программы, приведенного в листинге 7.17, будет сформирован временный файл, содержание которого приведено в листинге 7.20. Именно этот файл и будет преобразован в машинные коды микроконтроллера, которые будут сохранены на жестком диске компьютера в виде загрузочного файла.

Подведем итоги. При многофайловом программировании:

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

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

— разбираться с короткими файлами, реализующими одну или несколько связанных между собой функций, намного легче, чем работать с одним большим файлом;

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

Разбивать на несколько файлов можно не только программы, исходный код которых разработан на языках высокого уровня. Включение файлов можно использовать и в программах на языке ассемблера. В листинге 7.21 приведен пример использования включения файлов в программе на языке ASM-51.

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

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

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

Еще один файл, который обычно включается в состав программного проекта, — это файл листинга компилятора, в который помещается исходный текст программы, машинные коды в текстовом виде и сообщения об ошибках. Этот файл облегчает поиск и исправление синтаксических ошибок.[2]

Многомодульные программы

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

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

В связи с этим значительные затраты времени на компиляцию представляются необоснованно большими;

— максимальное количество имен переменных и меток бывает ограничено программой-транслятором и может быть исчерпано при написании исходного текста программы, если он достаточно велик;

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

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

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

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

Создание загрузочного файла программы при раздельной трансляции производится с использованием двух программ: транслятора исходного текста и редактора связей объектных файлов.

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

Вот как выглядит вызов редактора связей из командной строки DOS для объединения трех модулей:

rl51.exe progr.obj, modull.obj, modul2.obj

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

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

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

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

Для ссылки на переменную или метку из другого программного модуля служит директива EXTRN. В ней перечисляются через запятую метки и переменные, точное значение которых редактор связей должен получить из другого модуля и модифицировать все команды, в которых эти метки или переменные используются. Пример применения директивы EXTRN в программе на языке программирования ASM-51:

EXTRN DATA (Buflnd, ERR)

EXTRN CODE (Podprogr)

Для того чтобы редактор связей мог осуществить связывание модулей в единую программу, переменные и метки, объявленные, по крайней мере, в одном из модулей как EXTRN, в другом модуле должны быть объявлены как доступные для всех модулей при помощи директивы PUBLIC. Пример использования директивы public на языке программирования ASM-51:

PUBLIC Buflnd, Parametr

PUBLIC Podprogr,?Podprogr?Byte

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

Из всего рассмотренного ранее понятно, что большую программу можно разделить на части. Остается открытым вопрос — как разделять единую программу на части, ведь это же цельный организм, который живет и развивается по мере разработки программы! Как найти часть программы, наименьшим образом связанную с остальными частями? А искать особенно и не нужно! Ведь у нас уже есть такие части, которые связаны с остальной программой либо одним, либо несколькими заранее оговоренными и неизменными параметрами! Это подпрограммы.

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

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

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

Программа-монитор

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

Рассмотрим программу для микроконтроллера, которая будет выполнять поставленные задачи. Назовем эту программу «монитор», поскольку она «наблюдает» за использованием ресурсов системы. Схема алгоритма программы-монитора приведена на рис. 7.10.

Рис. 7.10. Схема алгоритма программы-монитора

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

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

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

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

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

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

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

Рассмотрим подробнее, как реализуется чтение информации с выводов микроконтроллера. Очень важно, чтобы сигналы со всех выводов вались одновременно, иначе можно получить комбинации битов, не соответствующие действительности. Иллюстрация такой ситуации приведена на рис. 7.11. В результате опроса сигналов в контроллер будет введен код 000, который никогда не присутствовал на выводах микросхемы!

Рис. 7.11. Пример неодновременного опроса сигналов на выводах микроконтроллера

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

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

Рис. 7.12. Схема подключения клавиатуры к микроконтроллеру

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

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

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

MOV SkanCode, P1.

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

Сначала определим, каким сигналом будем осуществлять сканирование. Для этого нужно вспомнить внутреннее устройство порта. Обратите внимание, что в качестве примера выбран микроконтроллер семейства MCS-51! При использовании других микроконтроллеров принципиальная схема и сигналы опроса могут оказаться другими! Путь протекания тока через замкнутую кнопку клавиатуры и элементы порта для микроконтроллеров семейства MCS-51 показан на эквивалентной схеме двух разрядов порта Р1 (рис. 7.13).

Рис. 7.13. Эквивалентная схема цепи протекания тока через замкнутую кнопку клавиатуры

Из приведенной на рис. 7.13 эквивалентной схемы видно, что для опроса колонки кнопок необходимо открыть нижний транзистор порта, подключенного к выбранной колонке. При этом нужно обеспечить запирание транзисторов, подключенных к остальным колонкам кнопок. Это позволит исключить неоднозначность определения номера нажатой кнопки. Для открывания транзистора достаточно записать в соответствующий бит порта логический 0, а для запирания — логическую 1. В результате коды опроса клавиатуры для схемы, приведенной на рис. 7.12, будут выглядеть следующим образом:

— код опроса первой колонки клавиатуры — 11110111b;

— код опроса второй колонки клавиатуры — 11111011b;

— код опроса третьей колонки клавиатуры — 11111101b;

— код опроса четвертой колонки клавиатуры — 11111110b.

То есть для опроса состояния кнопок клавиатуры потребуется выдача на порт Р1, по крайней мере, четырех кодов. При этом следует отметить, что скан-код, считанный с клавиатуры, содержит намного больше информации, чем просто номер замкнутого контакта. Например, при помощи скан-кода можно определить нажатие нескольких кнопок одновременно, поэтому лучше хранить во внутренней памяти контроллера непосредственно скан-код, а не готовый номер кнопки. Пример подпрограммы опроса клавиатуры приведен в листинге 7.23.

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

Эта же подпрограмма может быть реализована и на языке программирования ассемблер. При этом она практически не будет отличаться от подпрограммы, приведенной выше (см. листинг 7.23). Пример реализации опроса клавиатуры на языке программирования ASM-51 приведен в листинге 7.24.

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

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

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

Рассмотрим подробнее механизм возникновения явления дребезга контактов. Его причиной является упругость самих контактов. При нажатии на кнопку или при срабатывании какого-либо датчика контакты испытывают механическое импульсное воздействие. Обладая некоторой упругостью и массой, они начинают совершать колебательное движение: один контакт ударяется о другой (замыкание) и снова отходит (размыкание). Колебательный процесс продолжается некоторое время. При этом количество замыканий и размыканий контактов случайно и зависит от механических свойств контактов и механической силы, воздействующей на эти контакты. Обычно этот процесс длится от одной до восьми миллисекунд.

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

Пример временной диаграммы сигнала на контактах кнопки приведен на рис. 7.14. На временной оси этого рисунка моменты считывания сигналов показаны рисками. Внизу рисунка обозначены номера временных слотов. На приведенной временной диаграмме четко просматривается зона дребезга контактов.

Для иллюстрации эффекта подавления дребезга контактов за счет ввода информации в строго определенные моменты времени на этой временной диаграмме выбран наихудший случай момента взятия отсчета. Этот момент совпадает с зоной дребезга контактов. При этом на выводе порта микроконтроллера может быть считан сигнал логического нуля или логической единицы. Но даже в этом случае дополнительный импульс в считанном сигнале не возникает! В случае считывания в момент дребезга контактов логического нуля сигнал, введенный в микроконтроллер, будет выглядеть как показано на рис. 7.14 сплошной линией, а в случае считывания логической единицы — пунктиром. Однако вне зависимости от того, какое значение сигнала мы считаем — нулевое или единичное, переход будет только один.

Рис. 7.14. Временные диаграммы напряжения на контактах кнопки и сигнала, введенного в микроконтроллер

Дребезг контактов приводит только к неопределенности обнаружения момента нажатия кнопки, которая не превышает периода опроса клавиатуры. Выберем это время. Так как мы уже определили, что время дребезга контактов не превышает 8 мс, то можно производить опрос портов, к которым подключены механические контакты, с периодом, который несколько больше, например, 10 мс. Это время и будет временем реакции системы. Но как обеспечить периодический опрос клавиатуры? Ведь программа в процессе выполнения может проходить по различным путям в зависимости от состояния опрашиваемых контактов и содержимого внутренних переменных микроконтроллера! В результате время выполнения программы (тела рабочего цикла) будет случайным. Для того чтобы время прохождения программы по циклу было строго фиксированным, можно воспользоваться таймером.

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

Рис. 7.15. Соотношение времени выполнения рабочей части цикла и ожидания срабатывания таймера

Для организации периодического опроса клавиатуры требуется усложнить подпрограмму инициализации. Теперь в ней требуется настроить таймер на выбранный период времени, а значит задать и его режим работы. Выберем для задания равных промежутков времени таймер Т0. Он может обеспечить задание 10-мс промежутка времени только в режиме 16-разрядного таймера. Подпрограмма инициализации микроконтроллера, выполняющая настройку таймера, приведена в листинге. 7.25.

В приведенном примере предполагается, что микроконтроллер работает с частотой тактового генератора 12 МГц. В этом случае на вход таймера Т0 будут поступать импульсы с периодом 1 мкс. Число, которое необходимо загружать в таймер, можно найти как отношение требуемого интервала времени, 10 мс, и периода импульсов на входе таймера, 1 мкс.

Так как таймер Т0 суммирующий, то в регистры таймера будем загружать отрицательное число с абсолютным значением, равным требуемому отношению. В приведенном участке программы (см. листинг 7.25) для выделения старшего байта из 16-разрядного числа использована функция сдвига этого числа на 8 разрядов вправо.

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

Рис. 7.16. Эквивалентная схема устройства, реализованного программой, приведенной в листинге 7.26

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

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

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

В программе, приведенной в листинге 7.26, процессор все время потребляет максимальный ток, определяемый тактовой частотой микроконтроллера. Чем выше тактовая частота — тем больше ток. Максимальный ток потребляется даже при ожидании срабатывания таймера. В то же время в режиме ожидания микроконтроллер можно перевести в режим пониженного энергопотребления. Это делается записью соответствующего бита в регистр SCON. Выйти из этого режима и продолжить выполнение программы микроконтроллер сможет только по прерыванию от таймера.

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

В этой подпрограмме флаг переполнения таймера Т0 не сбрасывается, т. к. после выполнения сброса микроконтроллера этот флаг и так содержит нулевое значение. Разрешение прерываний осуществляется командой записи в регистр ie соответствующего числа. Для разрешения прерываний от таймера Т0 достаточно записать единицу в первый бит этого регистра. Кроме того, для разрешения прерываний необходимо записать единицу в седьмой бит регистра IE, разрешающий или запрещающий все прерывания.

Для тех, кто еще не привык считать в двоичной арифметике и легко переводить двоичные числа в шестнадцатеричные и обратно, в этом примере приведен способ вычисления констант с помощью операции сдвига. Известно, что 20 =1. Тогда операция двоичного сдвига (1 << 5) вычислит константу 25. Нам надо записать единицу в два бита. Необходимую константу можно образовать при помощи операции логического сложения «|». Не нужно пугаться довольно сложного выражения для вычисления константы, записываемой в IE. Оно вычисляется только один раз, на этапе трансляции программы в машинные коды микроконтроллера. В разрабатываемую программу будет помещен готовый результат вычислений. При выполнении программы значение константы уже известно.

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

Теперь при переполнении таймера будут возникать прерывания, которые будут передавать управление программой на вектор прерывания. Нам пока ничего не нужно делать по прерыванию, но, тем не менее, подпрограмму обслуживания прерывания для того, чтобы вернуться из прерывания в основную программу, необходимо написать. Пример такой подпрограммы на языке программирования С-51 приведен в листинге 7.28.

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

Теперь все подготовительные операции выполнены, и можно осуществить режим пониженного потребления тока микроконтроллера. У микроконтроллеров семейства MCS-51 есть два режима энергопотребления: остановка процессорного ядра и остановка задающего генератора. В нашем случае останавливать задающий генератор ни в коем случае нельзя, т. к. при этом остановится таймер, и микроконтроллер можно будет разбудить только при помощи аппаратного сброса. Остается только режим остановки процессорного ядра.

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

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

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

Использование таймера для организации параллельных программных потоков

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

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

Рис. 7.17. Схема устройства, реализующая разное время реакции на входные воздействия

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

На временной диаграмме рис. 7.18 общее время выполнения программы (максимальное допустимое время реакции системы) разбито на шесть временных слотов. При этом первый, третий и пятый временные слоты выделены для второго монитора, что обеспечивает время реакции устройства t2, реализуемое этим монитором, не более 3,3 мс. Это будет один программный поток.

Для первой подпрограммы-монитора с временем реакции на входное воздействие t1 выделен четвертый временной слот. Если времени одного слота для выполнения монитора 1 недостаточно, то есть еще два свободных временных слота. Подпрограмма-монитор 1 может быть разбита на три части, каждая из которых будет вызываться в своем временном слоте. При этом максимальное время реакции на входное событие у монитора 1 составит t1 = 10 мс.

Рис. 7.18. Пример временной диаграммы работы микроконтроллера с двумя подпрограммами-мониторами

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

Теперь рассмотрим, как можно реализовать приведенные выше принципы организации программы на языке программирования С-51. Исходный текст программы приведен в листинге 7.30. Для нумерации слотов, на которые разбивается время, используется глобальная переменная NomSlot. Эта переменная используется как счетчик временных слотов. Именно по ее значению вызывается одна из подпрограмм-мониторов. Подпрограмма инициализации микроконтроллера такая же, как в примере, приведенном в листинге 7.27, и поэтому сейчас рассматриваться не будет.

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

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

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

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

Наиболее ярким примером источника событий, наступающих в произвольный момент времени, является последовательный порт. Обычно через него принимаются или передаются многобайтные команды или пакеты данных. Рассмотрим пример обмена микроконтроллера с универсальным компьютером. Обычно для обмена используется последовательный порт компьютера (СОМ-порт). Схема согласования уровней сигналов последовательного порта микроконтроллера и СОМ-порта компьютера приведена на рис. 7.19.

Рис. 7.19. Схема согласования уровней сигналов последовательного порта микроконтроллера и СОМ-порта компьютера

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

Пусть обмен будет производиться ANSI-символами (их легче всего сформировать в любой терминальной программе на персональном компьютере). Первый переданный символ будет рассматриваться как команда. При использовании в качестве команды заглавных и строчных букв латинского алфавита будет доступно 56 команд. При желании можно добавить еще 64 команды, обозначаемые буквами русского алфавита. Следующие несколько символов составят поле данных. Обычно здесь используются цифры. Пусть у нас поле данных содержит четыре символа. В качестве завершения команды используем символ возврата каретки (ASCII-код «13»). Этот символ вводится при нажатии на клавишу <Enter>.

Итак, подпрограмма ввода информации должна принять шесть 8-разрядных символов и только после этого передать управление программе обработки информации. Естественно, что подпрограмма обработки информации, которая входит в один из мониторов, не знает, когда будет завершен прием команды, поэтому введем однобитовую переменную (флаг) завершения приема команды. Подпрограмма ввода информации будет записывать в эту переменную единицу, а подпрограмма обработки команд после выполнения команды будет записывать в эту переменную ноль.

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

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

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

Для обмена данными с основной программой используется глобальная переменная буфера команд cmd и флаг завершения приема команды cmPrinjata. Выберем длину буфера команд равной максимальной длине команды — шесть байтов.

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

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

Флаг завершения приема команды cmPrinjata устанавливается при обнаружении символа возврата каретки. Теперь необходимо подготовиться к приему следующей команды. Для этого в указатель ptr записывается адрес начального байта буфера обмена.

Работа с другими источниками прерываний происходит так же, как в рассмотренном примере. Подпрограмма обслуживания прерывания должна осуществить ввод или вывод информации, и не более того! Вся основная обработка информации будет проводиться в главной программе. Это делается с целью не пропустить очередное прерывание.

Итак, подведем итоги

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

Особое внимание было уделено методу создания программ сверху вниз. Для этого был рассмотрен пример развития программы от построения программы-прототипа до детализации, достаточной для реализации устройства.

В главе были использованы некоторые интуитивно понятные конструкции языков программирования С и ASM-51. Однако для написания серьезных программ требуются более глубокие знания по этим языкам программирования, поэтому в последующих главах мы остановимся на этих языках подробнее.