- •Микропроцессорные системы
- •Введение
- •Проектирование микропроцессорных систем
- •Понятие системы
- •Цикл проектирования системы
- •Задание на курсовой проект
- •Содержание и оформление курсового проекта
- •Защита курсовых проектов
- •Требования пользователей и функциональная спецификация
- •Проектирование системы
- •Проектирование аппаратных средств микропроцессорного устройства
- •Типовая структура мпу
- •Система питания мпу
- •Питание от аккумуляторов
- •Комбинированный источник питания
- •Расчет потребляемой мощности
- •Модуль сброса и синхронизации
- •Расчет схемы сброса и синхронизации
- •Организация памяти микроэвм
- •Блок связи с оператором (пульт управления)
- •Подключение клавиатуры
- •Подключение индикатора
- •Расчет пульта оператора
- •Организация ввода данных
- •Ввод аналоговой информации
- •Расчет входных схем
- •Ввод цифровой и дискретной информации
- •Организация вывода данных
- •Цифровые выходы
- •Аналоговые выходы
- •Управление силовыми цепями
- •Стандартные последовательные интерфейсы
- •Гальваническая развязка
- •Выбор микроконтроллера
- •Проектирование программных средств микропроцессорных устройств
- •Технология разработки программного обеспечения
- •Технология задачи/состояния
- •Взаимодействие между задачами
- •Программная реализация типовых модулей мпу
- •Сопряжение с клавиатурой
- •Сопряжение с жки-модулем
- •Сопряжение с памятью по интерфейсу i2c
- •Сопряжение с последовательным асинхронным интерфейсом
- •Сопряжение с датчиком температуры
- •Пример проектирования микропроцессорного устройства
- •Требования пользователя и построение функциональной спецификации
- •Проектирование системы
- •Проектирование аппаратной части устройства
- •Проектирование программной части
- •Void init(void) // инициализация контроллера
- •Варианты заданий
- •Приложение а
- •1. Основание для разработки
- •2. Назначение разработки
- •3. Требования к разработке
- •3.1Требования к функциональным характеристикам
- •3.2Требования к надежности
- •3.3 Требования к условиям эксплуатации
- •3.4 Требования к составу и параметрам технических средств
- •3.5. Требования к программной и информационной совместимости
- •Приложение в
- •Приложение г
- •Библиографический список
Программная реализация типовых модулей мпу
Ниже приведены примеры реализации некоторых типовых модулей, написанных на языке С для микроконтроллеров семейства MCS51.
Сопряжение с клавиатурой
Рассмотрим организацию декодирующей клавиатуры размерностью 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: …; /* в с – код нажатой клавиши, буфер еще не пуст */
}