Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КП МПС Варианты.doc
Скачиваний:
77
Добавлен:
12.04.2015
Размер:
1.44 Mб
Скачать
    1. Программная реализация типовых модулей мпу

Ниже приведены примеры реализации некоторых типовых модулей, написанных на языке С для микроконтроллеров семейства MCS51.

      1. Сопряжение с клавиатурой

Рассмотрим организацию декодирующей клавиатуры размерностью 4 х 4 (рис.5.9). Здесь линии порта Р1.7 – Р1.4, подключенные к горизонтальным линиям клавиатурной матрицы, являются выходными, а линии порта Р1.3 – Р1.0, подключенные к вертикальным линиям матрицы, – входными.

В таких декодирующих клавиатурах идентификация нажатой клавиши осуществляется по методу сканирования. Сущность этого метода заключается в следующем: в каждый момент времени программным путем только на одной из выходных горизонтальных линий формируется сигнал логического нуля, на остальных горизонтальных линиях должен быть уровень логической единицы. Выдача сигнала 0 последовательно повторяется для каждой выходной линии, т.е. возможны следующие выходные комбинации: 1110, 1101, 1011, 0111. После установки выходной комбинации контроллер опрашивает вертикальные линии матрицы. Если при этом некоторая вертикальная линия Р1.3 – Р1.0 приобретет значение 0, то имеется возможность программным путем определить нажатую клавишу, так как сигнал на входной вертикальной линии будет иметь значение 0 только в случае, когда нажатая клавиша соединяет её с горизонтальной линией, на которой в данный момент времени присутствует уровень 0.

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

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

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

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

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

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

Рис.5.10. Часть диаграммы задач работы с клавиатурой

Организация опроса клавиатуры

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

Управление обработчику прерывания передается по переполнению таймера Т0: выставляется флаг переполнения TF0. Обработчик прерывания сканирует клавиатуру, доступ к которой осуществляется через порт Р1. Если ни одна клавиша не нажата, осуществляется выход из прерывания. Если фиксируется нажатие, то формируется скан-код нажатой клавиши, который записывается в буферKeyBufи выставляется признак нажатия. Пользовательский процесс периодически опрашивает буфер при помощи функцииKeyRead().

Обработчик прерывания

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

Рис.5.11. Диаграмма задач со схемой взаимодействия

Исходя из этого, временная задержка τ для защиты от дребезга клавиш реализуется не на прямую, а через время между двумя соседними прерываниями от таймера. Кроме того, для уменьшения времени нахождения в прерывании сканирование можно выполнять только при установлении факта нажатия какой-либо клавиши. В этом случае, если ни одна клавиша не нажата, сразу осуществляется выход из прерывания. Обнаружение нажатия какой-либо клавиши просто осуществить при помощи установки всех линийР1.7 - Р1.4выходного порта клавиатуры в ноль. При нажатии любой клавиши во входном порту появится ноль на одной из линийР1.3 – Р1.0.

Граф состояний задачи обработчика прерываний

Граф состояний задачи сканирования клавиатуры представлен на рис.5.12.

На рисунке введены следующие обозначения:

Q0– состояние инициализации – задается режим таймера, настраивается прерывание по переполнению таймера и т.д;

Q1– состояние сканирования клавиатуры и определения момента нажатия/отпускания клавиши;

Q2– состояние повторного чтения кода клавиши через времяτ для защиты от "дребезга" контактов;

Q3– состояние обработки кода нажатой клавиши – записи в буфер клавиатуры;

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

Рис.5.12. Граф состояний для задачи сканирования клавиатуры

Описание графа состояний для задачи сканирования клавиатуры.

В состоянии Q1осуществляется сканирование клавиатуры и чтение ее состояния. Микроконтроллер будет находиться в этом состоянии до тех пор, пока считанный код не изменится. Код изменится в двух случаях:

  • если будет нажата клавиша;

  • если будет отпущена ранее нажатая клавиша (в Q1перешли изQ3 после записи кода нажатой клавиши).

При изменении считанного кода (Xi ≠Xi-1) происходит переход в состояниеQ2.

В состоянии Q2осуществляется задержка на времяτ, определяемое характеристиками клавиатуры, после чего выполняется сканирование и чтение клавиатурной матрицы. Если код клавиши не изменился, и она была нажата, то делается вывод о нажатии на клавишу и осуществляется переход в состояниеQ3. Если код клавиши изменился или клавиша была не нажата, то делается вывод об ошибке или об отпускании клавиши после удержания. После чего осуществляется переход в состояниеQ1.

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

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

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

Программная реализация обработчика прерываний

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

/*Программа сканирования клавиатуры */

/* Timer 0 interrupt - 400 Hz */

interrupt [0x0B] void T0_int(void)

{

char i, t; /* Локальные переменные */

/* Сканирование клавиатуры и вычисление кода нажатой клавиши */

TL0 = Timer400L; /*Перезагрузка таймера для вызова прерывания*/

TH0 = Timer400H;

t=(~KeybPort) & 0xF; /*Читаем порт, младшие 4 бита – входные линии от

клавиатуры, при нажатии на линии 0 */

if (t) /* Клавиша нажата? */

{

for (i = 0;!(t&(1<<i));i++); /*Поиск установленного бита*/

if (t = = (1<<i)) /*Установлен только один бит? (нажата одна клавиша)*/

if (Key) /* Были нажаты клавиши в других линиях в

данном цикле сканирования (LRs=0-3)? */

Key = MultipleBit; /*Да, установить признак множественного нажатия */

else

Key = ((LRs<<2)|i)+1; /*Нет, клавиша одна, запомнить код клавиши*/

}

else

Key = 0;

LRs++; LRs&=3; /* Вычислить номер следующей линии сканирования */

KeybPort = (0xEF << LRs) | 0xF; /* Установить следующую линию сканирования в 0 */

if (!LRs) /* Пройден полный цикл сканирования? */

{

switch (State) /* в какое состояние перейти ? */

{

case 1: /* Состояние Q1 */

/* Функция действия */

if (Key & MultipleBit) /* Множественное нажатие? */

Key = 0; /* Сбросить код клавиши */

/* Проверка выхода */

if (Key = = PrevKey) /* Считанный код не изменился? */

{

/* Функция выхода 1-1*/

PrevKey = Key; /*Запомнить код клавиши */

/* Задание нового состояния */

State = 1; /* Остаемся в состоянии Q1 */

}

else

{

/* Функция выхода 1-2*/

PrevKey = Key; /*Запомнить код клавиши */

Count = 0; /* Сбросить счетчик циклов сканирования */

/* Задание нового состояния */

State = 2; /* Переходим в состояние Q2 */

}

break;

case 2: /* Состояние Q2 */

/* Проверка выхода 2-1*/

if (++Count < KeyTime-1) /* Время дребезга не закончилось? */

/* Задание нового состояния */

State = 2; /* Остаемся в состоянии Q2 */

else

/* Проверка выхода 2-2*/

if ((Key = = PrevKey)& (Key<>0) /* Считанный код не изменился? */

/* Задание нового состояния */

State = 3; /* Переходим в состояние Q3 */

else

/* Функция выхода 2-3*/

PrevKey = 0; /*Обнулить старый код клавиши */

/* Задание нового состояния */

State = 1; /* Переходим в состояние Q1 */

break;

case 3: /* Состояние Q3 */

/* Функция действия */

/* Запись кода в буфер клавиатуры и установка признака нажатия*/

if (HPtr = =KeySize-1) /*Дошли до конца буфера?*/

i =0; /* Да, установить адрес на начало буфера */

else

i =HPtr+1; /*Нет, увеличить адрес в буфере*/

if (i != TPtr) /* Нет переполнения буфера?

(адрес для записи не равен адресу по чтению из буфера?) */

{

KeyBuf[HPtr] = Key; /* Запишем в

буфер код по адресу записи */

HPtr = i; /* Установим новый адрес для записи

будущего кода нажатия */

KeyPressed =1; /* Установим признак

наличия данных в буфере */

}

/* Задание нового состояния */

State = 0; /* Переходим в состояние Q0 */

break;

} /* switch (State) */

} /* if (!LRs) */

} /* interrupt */

Частота вызова подпрограммы, определяющая временную задержку для подавления "дребезга", выбрана равной 400Гц (период 2,5мс). Временная задержка при сканировании четырех линий в этом случае составит – 4х2,5мс=10мс. Для задания частоты 400 Гц в подпрограмме осуществляется перезагрузка таймера (TH0 = Timer400H и TL0 = Timer400L) числом (65536-Fg/12/400), где Fg – частота генератора, Fg/12 – частота на входе таймера, Fg/12/400 – количество импульсов для загрузки таймера, работающего в режиме вычитания, задающее на его выходе частоту 400Гц. Так как таймер в MCS-51 работает в режиме нарастающего счета, то число для загрузки 16-разрядного счетчика составит (65536-Fg/12/400). В процедуре инициализации могут быть использованы следующие команды определения:

#define SysFreq 11059200 /* Частота генератора 11,0592 МГц */

#define Timer400H HIGH(65536-SysFreq/12/400) /*Число для загрузки таймера*/

#define Timer400L LOW(65536-SysFreq/12/400) /*Число для загрузки таймера*/

#define KeybPort P1 /* Порт связи с клавиатурой */

#define MultipleBit 0x20 /* Признак одновременного нажатия более одной клавиши */

#define KeyTime 3 /* Количество повторных считываний кода клавиши,

определяющих временную задержку для подавления "дребезга" контактов */

Процедура выдает код без автоповтора в диапазоне 1-17. Нумерация клавиш матрицы осуществляется справа налево, сверху вниз, т.е. код правой верхней клавиши равен 1, а нижней левой - 17.

В программе использованы следующие константы и переменные:

константы: Timer400,MultipleBit,KeyTime, определяющие соответственно число для загрузки таймера Т0, признак множественного нажатия и временную задержку для подавления "дребезга" контактов;

глобальные переменные:

- используемые только процедурой сканирования: char:Key,PrevKey,Count,LRs,State, соответственно текущий код нажатой клавиши, считанный код, количество циклов удержания клавиши, код на выходных линиях, номер выполняемого состояния графа;

- используемые для связи с другими процедурами: char:HPtr,TPtr,KeySize, KeyBuf[],KeyPressed, соответственно указатель записи и указатель чтения буфера клавиатуры, размер буфера, буфер клавиатуры, признак нажатия.

Если ни одна клавиша не нажата, то с порта Р1 считывается код 0Fh, при нажатии клавиши считанный код будет отличаться от 0Fh.

Передачу кодовнажатых клавишмежду задачамипосредством вызова функции выполнено следующим образом:

При нажатии клавиши функция прерывания помещает код соответствующей клавиши в буфер KeyBuf[] по адресуHPtr(голова буфера) и устанавливает признак нажатияKeyPressedв единицу. Процедуре, которая обрабатывает коды клавиш, необходимо постоянно анализировать признак нажатия. ПриKeyPressed=1 она должна забирать коды нажатых клавиш через вызов функции

char KEYRead (char &c),

выполнить необходимые действия и сбросить в 0 признак KeyPressed.

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

char KEYRead (char *c)

{

char t=0, IEs;

IEs = IE; EA = 0; /* сохранение регистра прерывания, запрет прерывания*/

if ( HPtr = = TPtr ) t = 0; /* указатель начала и конца совпадают – буфер пуст*/

else

{ *c = KEYBuf [ TPtr ++]; /* запись кода клавиши в с*/

if ( TPtr = = KeySize) TPtr = 0; /* сброс указателя конца буфера */

if ( HPtr = = TPtr) t = 2; /* указатель начала и конца буфера совпадают – буфер пуст*/

else t = 1;

}

IE = IEs; /* восстановление регистра прерываний*/

return t;

}

Процедура чтения кода нажатой клавиши из буфера записывает его по адресу и возвращает:

0 – буфер пуст;

1 – байт считан, в буфере есть еще данные; 2 – байт считан, в буфере больше нет данных.

Для сохранения целостности данных выполнено выделение критической области с помощью стандартной функции управления прерыванием – общего запрета прерываний ЕА=0 с последующим восстановлением состояния регистра прерываний IE.

Ниже приведена программа работы с клавиатурой.

/****** Модуль обработки клавиатурной матрицы 4 х 4 ******/

#include <io51.h>

#define SysFreq 11059200 /* Частота генератора 11,0592 МГц */

#define Timer400 65536-SysFreq/12/400 /*Число для загрузки таймера=0хF700 */

#define KeybPort P1 /* Порт связи с клавиатурой */

#define MultipleBit 0x20 /* Признак одновременного нажатия более одной клавиши */

#define KeyTime 3 /* Количество повторных считываний кода

клавиши, определяющих временную задержку

для подавления "дребезга" контактов */

char Key, PrevKey, Count, LRs, State = 1; /* Переменные

Key – текущий код нажатой клавиши

PrevKey – код клавиши при первом считывании по нажатию

Count – счетчик текущего количества циклов удерживания клавиши

LRs – текущая выходная комбинация для сканирующих линий

State – номер текущего состояния графа*/

char HPtr, TPtr, KeySize = 10; /* TPtr - текущий адрес хвоста (адрес самого старого

невыбранного кода из буфера);

HPtr - текущий адрес головы (адрес

последнего занесенного кода в буфер);

KeySize - размер буфера*/

char KeyPressed, KeyBuf [KeySize] ; /*признак нажатия и буфер клавиатуры*/

/* Инициализация клавиатуры*/

void KEYInit(void)

{

TMOD = 0x01; /* Timer 0 - mode 1 */

TR0 = 1; /* Разрешение работы таймера */

ET0 = 1; /*Разрешение прерывания по переполнению таймера*/

}

char KEYRead (char *c)

{

char t=0, IEs;

IEs = IE; EA = 0; /* сохранение регистра прерывания, запрет прерывания*/

if ( HPtr = = TPtr ) t = 0; /* указатель начала и конца буфера совпадают – буфер пуст*/

else

{ *c = KEYBuf [ TPtr ++]; /* запись кода клавиши в с*/

if ( TPtr = = KeySize) TPtr = 0; /* сброс указателя конца буфера */

if ( HPtr = = TPtr) t = 2; /* указатель начала и конца буфера совпадают – буфер пуст*/

else t = 1;

}

IE = IEs; /* восстановление регистра прерываний*/

return t;

}

/* Timer 0 interrupt - 400 Hz */

interrupt [0x0B] void T0_int(void)

{

char i, t; /* Локальные переменные */

/* Сканирование клавиатуры и вычисление кода нажатой клавиши */

TH0 = Timer400>>8; /*Перезагрузка таймера для вызова прерывания*/

t=(~KeybPort) & 0xF; /*Читаем порт, младшие 4 бита – входные линии от

клавиатуры, при нажатии на линии 0 */

if (t) /* Клавиша нажата? */

{

for (i = 0;!(t&(1<<i));i++); /*Поиск установленного бита*/

if (t = = (1<<i)) /*Установлен только один бит?(нажата одна клавиша)*/

if (Key) /* Были нажаты клавиши в других линиях в

данном цикле сканирования (LRs=0-3)? */

Key = MultipleBit; /*Да, установить признак множественного нажатия */

else

Key = ((LRs<<2)|i)+1; /*Нет, клавиша одна, запомнить код клавиши*/

}

else

Key = 0;

LRs++; LRs&=3; /* Вычислить номер следующей линии сканирования */

KeybPort = (0xEF << LRs) | 0xF; /* Установить следующую линию сканирования в 0 */

if (!LRs) /* Пройден полный цикл сканирования? */

{

switch (State) /* в какое состояние перейти ? */

{

case 1: /* Состояние Q1 */

/* Функция действия */

if (Key & MultipleBit) /* Множественное нажатие? */

Key = 0; /* Сбросить код клавиши */

/* Проверка выхода */

if (Key = = PrevKey) /* Считанный код не изменился? */

{

/* Функция выхода 1-1*/

PrevKey = Key; /*Запомнить код клавиши */

/* Задание нового состояния */

State = 1; /* Остаемся в состоянии Q1 */

}

else

{

/* Функция выхода 1-2*/

PrevKey = Key; /*Запомнить код клавиши */

Count = 0; /* Сбросить счетчик циклов сканирования */

/* Задание нового состояния */

State = 2; /* Переходим в состояние Q2 */

}

break;

case 2: /* Состояние Q2 */

/* Проверка выхода 2-1*/

if (++Count < KeyTime-1) /* Время дребезга не закончилось? */

/* Задание нового состояния */

State = 2; /* Остаемся в состоянии Q2 */

else

/* Проверка выхода 2-2*/

if ((Key = = PrevKey)& (Key<>0) /* Считанный код не изменился? */

/* Задание нового состояния */

State = 3; /* Переходим в состояние Q3 */

else

/* Функция выхода 2-3*/

PrevKey = 0; /*Обнулить старый код клавиши */

/* Задание нового состояния */

State = 1; /* Переходим в состояние Q1 */

break;

case 3: /* Состояние Q3 */

/* Функция действия */

/* Запись кода в буфер клавиатуры и установка признака нажатия*/

if (HPtr = =KeySize-1) /*Дошли до конца буфера?*/

i =0; /* Да, установить адрес на начало буфера */

else

i =HPtr+1; /*Нет, увеличить адрес в буфере*/

if (i != TPtr) /* Нет переполнения буфера?

(адрес для записи не равен адресу по чтению из буфера?) */

{

KeyBuf[HPtr] = Key; /* Запишем в

буфер код по адресу записи */

HPtr = i; /* Установим новый адрес для записи

будущего кода нажатия */

KeyPressed =1; /* Установим признак

наличия данных в буфере */

}

/* Задание нового состояния */

State = 0; /* Переходим в состояние Q0 */

break;

} /* switch (State) */

} /* if (!LRs) */

} /* interrupt */

Программа обслуживания клавиатуры выполняет следующие действия:

– инициализацию клавиатуры;

– сканирование клавиатуры и запись кода нажатой клавиши в буфер клавиатуры;

– чтение кода нажатой клавиши из буфера клавиатуры.

Процедура инициализации void KEYInit(void) задает режим работы таймера и разрешает его прерывание.

Любой модуль, периодически вызывая функцию

(KEYRead (&c))

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

/*Модуль опроса клавиатуры через вызов функции*/

extern void KEYInit(void);

extern charKEYRead(char *c);

char c

KEYInit(); /* Инициализация (запуск сканирования) клавиатуры */

EA = 1; /* Общее разрешение прерываний */

switch (KEYRead (&c)) /* вызов функции с проверкой возвращаемого признака */

{

case 0: …; break; /* буфер не содержит информации */

case 2: …; break; /* в с – код нажатой клавиши, буфер пуст */

default: …; /* в с – код нажатой клавиши, буфер еще не пуст */

}