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


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

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



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

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

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

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

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

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

Листинг 21.17. Пример программы на языке С-51, использующей директивы include.

#include <global.h> /* Добавление файла определений
переменных, связанных с выводами микросхемы*/

#include <reg51.h> /* Добавление файла описаний

регистров специальных функций микроконтроллера */

#include <IO.h> /*Подключить файл с подпрограммами, осуществляющими

ввод и вывод данных в микросхему микроконтроллера*/

... /* Остальная часть программы */

В отдельные файлы выделяются, как правило, описания внутренних регистров микроконтроллера и переменных, связанных с выводами микросхемы микроконтроллера. Рассмотрим для примера содержимое включаемых файлов, имена которых использованы в примере, приведенном в листинге 21.17. Фрагменты исходных текстов этих файлов приведены в листингах 21.18 и 21.19.

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

Листинг 21.18. Фрагмент исходного текста, содержащегося в файле IO.h

 

void UART (void) interrupt 4

{register char tmp;

if(RI)

{RI=0;

tmp=SBUF;

*ptrBufUART++=tmp;

}

if(tmp==10)

{msgRsvd=1;

*ptrBufUART=0;

return;

}

}

 

/***********************************************************************

Подпрограмма преобразования тетрады в символ

***********************************************************************/

char Hex(char Nible)

{Nible&=0x0f;

if(Nible>=10)return(Nible+='7');

return(Nible+='0');

}

 

/***********************************************************************

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

завершенную нулем

***********************************************************************/

void LongToHex(char data *s,void data *Byte,unsigned char i)

{unsigned char tmp;

do{tmp=*(char data*)Byte;

Byte+=1;

s++ =Hex(tmp>>4);

*s++ =Hex(tmp);

}while(--i!=0);

*s =0;

}

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

Листинг 21.19. Фрагмент исходного кода, содержащегося в файле REG51.h

/*-----------------------------------------------------------------------

REG51.H

Файл объявления внутренних регистров для микроконтроллеров 80C51 и 80C31.

-----------------------------------------------------------------------*/

 

/* BYTE Register */

sfr P0 = 0x80;

sfr P1 = 0x90;

sfr P2 = 0xA0;

sfr P3 = 0xB0;

sfr PSW = 0xD0;

sfr ACC = 0xE0;

sfr B = 0xF0;

sfr SP = 0x81;

sfr DPL = 0x82;

sfr DPH = 0x83;

sfr PCON = 0x87;

sfr TCON = 0x88;

sfr TMOD = 0x89;

sfr TL0 = 0x8A;

sfr TL1 = 0x8B;

sfr TH0 = 0x8C;

sfr TH1 = 0x8D;

sfr IE = 0xA8;

sfr IP = 0xB8;

sfr SCON = 0x98;

sfr SBUF = 0x99;

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

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

Листинг 21.20. Временный файл, полученный в результате работы препроцессора

/*-----------------------------------------------------------------------

IO.H

-----------------------------------------------------------------------*/

void UART (void) interrupt 4

{register char tmp;

if(RI)

{RI=0;

tmp=SBUF;

*ptrBufUART++=tmp;

}

if(tmp==10)

{msgRsvd=1;

*ptrBufUART=0;

return;

}

}

 

/***********************************************************************

Подпрограмма преобразования тетрады в символ

***********************************************************************/

char Hex(char Nible)

{Nible&=0x0f;

if(Nible>=10)return(Nible+='7');

return(Nible+='0');

}

 

/***********************************************************************

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

завершенную нулем

***********************************************************************/

void LongToHex(char data *s, void data *Byte, unsigned char i)

{unsigned char tmp;

do{tmp=*(char data*)Byte;

Byte+=1;

*s++ =Hex(tmp>>4);

*s++ =Hex(tmp);

}while(--i!=0);

*s =0;

}/*-----------------------------------------------------------------------

REG51.H

Файл объявления внутренних регистров для микроконтроллеров 80C51 и 80C31.

-----------------------------------------------------------------------*/

 

/* BYTE Register */

sfr P0 = 0x80;

sfr P1 = 0x90;

sfr P2 = 0xA0;

sfr P3 = 0xB0;

sfr PSW = 0xD0;

sfr ACC = 0xE0;

sfr B = 0xF0;

sfr SP = 0x81;

sfr DPL = 0x82;

sfr DPH = 0x83;

sfr PCON = 0x87;

sfr TCON = 0x88;

sfr TMOD = 0x89;

sfr TL0 = 0x8A;

sfr TL1 = 0x8B;

sfr TH0 = 0x8C;

sfr TH1 = 0x8D;

sfr IE = 0xA8;

sfr IP = 0xB8;

sfr SCON = 0x98;

sfr SBUF = 0x99;

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

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

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

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

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

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

Листинг 21.21. Включение файлов в программе на ASM-51

$include (stdio.asm); включение Файла

; с функциями стандартного ввода-вывода

$include (reg51.inc); включение Файла с описаниями

; регистров специальных функций микроконтроллера

 

...;Остальная часть программы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

rl51.exe progr.obj, modul1.obj, modul2.obj

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

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

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

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

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

EXTRN DATA (BufInd, ERR)
EXTRN CODE (Podprogr)

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

PUBLIC BufInd, Parametr
PUBLIC Podprogr,?Podprogr?Byte

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

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

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

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

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

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

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

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

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

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

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

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

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

Листинг 21.22. Пример реализации программы-монитора на языке C-51

/*-------------------------------------------------------

Подпрограмма инициализации микроконтроллера

--------------------------------------------------------*/

void Init(void)

{//По мере написания программы здесь будут добавляться операторы,
//настраивающие элементы внутренней структуры микроконтроллера

//на необходимый режим работы.}

 

/*-------------------------------------------------------

Подпрограмма опроса клавиатуры

--------------------------------------------------------*/

void SborInf(void)

{}

 

/*-------------------------------------------------------

Подпрограмма обработки информации

--------------------------------------------------------*/

void ObrabInf(void)

{//В этой подпрограмме обычно осуществляется переключение

//режимов работы устройства}

 

/*-------------------------------------------------------

Подпрограмма обработки ошибок

--------------------------------------------------------*/

void ObrabOshib(void)

{//В этой подпрограмме обычно осуществляется индикация

//ошибочного ввода информации с клавиатуры}

 

void main(void)

{Init();

while(1)

{SborInf();

ObrabInf();

ObrabOshib();}

}

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

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

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

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

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

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

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

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

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

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

MOV SkanCode, P1.

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

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

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

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

q код опросапервой колонки клавиатуры – 11110111b.

q код опроса второй колонки клавиатуры – 11111011b.

q код опроса третьей колонки клавиатуры – 11111101b.

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

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

Листинг 21.23. Подпрограмма опроса клавиатуры на языке программирования С-51

/*-------------------------------------------------------

Подпрограмма опроса клавиатуры

--------------------------------------------------------*/

#define ScanCol1 0xf7 //11110111b

#define ScanCol2 0xfb //11111011b

#define ScanCol3 0xfd //11111101b

#define ScanCol4 0xfe //11111110b

 

void SborInf(void)

{register char tmp;

SkanCode=0xff; //Занести код отсутствия нажатой кнопки

 

P1=ScanCol1; //Выдать код опроса кнопок в колонке 1

tmp=P1; //прочитать состояние кнопок

if(tmp!= ScanCol1) //и если хоть одна кнопка в колонке нажата,

SkanCode &= tmp; //то запомнить скан-код

P1=ScanCol2; //Выдать код опроса кнопок в колонке 2

tmp=P1; //прочитать состояние кнопок

if(tmp!= ScanCol2) //и если хоть одна кнопка в колонке нажата,

SkanCode &= tmp; //то запомнить скан-код

P1=ScanCol3; //Выдать код опроса кнопок в колонке 3

tmp=P1; //прочитать состояние кнопок

if(tmp!= ScanCol2) //и если хоть одна кнопка в колонке нажата,

SkanCode &= tmp; //то запомнить скан-код

P1=ScanCol4; //Выдать код опроса кнопок в колонке 4

tmp=P1; //прочитать состояние кнопок

if(tmp!= ScanCol2) //и если хоть одна кнопка в колонке нажата,

SkanCode &= tmp; //то запомнить скан-код

}

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

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

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

public OprosKlaviat

 

OprosCol_1 equ 11110111b

OprosCol_2 equ 11111011b

OprosCol_3 equ 11111101b

OprosCol_4 equ 11111110b

 

_code segment code

 

rseg _code

;--------------------------------------------------------------------

;ПОДПРОГРАММА ОПРОСА КЛАВИАТУРЫ

;--------------------------------------------------------------------

;Подпрограмма опроса клавиатуры сканирует клавиатуру 4x4

;и возвращает в аккумуляторе полученный скан-код клавиатуры

 

OprosKlaviat:

mov SkanCode,#FFh;Занести код не нажатой кнопки

 

mov AdrKlav,#OprosCol_1;Выдать код опроса кнопок в колонке 1

mov A,AdrKlav;прочитать состояние кнопок

cjne A,#OprosCol_1,TstCol2;и если хоть одна кнопка в колонке нажата

sjmp TstCol2;(код опроса не совпадает со считанным),

SetCol1: anl A,SkanCode;то запомнить

mov SkanCode,A;скан-код

TstCol2:

mov AdrKlav,#OprosCol_2;Выдать код опроса кнопок в колонке 2

mov A,AdrKlav;прочитать состояние кнопок

cjne A,#OprosCol_2,SetCol2;и если хоть одна кнопка

sjmp TstCol3;в колонке нажата,

SetCol2: anl A,SkanCode;то запомнить

mov SkanCode,A;скан-код

TstCol3:

mov AdrKlav,#OprosCol_3;Выдать код опроса кнопок в колонке 3

mov A,AdrKlav;прочитать состояние кнопок

cjne A,#OprosCol_3,SetCol3;и если хоть одна кнопка

sjmp TstCol4;в колонке нажата,

SetCol3: anl A,SkanCode;то запомнить

mov SkanCode,A;скан-код

TstCol4:

mov AdrKlav,#OprosCol_4;Выдать код опроса кнопок в колонке 4

mov A,AdrKlav;прочитать состояние кнопок

cjne A,#OprosCol_4,SetCol4;и если хоть одна кнопка

sjmp EndOpros;в колонке нажата,

SetCol4: anl A,SkanCode;то запомнить

mov SkanCode,A;скан-код

EndOpros:

ret

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

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

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

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

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

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

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

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

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



Поделиться:


Последнее изменение этой страницы: 2017-02-07; просмотров: 636; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 34.238.242.168 (0.187 с.)