Пояснительная записка (1230175), страница 7
Текст из файла (страница 7)
| Программирование универсального контроллера параллельного обмена описанный универсальный контроллер параллельного обмена (УКПО) содержит 7 программно- доступных байтовых двунаправленных портов и регистр управляющего слова. Таким образом, на нижнем уровне программирования УКПО необходимо реализовать три функции: запись управляющего слова, запись данных в любой порт и чтение данных из любого порта. На языке Си они выглядят так: // *** функция записи управляющего слова // CW_ADDR - адрес регистра управляющего слова // cw - байт управляющего слова outportb (CW_ADDR, cw); // *** Функция записи данных в порт // BASE_ADDR - базовый адрес УКПО (адрес порта 0) // port - номер порта (О...6) // data - байт данных outportb (BASE_ADDR + port, data); // *** функция чтения данных из порта // BASE_ADDR - базовый адрес УКПО (адрес порта 0) // port - номер порта (О...6) // data - байт данных data = inportb (BASE_ADDR + port); Напишем для примера простейшую программу тестирования УКПО. Будем последовательно записывать во все порты код 55Н и контролировать правильность записи. При этом все порты, кроме тестируемого, будем переводить в Режим чтения. // *** Программа тестирования УКПО *** #include #Include #define NPORT 7 // Число портов УКПО // Глобальные переменные unsigned BASE_ADDR; // Базовый адрес УКПО unsigned CW_ADDR; // Адрес регистра управляющего слова void main (void) {unsigned i; unsigned char data = 0х55; for (i=0;i<="" pre=""> Как уже отмечалось, каждому порту УКПО соответствует один бит управляющего слова. Единица в этом бите устанавливав) порт в режим передачи данных, а ноль - в режим приема. Естественно, что когда порт находится в режиме передачи, ничто не мешает прочитать его содержимое, контролируя таким образом правильность вывода. Именно это свойство УКПО использовано в программе его тестирования. Поэтому, хотя мы и будем называть состояния портов "ввод" и "вывод", будем иметь в виду, что "вывод" на самом деле является "вводом/выводом" В нашем примере для последовательной установки портов в режим вывода использовалась операция сдвига единицы на число разрядов, равное номеру порта. Очевидно, что при этом данный порт устанавливается на вывод, а все остальные - на ввод. Используя описанные выше функции программирования УКПО, можно реализовать самые разные режимы его работы и алгоритмы взаимодействия с внешними устройствами [11]. Например, на основе УКПО может быть построен простейший тактовый генератор. Для этого будет использован один из битов порта 0 УКПО. Ниже приведен пример программы тактового генератора, работающего с частотой 1 кГц. // *** Программа тактового генератора на 1 кГц // *** на базе УКПО. // *** Выход генератора - бит 0 порта 0. #include #include #define MASK_BIT 1 // Маска бита О // Глобальные переменные unsigned BASE_ADDR; // Базовый адрес УКПО unsigned CW_ADDR; // Адрес регистра управляющего слова void main (void) { unsigned char data = 0; // Начальное состояние - О outportb (CW_ADDR, 1); // Установка конфигурации: // порт 0 на вывод, // остальные на ввод while (! kbhit()) // Пока не нажата любая клавиша { outportb (BASE, data); // Запись данных data := MASK_BIT; // Инверсия бита delay (1); // Задержка на 1мс }} //*** Конец программы В этой программе изменение состояния выхода УКПО производится инверсией бита с помощью операции "ИСКЛЮЧАЮЩЕЕ ИЛИ" с позиционной маской бита. Для выдержки временного интервала использована системная функция delay языка Си, в качестве аргумента принимающая время в миллисекундах. Очевидно, что максимальная частота такого тактового генератора как раз и равна 1 кГц, чего в большинстве случаев недостаточно. Кроме того, точность установки частоты снижается из-за суммирования задержек выполнения остальных команд цикла. Для повышения частоты тактового генератора можно использовать другой способ формирования программной задержки - цикл с заранее определенным временем исполнения. Например, если известно, что операция for (i=0;i<1;i++) // Пустой цикл, выполняемый один раз; выполняется N микросекунд, то, устанавливая предел выполнения цикла, можно получить любую задержку в диапазоне от N до 65536-N с шагом N. Однако такой способ требует предварительного определения быстродействия компьютера, что обычно нелегко сделать с достаточной точностью. Наконец, для выдержки временного интервала можно использовать системный таймер компьютера. При этом программные прерывания 08Н и 1СН позволяют устанавливать интервалы, не меньшие чем 55 мс (частота примерно 18.2 Гц). Для получения меньших временных интервалов необходимо самому запрограммировать таймер. С помощью УКПО просто реализуется асинхронный протокол взаимодействия с "медленными" внешними устройствами. Основа такого протокола - цикл типа "команда-ожидание реакции-действие". В качестве примера рассмотрим сопряжение БИС АЦП 1113ПВ1 с компьютером с помощью УКПО. Условное обозначение БИС, ее подключение к портам УКПО и временные диаграммы работы. Процедура чтения кода с АЦП реализуется последовательностью действий: запуск - ожидание готовности - чтение данных. // *** Программа чтения данных из БИС АЦП 1113ПВ1 *** #include #include #define START 1 // Маска бита запуска // (бит 0 порта 2) #define READY 0х80 // Маска бита готовности (бит 7 порта 1) #define DH 3 // Маска 8-го и 9-го битов данных АЦП // гобальные переменные unsigned BASE_ADDR; // Базовый адрес УКПО unsigned CW_ADDR; // Адрес регистра управляющего слова void main (void) { unsigned data_ADC = 0; // Данные с АЦП outportb (CW_ADDR, 4); // Установка конфигурации: // порт 2 на вывод, // остальные на ввод outportb (BASE+2, START^ 1) // Запуск АЦП - запись 0 while ( ( (data_ADC = inportb (BASE+1)) & READY ) != О ); // Опрос готовности data_ADC = (data_ADC & DH)<<8 + inportb (BASE); // Чтение младшего байта данных printf ("\пПрочитан код - %u", data_ADC); } //*** Конец программы В следующем примере рассмотрим реализацию протоколов обмена с "быстрым" устройством - модулем ОЗУ емкостью 16 КБайт с байтовой организацией. Подключим модуль ОЗУ к портам УКПО следующим образом: РО.О ... Р0.7 – младший байт шины адреса: АО ... А7, PI.О ... PI.5 – старшие линии шины адреса: А8...А13, Р1.6- сигнал разрешения записи -WE, PI.7- сигнал разрешения выхода - ОЕ, Р2.0 ... Р2.7- шина данных: DO ... D7. Ниже приведены драйверы модуля ОЗУ, реализующие процедуры записи и чтения байта данных по произвольному адресу. Перед вызовом драйверов должно быть предварительно установлено управляющее слово УКПО - ОЗН (порты 0 и 1 # на вывод, порт 2 - на ввод). // *** Драйверы модуля ОЗУ *** #define WE 0х40 // Маска бита WE (бит 6 порта 1) #define ОЕ 0х80 // Маска бита ОЕ (бит 7 порта 1) #define АН Ox3F // Маска линий А8...А13 шины адреса //(биты О... 5 порта 1) // Глобальные переменные unsigned BASE_ADDR; // Базовый адрес УКПО unsigned CW_ADDR; // Адрес регистра // управляющего слова // Прототипы функций void Write_RAM (unsigned addr, unsigned char data); unsigned char Read_RAM (unsigned addr); // Функция записи байта данных void Write_RAM (unsigned addr, unsigned char data) { outportb (CW_ADDR, 7); // Порты О...2 - на вывод outportb (BASE_ADDR, addr); // Младший байт адреса outportb (BASE_ADDR+1, (addr>>8) &AH | ОЕ & (~WE)); // Старшие линии адреса, //OE=1, WE=0 outportb (BASE_ADDR+2, data); // Установка данных outportb (BASE_ADDR+1, (addr>>8) & AH | ОЕ | WE)); //WE=1 outportb (CW_ADDR, 3); // Порты 0,1 - на вывод, // порт 2 - на ввод } // Функция чтения байта данных unsigned char Read_RAM (unsigned addr) { unsigned char data; outportb (BASE_ADDR, addr); // Младший байт адреса outportb (BASE_ADDR+1, (addr"8) & AH & (~OE) | WE); // Старшие линии // адреса, // OE=0, WE=1 data = inportb (BASE_ADDR+2); // Чтение данных outportb (BASE_ADDR+1, (addr>>8) & AH | ОЕ | WE)); // OE=1 return (data); } //*** Конец драйверов Важно обратить внимание на управление отдельными битами порта 1. Для этого использованы логические операции с прямыми и инверсными масками соответствующих битов. Кроме того, следует отметить, что подключение к порту 2 двунаправленной шины данных требует изменения направления передачи по этому порту, то есть перепрограммирования управляющего слова (в отличие от случаев подключения однонаправленных линий, когда управляющее слово устанавливается в начале программы и больше не модифицируется). Другой задачей, где в процессе работы требуется изменение направления передачи порта, является использование части его линий для ввода, а другой части - для вывода информации (как уже неоднократно говорилось, направление передачи информации можно установить только для всего порта целиком, а не для отдельных его битов). Рассмотрим сопряжение с компьютером простейшего устройства, состоящего из четырех ключей и четырех лампочек и предположим, что ввиду большой загруженности УКПО другими задачами для подключения нашего устройства выделен только один порт Драйверы, приведенные ниже, реализуют две функции - чтение состояния ключей и лампочек. // *** Драйверы устройства "Набор лампочек и кнопочек" *** #define LAMP OxOF // Маска битов светодиодов (биты 0...3 порта 5) #define Р5 0х20 // Маска бита порта 5 в управляющем слове // Глобальные переменные unsigned BASE_ADDR; // Базовый адрес УКПО unsigned CW_ADDR; // Адрес регистра управляющего слова unsigned char CW; // Управляющее слово // Прототипы функций unsigned char Get_Switch (void); void SetJ-amp (unsigned char data); // Функция чтения состояния ключей unsigned char Get_Switch (void) // В возвращаемом байте четыре старших бита (4...7) соответствуют четырем ключам //(О - замкнут, 1 - разомкнут). { unsigned char data; outportb (CW_ADDR, CW & (~P5)); // Порт 5 - на ввод data = inportb (BASE_ADDR+5); // Чтение состояния ключей outportb (CW_ADDR, CW | Р5); // Порт 5 - на вывод return (data); } // Функция "поджигания" лампочек void SetJ-amp (unsigned char data) // Четыре младших бита из data соответствуют четырем // лампочкам (1 - горит, 0 - погашен). outportb (BASE_ADDR+5, (data & LAMP) | Get_Switch()); //*** Конец драйверов Понятно, что если для "поджигания" лампочек просто вывести соответствующую тетраду в порт 5, это может привести к конфликту на четырех старших линиях между выводимой информацией и состоянием ключей. Поэтому в старшую тетраду надо записывать те данные, которые установлены ключами. Для этого приходится считывать их состояние при каждой записи в порт 5. Для считывания состояния ключей необходимо переводить порт 5 на ввод, а затем после чтения - опять на вывод. Такое кратковременное отключение порта от лампочек (время зависит от быстродействия компьютера, но во всяком случае не превосходит нескольких десятков микросекунд) не будет заметно. |
Программирование логического анализатора
| При написании драйверов логического анализатора необходимо реализовать три основные процедуры:
// *** Драйверы логического анализатора *** #define READY 0х01 // Маска бита готовности (бит 0) #define FREQ 0х07 // Маска битов кода частоты // регистрации (биты О...2) #define SYNC 0х38 // Маска битов кода синхролинии // (биты 3...5) #define POLAR 0х40 // Маска бита полярности // синхроперехода (бит 6) #define LENGTH 4096 // Глубина регистрации - // 4096 кадров // Глобальные переменные unsigned CW_ADDR; //Адрес регистра // управляющего слова unsigned PRE_REG_ADDR; // Адрес регистра глубины // предпусковой регистрации unsigned RO,R1 ,R2,R3; // Адреса чтения байтов 0...3 данных unsigned READY_ADDR; // Адрес флага готовности // Структура параметров регистрации typedef struct LA_Param { unsigned char freq; // Код частоты (О...7) unsigned char sync; // Номер синхролинии (О...7) unsigned char polar; // Код полярности синхроперехода // 0 - положительный, 1 - отрицательный. unsigned pre_reg; // Глубина предпусковой регистрации (О - (LENGTH-1)) }; // Прототипы функций int lnit_LA (LA_PARAM *la); unsigned Check_Ready (void); void Read_Data (unsigned long *data); // Функция установки параметров и запуска регистрации int lnit_LA (LA_PARAM *la) // Возвращает: 0 - запуск произведен, // -1 - ошибка параметров регистрации { unsigned char cw. pr; // Проверка правильности входных параметров it (la->freq > 7 11 la->sync > 7 11 la->polar > 1 11 la->pre_reg >= LENGTH) return (-1); // Выход по ошибке // Формирование управляющего слова // и кода предпусковой регистрации cw = (la->freq & FREQ) + ((la->sync " 3) & SYNC) + ((la->polar " 6) & POLAR); Pw = la->pre_reg " 4; // Точность - 16 кадров outportb (CW_ADDR, cw); // Запись управляющего слова outportb (PRE_REG_ADDR, pr); // Запись кода предпусковой //регистрации и запуск анализатора return (0); // Выход по нормальному запуску } // Функция проверки окончания регистрации unsigned Check_Ready (void) // Опрашивает флаг готовности и возвращает: // 0 - готов, иначе - не готов {return (inportb (READY_ADDR) & READY); } // Функция чтения данных из буферного ОЗУ логического // анализатора в память компьютера void Read_Data (unsigned long *data) // data - указатель на массив, в который // будут переписаны // данные из буферного ОЗУ. { unsigned i; for (i=0;i < LENGTH;i++) // Повторяем чтение 4096 раз data[i] = (unsigned long) inportb (RO) + (unsigned long) inportb (R1) << 8 + (unsigned long) inportb (R2) << 16 + (unsigned long) inportb (R3) " 24; } //*** Конец драйверов С использованием этих драйверов в качестве примера придумаем простую программу для подсчета соотношения длительности единицы и нуля по всем из подключенных ко входам логического анализатора линий за время 100 мкс после прихода первой единицы по линии 3. Для получения максимальной точности будем проводить регистрацию на предельной частоте 10 МГц. |















