Inp, inpw ввод из порта


Содержание

Порты ввода-вывода

Порт ввода-вывода – логическое объединение сигнальных линий, через которое принимаются и передаются данные.
Каждая линия порта, как правило, обозначается как Pnx, где

  • n – обозначение порта;
  • x – номер бита (линии) в порте.

Каждый порт ввода-вывода обслуживают как минимум 3 служебных регистра:

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

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

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

Для всех линий портов ввода-вывода, как правило, доступна программная конфигурация входных подтягивающих резисторов. Подтягивающие резисторы осуществляют доопределение потенциалов «брошенных» входов напряжением высокого (Pull-up) или низкого (Pull-down) уровня.

Для обращения к отдельным линиям порта используется маскирование битов.

Входные элементы управления

В качестве входных элементов управления могут использоваться

  • другие элементы схемы;
  • тумблеры;
  • джамперы;
  • множественные переключатели;
  • кнопки.

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

Различают 1-позиционные и 2-позиционные тумблеры:

1-позиционные

2-позиционные

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

Множественные переключатели представляют собой набор 1-позиционных тумблеров в миниатюрном формате.

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

Выходные элементы управления

В качестве выходных элементов управления могут использоваться

  • другие элементы схемы;
  • элементы индикации (единичные светодиоды или светодиодные сборки, в частности, — 7-сегментные индикаторы).

Единичные светодиоды

7-сегментные индикаторы

Различают 7-сегментные индикаторы с общим анодом и с общим катодом.

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

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

В таблице приведены коды для отображения цифр на 7-сегментном индикаторе

wxDev, не работает inp и outp

Подскажите пожалуйста, какую библиотеку в wxDev используют функции inp и outp для работы с портами? conio.h и dos.h не помагают.
Пишет: [Linker Error] undefined reference to `outp’
[Linker Error] undefined reference to `inp’

Добавлено через 1 час 12 минут
подозреваю что этих функций нет, тк это работает только под дос.
Следовательно, вопрос: а можно ли под win7 работать с портами, в частности интересуют каналы таймера?

06.06.2011, 18:38

Преобразовать каталог библиотеки MyHomeLib inp-txt-inp с перекодировкой в UTF-8
Как добавить/удалить символ в конце строки для Каталога Библиотеки MyHomeLib? Уважаемые.

Не работает отладка в wxDev-C++
Всем доброго времени суток. Прочитал на форуме много тем по данной теме, но всё они не доходили.

с wxdev boost работает?
с wxdev boost работает?

wxDev-как работает среда ?
include #include using namespace std; int main(int argc, char *argv) <.

Функция Outp
Ребята, пожалуйста, подскажите аналог функции Outp из C++ для C++ Builder v.6! Я никак найти не.

06.06.2011, 19:57 2 06.06.2011, 20:00 [ТС] 3 06.06.2011, 20:07 4

Поскольку при работе с портом требуется выполнение некоторого количества рутинных операций, то можно использовать какую-нибудь библиотеку. Правда тогда потребуется некоторое время на изучения работы с ней
Например libctb с сайта https://iftools.com/start/index.en.php
Правда я не задавался вопросом поиска идеальной библиотеки

Добавлено через 42 секунды
Упс. Чего это я решил, что нужен COM-порт?

Программирование STM32. Часть 5: Порты ввода-вывода GPIO

В этой части мы разберемся с порами ввода-вывода GPIO микроконтроллера STM32F103C8 и напишем «Hello, World!» с мигающим светодиодом, а так же научимся читать состояние выводов микроконтроллера и использовать встроенный подтягивающий резистор. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1.

Введение

General-purpose input/output (GPIO) — важный компонент любого микроконтроллера, с помощью которого он взаимодействует с окружающим миром. В микроконтроллерах STM32 порты именуются буквами A, B, C и так далее: GPIOA, GPIOB, GPIOC... Каждый GPIO имеет 16 линий ввода/вывода, причем каждая линия может быть настроена независимо от других. Вот варианты настройки:

  • Input floating — вход с отключенными подтягивающими резисторами
  • Input pull-up — вход с подтяжкой к логической единице
  • Input-pull-down — вход с подтяжкой к логическому нулю
  • Analog — аналоговый вход (например, для АЦП)
  • Output open-drain — выход с открытым коллектором (записали 1 — выход в высокоимпедансном состоянии, записали 0 — выход прижат внутренним транзистором к земле)
  • Output push-pull — выход «тяни-толкай» (записали 1 — на выходе лог. 1, записали 0 — на выходе лог. 0)
  • Alternate function push-pull — альтернативная функция в режиме «тяни-толкай»
  • Alternate function open-drain — альтернативная функция в режиме открытого коллектора

Дам несколько пояснений по поводу режимов.

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

Alternate function. В этом режиме ножкой микроконтроллера управляет внутренняя цифровая периферия, например, модуль USART.

Регистры GPIO

Давайте рассмотрим регистры портов GPIO.

Port configuration register low (GPIOx_CRL)

Рис. 1. Регистр CRL

Это конфигурационный регистр для выводов порта с номерами от 0 до 7. Каждому выводу предоставлено 4-ре бита конфигурации: 2 бита MODEy и 2 бита CNFy.

MODEy[1:0]: Режим ножки порта, вход или выход. В режиме выхода нужно выбрать максимальную частоту переключения данной ножки, насколько понял это является оптимизацией энергопотребления порта.

  • 00: Вход (значение после сброса)
  • 01: Выход, максимальная частота 10 MHz.
  • 10: Выход, максимальная частота 2 MHz.
  • 11: Выход, максимальная частота 50 MHz.

CNFy[1:0]: Конфигурация режима.

В режиме входа (MODEy[1:0]=00):

  • 00: Analog mode — аналоговый режим (подключен к АЦП или ЦАП-у)
  • 01: Floating input — вход с отключенными подтягивающими резисторами (значение после сброса)
  • 10: Input with pull-up / pull-down — вход с подтяжкой вверх или вниз
  • 11: Reserved — не используется

В режиме выхода (MODEy[1:0]>00):

  • 00: General purpose output push-pull — выход в режиме тяни/толкай
  • 01: General purpose output Open-drain — выход с открытым коллектором
  • 10: Alternate function output Push-pull — выход альтернативной функции режиме тяни/толкай
  • 11: Alternate function output Open-drain — выход альтернативной функции с открытым коллектором

Port configuration register high (GPIOx_CRH)

Рис. 2. Регистр CRH

Это конфигурационный регистр для выводов порта с номерами от 8 до 15. Тут все то же, что и для регистра GPIOx_CRL.

Port input data register (GPIOx_IDR)

Рис. 3. Регистр IDR

IDRy: в этих битах содержится входное значение соответствующего порта ввода-вывода.

Port output data register (GPIOx_ODR)

Рис. 4. Регистр ODR

ODRy: выходные данные порта.

Port bit set/reset register (GPIOx_BSRR)

Рис. 5. Регистр BSRR

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

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

BSy: Установить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Устанавливает в единицу соответствующий бит ODRx

Port bit reset register (GPIOx_BRR)

Рис. 6. Регистр BRR

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

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

Port configuration lock register (GPIOx_LCKR)

Рис. 7. Регистр LCKR

Этот регистр используется для блокировки конфигурационных битов порта после записи корректной последовательности в 16 бит (LCKK) регистра. Значения битов [15:0] используется для блокировки конфигурации GPIO. Во время блокирующей последовательности в LCKK значения LCKR [15: 0] не должны меняться. Когда блокирующая последовательность была записана, конфигурация выбранных портов ввода/вывода может быть изменена только после сброса микроконтроллера. Каждый LCKy бит блокирует возможность изменения четырех битов конфигурации порта (CRL, CRH).

LCKK[16]: Ключ блокировки.

  • 0: Блокировка конфигурации порта не активна.
  • 1: Блокировка конфигурации порта активна. GPIOx_LCKR заблокирован до следующего сброса микроконтроллера.

Для блокировки необходимо выполнить следующую последовательность:

  • Записать 1
  • Записать 0
  • Записать 1
  • Прочитать 0
  • Прочитать 1 (эта операция чтения не является обязательной, а всего лишь подтверждает успешность установки блокировки)

LCKy: Эти биты могут быть прочитаны и записаны, но запись можно произвести только если бит LCKK равен нулю.

  • 0: Конфигурация пина номер y не заблокирована
  • 1: Конфигурация пина номер y заблокирована

Настройка порта GPIO


Итак, с регистрами разобрались, настало время практики. Все примеры в этой статье для микроконтроллера STM32F103C8. В моем распоряжении есть вот такая отладочная плата:

На ней установлен кварцевый резонатор на 8 МГц и светодиод на порту PB12. Вот с помощью этого светодиода мы и устроим Hello, World! ��

Задача ясна: настраиваем PB12 на выход в режиме push-pull и с помощью регистра ODR дергаем 12-й пин порта GPIOB туда-сюда! Но мы забыли об одной маленько детали: RCC. Дело в том, что по-умолчанию после сброса микроконтроллера все периферийные модули отключены от источника тактового сигнала, в том числе и GPIO. А подать тактирование можно с помощью регистров RCC. В 3-ей части я про это говорил. Для начала нужно определить, к какой шине у нас подключен GPIOB. Открываем даташит на микроконтроллер, ищем вот эту таблицу:

Рис. 8. Таблица шин и периферийных устройств

GPIOB у нас подключен к шине APB2. Идем в Reference manual, открываем раздел про RCC, переходим к пункту 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). С помощью этого регистра можно подать тактовый сигнал на устройства шины APB2:

Рис. 9. Регистр RCC_APB2ENR

В регистре RCC_APB2ENR много флагов для разной периферии, в том числе и для нашего GPIOB, флаг называется IOPBEN. Перед началом инициализации PB12 нам надо установить этот бит в единицу.

ПОРТЫ ВВОДА/ВЫВОДА

Каждый МК имеет некоторое количество линий ввода/вывода, которые объединены в 8-разрядные параллельные порты ввода/вывода РТх («х» — имя порта, используемое в техническом описании). В карте памяти МК каждый порт ввода/вывода представлен регистром данных порта DPTx. В режиме ввода логические уровни сигналов на линиях порта РТх отображаются нулями и единицами в соответствующих разрядах регистра DPTx. В режиме вывода данные, записанные под управлением программы в регистр DPTx, передаются на выводы МК, которые отмечены в качестве линий порта РТх. Обращение к регистру данных DPTx осуществляется теми же командами, что и обращение к ячейкам оперативной памяти. Кроме того, во многих МК отдельные разряды портов могут быть опрошены командами битового процессора.

В зависимости от функций, которые реализуют те или иные порты ввода/вывода, различают следующие типы параллельных портов:

1. Однонаправленные порты, предназначенные в соответствии со спецификацией МК только для ввода или только для вывода информации.

2. Двунаправленные порты, направление передачи которых (ввод или вывод) определяется в процессе инициализации системы.

3. Порты с альтернативной функцией. Отдельные линии этих портов связаны со встроенными в МК периферийными устройствами, такими, как таймер, АЦП, контроллеры последовательных приемопередатчиков. Если соответствующий периферийный модуль МК не используется, то его выводы можно задействовать как обычные линии ввода/вывода. Напротив, если модуль активизирован, то принадлежащие ему линии ввода/вывода автоматически конфигурируются в соответствии с функциональным назначением в модуле и не могут быть использованы в качестве линий ввода/вывода.

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

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

1. Режим простого программного ввода/вывода.

2. Режим ввода/вывода со стробированием.

3. Режим ввода/вывода с полным набором сигналов квитирования.

Временные диаграммы простого программного ввода представлены на Рис. 1.2. Внешнее устройство изменяет данные на линиях порта РТх в произвольный момент времени. Событие обновления кода и момент его наступления не отмечаются дополнительными сигналами. Информация об изменении кода на линиях порта ввода в МК не передается. МК производит считывание данных с порта РТх в моменты времени, которые определяются только ходом вычислительного процесса и не связаны с моментами изменения данных на линиях порта РТх. Поэтому одно и то же состояние линий порта РТх может быть считано несколько раз подряд, но может быть и пропущено, если данные на линиях порта изменяются слишком часто. При обращении к регистру данных порта DPTx МК формирует сигнал чтения и состояние линий РТх7. РТхО по внутренней магистрали данных передается в один из регистров центрального процессора. Если состояние линий порта РТх7. РТхО в этот момент изменяется, то считанные данные могут оказаться ошибочными. В случае изменения состояния только одной линии в регистре CPU будет зафиксировано либо предшествующее, либо последующее значение, но оба эти значения будут достоверными. В случае изменения состояния сразу нескольких линий может возникнуть классическая ситуация гонок в цифровой системе, и прочитанное слово окажется недостоверным.

В режиме простого программного вывода (Рис. 1.3) состояние линий порта РТх7. РТхО изменяется после каждой операции записи в регистр данных DPTx. Это состояние сохраняется неизменным до тех пор, пока МК не запишет новые данные в регистр DPTx.

Возможность передачи недостоверной информации устраняет режим стробируемого ввода/вывода. При выводе данных в этом ре жиме (Рис. 1.4) каждое изменение данных на линиях порта сопровождается коротким стробом подтверждения вывода STBB (обычно 1. 2 периода fBUS МК). Этот строб может быть использован для записи данных в регистр периферийной ИС. Например, на Рис. 1.5 показан вариант использования режима стробируемого вывода для передачи информации в четырехканальный ЦАП AD8408 фирмы Analog Devises. Микросхема AD8408 имеет 8-разрядную магистраль данных и два входа встроенного дешифратора адресации канала ЦАП, в регистр которого производится запись. Магистраль данных ЦАП соединена с линиями порта РТх, который работает в режиме стробируемого вывода. Две линии порта РТх используются для задания номера канала ЦАП. Порт РТх работает в режиме простого программного вывода.

Рис. 1.2. Временные диаграммы обмена в режиме простого программного ввода

Режим ввода данных со стробированием (Рис.1.6) позволяет избежать считывания недостоверной информации в случае, если момент чтения совпадает с моментом изменения многоразрядного кода на входе. В этом режиме для обслуживания порта ввода/вывода используется дополнительный регистр-защелка DPTLx (см. Табл. 1.2), в котором запоминаются данные, присутствующие на входе порта во время строба. При работе в режиме стробируемого ввода линии порта могут быть использованы как для контроля за текущим состоянием (тогда данные следует читать из регистра DPTx), так и для ввода стробируемой информации (тогда данные следует читать из регистра-защелки DPTLx).

Режим обмена с полным набором сигналов квитирования наиболее часто используется для обмена в параллельном коде между двумя МК. На Рис. 1.7 и 1.8 представлены временные диаграммы работы и функциональная модель порта ввода данных в рассматриваемом режиме. Каждое изменение данных на линиях порта РТх внешнее устройство сопровождает импульсом записи на линии STBA. Данные с входов порта РТх передаются в триггеры регистра DPTLx при низком активном уровне STBA. По положительному фронту сигнала STBA эти данные запоминаются в регистре-защелке порта DPTLx. Одновременно аппаратными средствами формируется нарастающий фронт сигнала подтверждения приема STBB. Высокий логический уровень STBB информирует внешнее передающее устройство о том, что данные в регистр-защелку помещены, но еще не считаны в память МК. Поэтому передавать новые данные не следует. Высокий активный уровень сигнала STBB устанавливается именно по срезу сигнала STBA: передаваемые данные еще не запомнены в DPTLx, но передача начата и, следовательно, приемное устройство уже занято. По положительному фронту сигнала STBA передаваемые данные запоминаются. Следовательно, изменения данных уже не будет и аппаратные средства устанавливают в 1 триггер готовности данных Тх. Этот триггер может быть считан под управлением программы, он отображается в одном из регистров специальных функций МК. Путем инициализации можно также назначить прерывания по триггеру готовности данных. По состоянию триггера готовности данных МК обнаруживает, что в регистре-защелке порта DPTLx находятся данные, и производит считывание. В процессе чтения формируется сигнал выборки регистра-защелки RD • DCSPTLx, отрицательный фронт которого сбрасывает триггер готовности Тх. Позднее по нулевому состоянию триггера готовности Тх МК сможет определить, что предыдущие данные уже считаны из порта, а новые еще не поступили. Положительный фронт сигнала выборки устанавливает в 0 линию подтверждения приема STBB, информируя передающее устройство, что данные прочитаны микроконтроллером из порта в память, и можно передавать новые.

Временные диаграммы вывода с полным набором сигналов квитирования представлены на Рис. 1.9, Рис. 1.10 отражает функциональную модель порта. МК записывает данные в регистр данных порта РТх под управлением программы. При этом формируется внутренний сигнал записи WR • DCSPTx, отрицательный фронт которого устанавливает в 0 сигнал подтверждения вывода STBB для принима ющего устройства. По положительному фронту WR • DCSPTx сбрасывается триггер Тх. В этом режиме его можно интерпретировать как триггер готовности порта к обмену. Нулевое состояние триггера Тх информирует МК о том, что данные, ранее записанные в порт, еще не считаны внешним устройством. Принимающее устройство считывает данные с линий порта РТх, копируя их в одном из собственных регистров, и формирует строб подтверждения приема STBA. В ответ передающее устройство устанавливает в 1 сигнал подтверждения вывода STBB. Наличие высокого логического уровня на этой линии свидетельствует об отсутствии новых данных на линиях порта вывода. По положительному фронту сигнала STBA устанавливается в 1 триггер готовности порта к обмену, информируя МК о том, что предыдущие данные приняты внешним устройством и можно записать в порт новые. Аналогично предыдущему случаю, состояние триггера готовности порта к обмену Тх может быть считано программно или генерируется запрос на прерывание.

Рассмотрим использование режимов обмена с полным набором сигналов квитирования на примере передачи массива данных в параллельном коде между двумя МК. Структурная схема связи МК приведена на Рис. 1.11. Передача массива происходит из МК1 в МК2. Передачу инициирует МК1. Формат передаваемого массива следующий: , Элемент 1 >, Элемент 2>. Элемент п>, . Адрес размещения массива в памяти МК2 определен заранее.

1. МК1 анализирует состояние линии STBB. Если 0, то порт РТс МК2 свободен для приема данных от МК1.

2. МК1 производит запись числа элементов передаваемого массива (первый байт посылки) в порт РТа. Одновременно аппаратными средствами формируется строб записи STBA.

3. МК2 запоминает данные в регистре-защелке DPTLc и одновременно выставляет в 1 триггер готовности данных Тс. Сигнал с выхода триггера Тс поступает в подсистему прерывания МК2. МК2 переходит на выполнение подпрограммы прерывания, в которой инициализирует начальный адрес области записи принимаемого массива, запрещает прерывания по триггеру готовности данных Тс, считывает содержимое порта РТс в память, устанавливая таким образом начальное значение счетчика циклов обмена. В процессе чтения РТс триггер Тс сбрасывается и линия подтверждения приема данных STBB устанавливается в 0.

4. МК1 после выдачи первого байта переходит к опросу линии STBB. При установке линии в 0 происходит запись очередного байта в регистр порта РТа. Автоматически формируется очередной строб записи STBA.

5. МК2 принимает все байты, кроме первого, путем программного опроса триггера готовности Тс. После завершения приема байта с номером n MK2 вычисляет контрольную сумму принятого массива и принимает последний байт от МК1. МК2 производит сравнение рассчитанной и принятой контрольной суммы. В случае их равенства устанавливает в 1 бит PTdO. MK2 завершает обмен, разрешая прерывания по триггеру Тс.

6. МК1 после передачи байта контрольной суммы формирует задержку времени; необходимую МК2 для сравнения контрольных сумм. Далее считывает РТЫ и выходит из подпрограммы обмена. Если равенство контрольных сумм не установлено, то подпрограмма обмена может быть повторена.

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

В ряде МК реализуется модификация режима вывода с полным набором сигналов квитирования, при которой буферы порта вывода становятся активными только во время формирования строба подтверждения приема информации. Это удобно с точки зрения уменьшения энергии потребления, т.к. буферы порта большую часть времени во время обмена находятся в высокоомном состоянии и практически не потребляют ток. Обычно активные уровни сигналов квитирования обмена STBA и STBB настраиваются программно посредством установки битов регистра управления режимами порта РТСх (Табл. 1.2).

Если в составе МК нет порта, который поддерживает стробируе-мый обмен или обмен с сигналами квитирования, то эти алгоритмы могут быть воспроизведены программным способом.

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

Различают три типа драйверов ввода/вывода:

1. Двунаправленные линии, которые настраиваются на ввод или на вывод программированием бита в регистре направления передачи DDPTx.

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

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

Примером драйверов первого типа могут служить драйверы линий ввода/вывода МК НС05 и НС08 фирмы Motorola (Рис. 1.12). Каждой линии порта поставлен в соответствие одноименный разряд регистра направления передачи DDPTx. Нулевое значение разряда конфигурирует линию на ввод, единичное — на вывод. После сброса МК все линии настроены на ввод. Из Рис. 1.12 видно, что в режиме ввода непосредственно в момент считывания логический уровень сигнала линии PTxi передается на внутреннюю магистраль данных, минуя регистр данных порта DPTx. В процессе чтения состояние линии не запоминается в регистре DPTx, и, следовательно, каждое новое обращение к порту ввода может возвращать новое значение. В режиме ввода транзисторы VT1 и VT2 закрыты, буфер находится в высокоомном состоянии (Z-состояние). Возможна ситуация, при которой операция чтения неподключенного входа будет возвращать нулевое значение. Поэтому, если в качестве источника сигнала используется открытый коллекторный выход или релейный контакт (Рис. 1.12), то уровень сигнала при разомкнутом контакте в общем случае не определен. Для задания единичного логического уровня сигнала при разомкнутом контакте следует подключить внешний резистор, который обычно обозначают RPULLUP- В режиме вывода транзисторы VT1 и VT2 управляются сигналом с выхода триггера регистра данных DPTx.

Примером квазидвунаправленных драйверов портов могут служить порты МК Intel MCS-51 (Рис. 1.13). Особенность этих драйверов заключается в том, что при считывании возвращаемое значение равно логическому произведению сигнала на линии и содержимого одноименного триггера регистра данных порта DPTx. По этой причине те разряды порта, которые будут считываться, должны быть предварительно установлены в 1 командой записи в порт и лишь затем прочитаны. Квазидвунаправленные порты не имеют регистра направления передачи и, следовательно, не должны инициализироваться. Кроме того, драйвер линии портов этого типа содержит «подтягивающий» резистор RPULLUP. поэтому операция чтения разомкнутого контакта будет возвращать 1.

Илон Маск рекомендует:  Хаки для IE6

Драйверы линий с изменяемой схемотехникой могут быть выполнены двумя способами (Рис. 1.14 и 1.15). Однако преследуемая цель одна — сократить число навесных элементов платы МП контроллера. В первом случае (Рис. 1.14) драйвер каждой линии содержит «подтягивающий к 1» резистор (RPULLUP). который обеспечивает уровень логической единицы на входе при разомкнутом контакте. Во втором случае (Рис. 1.15) драйвер дополнен «подтягивающим к 0» резистором (RPULLDOWN), который способен служить нагрузочным резистором датчика, выходной каскад которого выполнен по схеме эмиттерного повторителя. Логика управления встроенными «подтягивающими» резисторами одинакова для обоих типов драйверов:

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

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

• Коммутацией «подтягивающего» резистора каждой линии управляет одноименный бит регистра входного сопротивления PTUEx. Значение этого бита может многократно изменяться в ходе выполнения прикладной программы, тем самым осуществляется динамическое управления входным сопротивлением линии порта ввода и током потребления этой линии.

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

Рассматривая особенности драйверов линий ввода/вывода, нельзя не остановиться на понятии нагрузочной способности линии, Различают линии с нормальной и повышенной нагрузочной способностью. Если речь идет о нормальной нагрузочной способности, то следует ориентироваться наследующие цифры: !вых= 1.6. 2,0 мА, 11вых — 0.4. 2.0 мА. Типовые значения повышенной нагрузочной способности: 1°Вых

11вых — 25 мА. Предельное значение повышенной нагрузочной способности на сегодняшний день составляет 11Вых = 60 мА для Microchip PIC17, Следует заметить, что число выводов с повышенной нагрузочной способностью обычно ограничено. Кроме того, в справочных данных указан максимальный суммарный ток всех линий ввода/вывода, который ограничен теплоотводом корпуса МК.

Не нашли то, что искали? Воспользуйтесь поиском:

Другая жизнь LPT порта (часть 1)

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

В тексте вы встретитесь с общепринятой аббревиатурой записывания чисел. Например, 10102 — двойка в нижнем индексе указывает, что число 5 представлено в двоичном исчислении, 12410 – десятка в нижнем индексе, говорит о том, что число 124 десятичное. Это так… на всякий случай

Как показала практика, все программы, правильно написанные и дополненные соответствующими библиотеками (vbio32.dll, inpout32.dll, dlportio.dll и т.д.) работают на большинстве компьютеров с операционными системами семейства Windows. Я проверял работу всех своих программ (Visual Basic5.0, 6.0) на Win95, 98, Me, 2000, XP HE, XP Prof и даже в DOS6.22 (QBasic) – все работает прекрасно. В DOS-е вообще никаких библиотек не надо, там все и так работает. Сразу оговорюсь, что vbio32.dll и inpout32.dll НЕ БУДУТ РАБОТАТЬ ПОД Win2000, но совершенно спокойно будут работать под Win95, 98, Me.

Кстати, взять любую из этих библиотек вы можете здесь. Мне захотелось попробовать dlportio.dll и в данный момент я работаю с этой библиотекой. Ну и последнее, перед написанием программ необходимо правильно объявить библиотеку, которую вы используете.

Private Declare Function Inp Lib «inpout32.dll» Alias «Inp32» (ByVal PortAddress As Integer) As Integer

Private Declare Sub Out Lib «inpout32.dll» Alias «Out32» (ByVal PortAddress As Integer, ByVal Value As Integer)

Private Declare Function DlPortReadPortUchar Lib «dlportio.dll» (ByVal Port As Long) As Byte

Private Declare Sub DlPortWritePortUchar Lib «dlportio.dll» (ByVal Port As Long, ByVal Value As Byte)

Чем отличается Private от Public я писать не буду.

Параллельный порт для связи с принтером (или другим устройством) имеет базовый адрес &H378 (LPT1), &H278 (LPT2), &H3BC (LPT3). В данной статье мы будем рассматривать только LPT1. Адресное пространство данного порта занимает диапазон &H378-&H37F.

· Адрес &H378 называется базовым и служит для записи (чтения, но об этом попозже) данных в порт, на линии D0-D7.

· Адрес &H379 (базовый+1) предназначен для чтения битов состояния с устройства, подключенного к LPT-порту (принтер, сканер и т.д)

· Адрес &H37A (базовый+2) служит для записи битов управления устройства, подключенного к LPT-порту (принтер, сканер и т.д.).

На приведенных ниже таблицах «расшифрованы» контакты и сигналы каждого из адресов

Контакты 18-25 – «земля» (общий, GND, GROUND и т.д.)

Рассмотрим программирование каждого из адресов.

· Базовый адрес &H378 (LPT1) позволяет записывать данные в порт на линии D0-D7 в диапазоне от 0 до 255.

· Записываем в порт число 69

Код следующий. Для тех, кто использует

inpout32.dll

dlportio.dll

DOS

DlPortWritePortUchar &H378, 69

· Адрес &H379 служит для чтения битов состояния.

· Читаем состояние порта по адресу &H379

При чтении адреса &H379 необходимо помнить, что первые три бита – не используются и всегда имеют значение лог. «1», а 7-й бит – инверсный. В результате если все контакты 15, 13, 12, 10, 11 посадить на «землю», то при чтении информации вы получите на первых трех битах (которые не используются) 1+2+4 и на 7-м бите (контакт 11-инверсный, значит, при замыкании на землю будет лог. «1») +128 итого 135. Об этом не надо забывать. Во второй части статьи мы остановимся на этом более подробно.

Код следующий. Для тех, кто использует

inpout32.dll

dlportio.dll

DOS

Dim A as Integer
A = Inp(&H379) Dim A as Integer
DlPortReadPortUchar(&H379) DEFINT A-Z
A=INP(&H379)

· Адрес & H37 A служит для записи битов управления.

· Записываем сигнал -STROBE (бит управления 0)

Код следующий. Для тех, кто использует

inpout32.dll

dlportio.dll

DOS

Out &H37A, 10 DlPortWritePortUchar &H37A, 10 OUT &H37A, 10

Почему 10? Давайте посмотрим в табличку.

(-STROBE) 2 0

(-AUTO) 2 1

(INIT) 2 2

(-SELECT IN) 2 3

(Сигналы) биты

Контакт 1

Контакт 14

Контакт 16

Контакт 17

1

1

2

8


0+2+0+8=10

Сигналы STROBE, AUTO, SELECT IN – инверсные, значит, чтобы на выходе контактов разъема 1, 14, 17 получить логическую «1» надо подать на эти биты логический «0», т.е. подали одно – получили противоположное. Сигнал INIT прямой (не инверсный), поэтому логическая «1» на контакте 16 появится тогда, когда мы подадим на этот бит логическую «1», т.е. что подали, то и получили.

Попытаемся получить на контактах 1,17 – низкий уровень сигнала «0», а на контактах 14 и 16 высокий уровень сигнала «1», т.е. на выходе контактов 1,14,16,17 будет присутствовать 0 1 1 0 (610).

На нулевой бит (-STROBE) подаем «1» (на контакте 1 будет «0»), на первый бит (-AUTO) подаем «0» (на контакте 14 будет «1»), на второй бит (INIT) подаем «1» (на контакте 1 будет «1») и, наконец, на третий бит (-SELECT IN) подаем «1» (на контакте 17 будет «0»), т.е. мы записали по адресу &H37A число 10112,-это 1310. Значит, чтобы на выходе получить 6 надо подать 13.

Для удобства привожу таблицу со всеми возможными комбинациями чисел от 0 до 15

Подаваемый сигнал

Получаемый сигнал

Десятичное число

(-STROBE) 2

(-AUTO) 2 1

(INIT) 2 2

(-SELECT IN) 2 3

контакт 1

контакт 14

контакт 16

контакт 17

Десятичное число

1

2

4

8

1

2

4

8

1

1

1

11

1

1

1

1

10

2

1

1

1

9

3

1

1

1

8

4

1

1

1

1

1

15

5

1

1

1

1

1

14

6

1

1

1

1

1

13

7

1

1

1

1

1

12

8


1

1

1

3

9

1

1

1

2

10

1

1

1

1

11

1

1

1

12

1

1

1

1

1

7

13

1

1

1

1

1

6

14

1

1

1

1

1

5

15

1

1

1

1

1

4

Ну и, наконец, последнее в этой части статьи. Если ваш компьютер поддерживает стандарт EPP, то четвертым битом по адресу &H37A вы сможете разрешить прерывание (для LPT1 это IRQ7) от принтера, только не спрашивайте меня что это такое, я все равно ничего не знаю про прерывания. А вот пятым битом 1101012 , например, подав число 4310, вы устанавливаете шину D0-D7 в режим ПРИЕМА данных. При этом все разряды (контакты 2-9) принимают значение логической «1». Чтобы подать на нужный контакт логический «0» надо замкнуть его через сопротивление 240 – 360 Ом на «землю». Таким образом, через LPT порт компьютера мы получаем в стандартном виде устройство с 12-ю выходными сигналами и 5-ю входными, а при переводе порта в режим EPP мы получаем 4 выходных сигнала и 13 входных сигналов.

Режим SPP (12 выходов и 5 входов)

Режим EPP (4 входа и 13 выходов)

Сигнал

Направление

Сигнал

Направление

D0

Выход

D0

Вход

D1

Выход

D1

Вход

D2

Выход

D2

Вход

D3

Выход

D3

Вход

D4

Выход

D4

Вход

D5


Выход

D5

Вход

D6

Выход

D6

Вход

D7

Выход

D7

Вход

ERROR

Вход

ERROR

Вход

SELECT

Вход

SELECT

Вход

PAPER END

Вход

PAPER END

Вход

ACK

Вход

ACK

Вход

BUSY

Вход

BUSY

Вход

STROBE

Выход

-STROBE

Выход

-AUTO

Выход

AUTO

Выход

INIT

Выход

INIT

Выход

-SELECT IN

Выход

-SELECT IN

Выход

Конец первой части.

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

Статью прислал Клюшников Алексей, г.Иваново.

KNZSOFT Разработка ПО, консультации, учебные материалы

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

Архив рубрики: Программирование драйверов

Глава 8. Управление устройствами

    Содержание

Игра с драйвером scull и похожими «игрушками» позволяет получить хорошее начальное представление о программном
интерфейсе драйверов устройств в Linux. Однако, реализация реального драйвера требует реального устройства. Драйвер
представляет собой абстрактный слой между программными концепциями и электронными схемами. На протяжении семи предыдущих
глав, мы знакомились с программными концепциями. В этой главе мы завершим картину рассказом о том как драйвер может
получить доступ к портам ввода/вывода и памяти ввода/вывода на Linux платформе.

Данная глава продолжает традицию максимально возможной независимости от особенностей специфических устройств при
подготовке примеров программирования. Поэтому, при подготовке примеров требующих работы с устройствами мы будем
использовать то, что уже есть или может оказаться под рукой. Для демонстрации работы инструкций ввода/вывода мы будем
использовать стандартный параллельный порт, а для примеров ввода/вывода отображенного в память (memory-mapped I/O) мы будем
использовать обычный frame-buffer видео памяти.

Цифровой ввод/вывод в параллельный порт является, наверное, простейшим примером программирования портов ввода/вывода.
Параллельный порт Centronics реализует «сырой» ввод/вывод и доступен на большинстве компьютеров. Биты данных,
которые пишутся в устройство напрямую отображаются на выходных контактах параллельного порта. И наоборот, уровень входного
напряжения на этих контактах может быть напрямую прочитан процессором. Если говорить о практической стороне, то все что
нужно сделать, чтобы увидеть результат цифрового вывода в параллельный порт, достаточно подключить к определенным
контактам обычный светодиод. В дальнейшем мы увидим, что управление таким светодиодом представляет собой достаточно простую задачу.

Порты ввода/вывода и память ввода/вывода

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

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

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

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

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

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

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

Регистры ввода/вывода и обычная (conventional) память

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

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

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

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

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

Использование портов ввода/вывода

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

Для начала давайте вспомним, что порты ввода/вывода должны быть распределены перед их использованием в драйвере. В разделе
«Порты ввода/вывода и память ввода/вывода» главы 2 «Построение и запуск модулей» мы рассматривали функции, используемые для захвата (распределения) и освобождения портов:

После того, как требуемый диапазон портов был запрошен и получен, драйвер может выполнить операции чтения/записи этих портов. Большая часть аппаратных платформ делает различие между 8,16 и 32-битовыми портами. Поэтому, как правило, вы не можете свободно
выбирать разрядность операций ввода/вывода, исходя из удобства программирования, как вы это делаете при обращении к обычной памяти. Здесь все будет определяться возможностями управляемого устройства.

Иногда, порты ввода/вывода представлены схемами адресации, схожими со схемами адресации памяти, и вы можете заменить две последовательные (по адресу) 8-ми битовые операции на одну 16-ти битовую. Например, такая замена применима при работе с видеоадаптерами для PC, но, в общем случае, вы не можете рассчитывать на такую возможность.

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

Замечание: В дальнейшем будет использоваться ключевое слово unsigned без дальнейшего уточнения типа. Такая условность будет нести нагрузку аппаратно-зависимых определений, которые могут быть уточнены только для конкретной платформы. Такие функции почти всегда портируемы, т.к. компилятор автоматически выполнит подстановку платформо-зависимого типа данных без соответствующих предупреждений на этапе компиляции. Ответственность за потерю информации о значении числа при таком преобразовании лежит на плечах программиста — необходимо учитывать особенности платформ и использовать корректные значения. За таким соглашением, мы закрепим понятие «нечеткое приведение типа» (incomplete typing), которое будем использовать в оставшейся части данной главы.

unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); Функции чтения и записи в однобайтовые (8-ми битовые) порты. Аргумент port, в зависимости от платформы, может быть определен как unsigned long, unsigned short и т.п. Тип возвращаемого значения для функции inb() также может быть различным на различных платформах. unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); Функции доступа к 16-ти битовым портам (т.е. портам длиною в «слово» (word)). Данные функции недоступны
для платформ M68k и S390, которые поддерживают только байтовый ввод/вывод. unsigned inl(unsigned port); void outl(unsigned longword, unsigned port); Функции доступа к 32-х битовым портам. Величина «длинное слово» (longword) может определяться как unsigned long или unsigned int в зависимости от платформы.

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

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

  • Программа должна быть скомпилирована с опцией компиляции -O для принудительного inline-расширения соответствующих функций.
  • Для получения разрешения на использование операций ввода/вывода необходимо обратиться к системным функциям ioperm() или iopl(). Функция ioperm() дает и снимает разрешение на использование заданного поддиапазона портов в диапазоне от 0 до 0x3FF (первые 1024 порта), в то время как функция iopl() может предоставить доступ ко всему диапазону портов. Обе эти функции являются специфичными для платформы Intel.
  • Программа должна быть запущена от пользователя root для выполнения функций ioperm() или iopl(). Alternatively, one of its ancestors must have gained port access running as root.

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

Если ваша Linux-платформа не поддерживает системные вызовы ioperm() и iopl(), то задачи пространства пользователя могут попробовать получить доступ к портам ввода/вывода используя файл устройства /dev/port. Хотя, необходимо заметить, что значение данного файла специфично для каждой из платформ, и, наиболее вероятно, что данный файл является бесполезным для любой платформы, кроме PC.

В каталоге примеров вы можете посмотреть файлы misc-progs/inp.c и misc-progs/outp.c которые демонстрируют создание простейших инструментов в пространстве пользователя для чтения и записи портов из командной строки. Данные инструменты устанавливаются в систему под различными именами, типа inpb, inpw, inpl и позволяют манипулировать одно, двух и четырехбайтовыми портами, в зависимости от того, какое имя инструмента будет использовано пользователем. Код этих инструментов построен так, что если системный вызов ioperm() не представлен в системе, то доступ к портам осуществляется через файл устройства /dev/port.

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

Строковые операции

В дополнении к описанным выше однобайтовым, двухбайтовым и четырехбайтовым операциям ввода/вывода, некоторые процессоры поддерживают специальные инструкции по передаче последовательности байт, слов (два байта) или двойных слов (четыре байта) в/из заданного порта ввода/вывода. Такие инструкции называются строковыми (string instructions) и выполняются несколько быстрее передачи такого же объема данных через обычный цикл языка Си. Представим макрос, реализующий концепцию строкового ввода/вывода, который либо использует одну машинную инструкцию (при соответствующей поддержки процессором), либо выполняет цикл передачи (если процессор не поддерживает строковые операции ввода/вывода). Данный макрос не определен для платформ M68k и S390. Данные платформы имеют настолько специфическую шинную архитектуру, что написание портируемых драйверов использующих шинный обмен данными для этих и других платформ в принципе невозможно.

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

void insb(unsigned port, void *addr, unsigned long count); void outsb(unsigned port, void *addr, unsigned long count); Читаем или пишем count байт, начиная с адреса памяти addr из/в порт port. void insw(unsigned port, void *addr, unsigned long count); void outsw(unsigned port, void *addr, unsigned long count); Читаем или пишем count 16-битовых значений в 16-битовый порт. void insl(unsigned port, void *addr, unsigned long count); void outsl(unsigned port, void *addr, unsigned long count); Читаем или пишем count 32-битовых значений в 32-битовый порт.

Ввод/вывод с задержкой

На некоторых платформах — в особенности на i386 — могут возникнуть проблемы, когда процессор пытается передать данные по шине слишком быстро. Такие проблемы могут возникать по причине того, что процессор работает на более высокой частоте, по сравнению, например, с шиной ISA. Проблемы могут проявиться на слишком медленных устройствах. Решением этой проблемы может быть реализация некоторой небольшой задержки между инструкциями ввода/вывода. Если ваше устройство теряет данные, или вы боитесь, что такая потеря может произойти, то, вместо обычных функций ввода/вывода описанных выше, вы можете использовать специальный набор функций, реализующих такую задержку. Имена этих функций отличаются от обычных окончанием «_p». Например, inb_p, outb_p и т.д. Такие функции определены для большинства архитектур, хотя, при компиляции на некоторые платформы, они расширяются на обычные, функции, не использующие временную задержку. Такая замена производится для тех платформ, архитектура которых исполнена на современных шинах, передача данных по которым не требует временных задержек.

Особенности платформ

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

Одной из таких архитектурных особенностей, является тип адреса порта. Вспомните сигнатуру функций ввода/вывода, в которой приведение типа зависит от используемой аппаратной архитектуры. Например, для архитектуры x86, адрес порта является типом unsigned short (процессор поддерживает 64-х килобайтное пространство ввода/вывода), в то время как на других платформах, которые трактуют порты как некую часть общего адресного пространства памяти, это unsigned long.

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

Илон Маск рекомендует:  Что такое код exec

IA-32 (x86) Архитектура поддерживает все функции, описанные в этой главе. Адреса портов имеют тип unsigned short. IA-64 (Itanium) Поддерживаются все функции. Адреса портов имеют тип unsigned long. Реализовано отображение памяти (memory map). Строковые функции реализованы циклами на языке Си. Alpha Поддерживаются все функции и отображение памяти (memory map). Реализация портов ввода/вывода различается на различных Alpha-платформах, в зависимости от используемого чипсета. Строковые функции реализованы циклами на языке Си и лежат в файле arch/alpha/lib/io.c. Адреса портов имеют тип unsigned long. ARM Поддерживаются все функции и отображение памяти (memory map). Строковые функции реализованы циклами на языке Си. Адреса портов имеют тип unsigned int. M68k Порты отображаются в память. Поддерживаются только байтовые функции. Строковые функции не поддерживаются. Адреса портов имеют тип unsigned char*. MIPS MIPS64 Поддерживаются все функции. Строковые операции реализованы циклом (процессор не поддерживает строкового ввода/вывода). Порты отображены в память. Адреса портов unsigned int на 32-х разрядных процессорах, и unsigned long на 64-х разрядных. PowerPC Поддерживаются все функции. Порты имеют тип unsigned char *. S390 Также как и M68k, платформа поддерживает только байтовые порты ввода/вывода и не имеет строковых операций. Порты отображены в память. Адреса портов определены как указатели на char. Super-H Поддерживаются все функции и отображение памяти (memory map). Адреса портов имеют тип unsigned int. SPARC SPARC64 Пространство ввода/вывода отображено в память. Функции ввода/вывода определены для работы с адресами портов unsigned long.

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

Интересно заметить, что за исключением семейства процессоров x86, нигде более не используется отдельное адресное пространство для портов, хотя некоторые из поддерживаемых архитектур используют ISA и PCI слоты, для которых реализован различный механизм ввода/вывода и различные адресные пространства.

Кроме того, некоторые процессоры (в особенности ранние версии Alpha-процессоров) не имели инструкций, которые могли передавать более одного или двух байтов по шине ввода/вывода. По этой причине, используемые на таких платформах чипсеты, эмулируют 8-ми и 16-ти битовую передачу отображением ее в специальный адресный диапазон общего адресного пространства памяти. Таким образом, инструкции inb и inw реализуются двумя 32-х битовыми операциями чтения памяти, которые оперируют в другом адресном пространстве. К счастью, все эти механизмы скрыты от разработчика драйверов с помощью макросов, описанных в заголовочном файле include/asm-alpha/core_lca.h.

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

Детали выполнения операций ввода/вывода описаны в руководствах программиста для каждой из платформ. Эти руководства, обычно доступны для загрузки с Web в виде PDF файлов.


Управление цифровыми устройствами ввода/вывода

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

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

Реализация и программный интерфейс таких устройств ввода/вывода меняется от системы к системе. Наиболее часто, входные и выходные контакты устройства управляется двумя наборами портов (областей памяти, в случае соответствующего отображения). Через один набор производится настройка устройства (выбор контактов используемых для чтения и для записи), а через другой — непосредственно чтение/запись логических значений. В некоторых случаях реализация еще проще — например, все контакты уже аппаратно разведены либо на чтение, либо на запись. Однако, такое устройство уже нельзя назвать устройством общего назначения. Такое устройство как «параллельный порт&qout; реализованное на всех персональных компьютерах является примером такого устройства НЕ общего назначения.

Краткий обзор «Параллельного порта»

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

Параллельный интерфейс в своей минимальной конфигурации (опустим режимы ECP и EPP) состоит из трех восьми битовых портов, которые, согласно стандарту PC, начинаются с адреса 0x378 для первого параллельного интерфейса, и с адреса 0x278 — для второго. Первый порт реализован как двунаправленный регистра данных, биты которого соответствуют контактам 2-9 физического разъема. Второй порт, доступный только для чтения, связан с регистром статуса. Когда, например, параллельный порт используется для подключения принтера, то через регистр статуса мы можем получить информацию о текущем состоянии принтера. Третий порт, доступный только для записи, связан с управляющим регистром, через который, среди прочего, можно разрешать или запрещать использование прерываний.

Сигнальные уровни, используемые в параллельном интерфейсе являются уровнями стандартной Транзистор-Транзисторную Логики (TTL): 0 и 5 вольт, с порогом около 1.2 вольт. Требования по току выполняются, наверное, для всех реализаций интерфейса, а большинство современных реализаций удовлетворяет как требования по току, так и по напряжению.

ПРЕДУПРЕЖДЕНИЕ: Контакты параллельного порта не изолированы от внутренних цепей компьютера (…, which is useful if you want to connect logic gates directly to the port). Вы должны быть осторожны при подключении вашего устройства к компьютеру — цепи параллельного порта могут быть легко повреждены неправильным подключением из-за отсутствия такой изоляции. You can choose to use plug-in parallel ports if you fear you’ll damage your motherboard.

Описание битов представлено на рисунке 8-1. Вы можете получить доступ к 12 выходным битам и пяти входным, некоторые из которых логически инвертируются на своем сигнальном пути. Только один бит не имеет связанного с ним контакта — это 4-й бит (0x10) 2-го порта. Этот бит разрешает прерывания от параллельного порта. Мы будем использовать этот бит в реализации обработчика прерываний, который мы рассмотрим в главе 9 «Обработка прерываний».

Пример драйвера

Драйвер, который мы хотим вам представить был назван «short» — английский акроним от Simple Hardware Operations and Raw Tests. Все что он делает, это чтение и запись нескольких восьмибитовых портов, начиная с адреса, заданного во время загрузки. По умолчанию, драйвер использует диапазон портов принадлежащий параллельному интерфейсу PC (персонального компьютера). Через файловые интерфейсы к драйверу, в зависимости от младшего номера, мы можем получить доступ к разным портам. Драйвер «short» не делает ничего полезного, кроме предоставления некого дополнительного интерфейса доступа к портам. Вы можете использовать этот драйвер для управления портами, можете замерить время передачи данных в порт через интерфейс драйвера, а можете сделать что-нибудь еще, что вам покажется интересным.

Для того чтобы драйвер «short» работал на вашей системе, он должен иметь свободный доступ к устройству (по умолчанию, это параллельный интерфейс PC). Другими словами, другие драйверы не должны мешать ему в доступе к устройству. Большинство современных дистрибутивов предоставляют драйвер параллельного порта в виде модуля, который загружается при необходимости. Поэтому, можно предположить, что между нашим драйвером и «родным» драйвером параллельного порта не возникнет конфликта. Однако, если вы получите сообщение «can’t get I/O addres» от драйвера «short» (в консоли, из которой драйвер был загружен, или в файле системного лога), то это означает, что какой-то другой драйвер уже занял этот адресный диапазон под свое использование. Как правило, просмотрев файл /proc/ioports вы можете узнать, какой из драйверов удерживает требуемый адрес порта. Описываемый здесь конфликт ресурсов может возникнуть по отношению к любым драйверам устройств и не является прерогативой параллельного интерфейса.

Для упрощения формулировок, начиная с этого момента, мы будем говорить только о «параллельном интерфейсе». Однако, установив параметр модуля base во время загрузки драйвера, вы можете перенаправить работу драйвера «short» на другое устройство ввода вывода. Такая возможность позволяет нашему коду работать на любой Linux-платформе, где возможно получить доступ к цифровому интерфейсу ввода/вывода с помощью команд inb, outb. Вообще, все реальные аппаратные платформы, за исключением платформы x86, имеют отображение портов в память. Наш драйвер может работать и на таких платформах, и позднее, в разделе «Использование памяти ввода/вывода», мы покажем, как драйвер «short» может быть использован для работы на платформах с отображением портов в память.

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

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

Если вы собираетесь просматривать выводимые на параллельный порт данные с помощью спаянного устройства из светодиодов, резисторов и ответной части разъема параллельного порта (D-type connector), то мы надеемся, что вы не задействуете контакты 9 и 10. Мы будем использовать эти контакты позднее для запуска примера из главы 9 «Обработка прерываний». Там нам понадобиться соединить между собой эти два контакта.

Поясним нотацию выбранную для обозначения имен файлов-интерфейсов к нашему драйверу «short». /dev/short0 — пишет и читает из 8-ми битового порта по адресу base = 0x378 адресного пространства ввода/вывода. При загрузке драйвера вы можете сменить базовый адрес. /dev/short1 — пишет в восьмибитовый порт по адресу base+1. То же самое до base+7.

В действительности, операции вывода, выполняемые /dev/short0, реализованы на программном цикле использующем outb. A memory barrier instruction is used to ensure that the output operation actually takes place and is not optimized away.

Вы можете зажечь ваши светодиоды с помощью следующей команды:

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

Чтение выполняется похожим способом, только вместо вызова outb() выполняется inb(). Для того, чтобы прочитать что-нибудь значащее из параллельного порта, необходимо подключить к соответствующим контактам параллельного интерфейса какое-нибудь устройство, способное производить сигналы. Если такого устройства не использовать, то мы прочитаем бесконечный поток одинаковых байт. Если вы выполните чтение с выходного порта, то, наиболее вероятно, вы прочитаете последнее записанное в этот порт значение. Так реализован параллельный интерфейс и большинство других цифровых цепей ввода/вывода. Хотя, надо признаться, что реализация возможности прочитать последнее записанное значение, для большинства устройств, скорее инженерная прихоть, нежели жизненная необходимость. В общем, как бы там не было, мы можем попробовать прочитать последнее выводимое в порт значение с помощью, например, следующей команды:

Для демонстрации использования всех инструкций ввода/вывода, мы представляем три варианта интерфейса к драйверу «short» в следующей нотации имен файлов: /dev/short0 выполняет программный цикл, как показано в примере выше, /dev/short0p использует функции с задержкой outb_p() и inb_p() вместо «быстрых» функций, и /dev/short0s использует строковые инструкции. Все это повторяется восемь раз, от short0 до short7. И хотя параллельный интерфейс персонального компьютера имеет только три порта, вам может понадобиться большее количество портов если вы будете использовать другое устройство для работы с нашим тестовым драйвером.

Драйвер «short» выполняет абсолютный минимум действий по управлению устройством, но в нем показаны все инструкции, которые могут быть использованы при при создании реального драйвера. Интересующиеся читатели могут познакомиться с источниками модулей parport и parport_pc, чтобы посмотреть сложность реализации поддержки общепринятого для параллельного интерфейса набора устройств (принтеры, ленточные накопители, сетевые интерфейсы и пр.).

Использование памяти ввода/вывода

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

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

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

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

Используется ли, или нет ioremap() для доступа к памяти ввода/вывода, но прямое использование указателей для доступа к этой памяти является нерекомендуемой практикой. Несмотря на то, что память ввода/вывода адресуется, на аппаратном уровне, как обычное ОЗУ (см. «Порты ввода/вывода и память ввода/вывода»), следует избегать использования обычных указателей (см. «Регистры ввода/вывода и обычная память»). Использование оберточных функций для доступа к памяти ввода/вывода безопасно на всех платформах и оптимизировано наилучшим для каждой платформы способом.

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

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

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

Прямо отображенная память

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

Процессоры MIPS, используемые в PDA (personal digital assistant — персональный цифровой помощник), предлагают интересный пример такой конфигурации. Два адресных диапазона, по 512МБт каждый, прямо отображаются в физические адреса. Любое обращение к памяти любого из этих диапазонов обходит MMU (Memory Management Unit), а обращение к одному из этих диапазонов обходит еще и кэш. Последняя, 512-ти мегабайтная область резервируется для периферийных устройств, и драйвера могут получить доступ к своей памяти ввода/вывода напрямую, не используя механизм кэширования.

Другие платформы имеют другие способы прямого отображения адресных диапазонов. Некоторые имеют специальные адресные пространства для разыменования физических адресов (например, SPARC64 имеет специальный «address space identifier» для этой цели), а другие используют особые виртуальные адреса обходящие кэш.

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

Эти макросы используются для получения 8-ми, 16-ти и 32-х битовых значений данных из памяти ввода/вывода. Преимущество использование макроса состоит в безтиповости аргумента: тип определяется непосредственно перед использованием. Процитируем комментарий из файла asm-alpha/io.h — «если еще не ясно целое это или указатель, то мы примем оба варианта». Ни функции чтения, ни функции записи не проверяют корректность адреса, стараясь получить максимум производительности.

Как и вышеописанные функции, эти функции (макросы) используются для записи 8-ми, 16-ти и 32-х битовых данных.

Когда вам необходимо выполнить «memset» над памятью ввода/вывода, вы можете воспользоваться этой функцией. (When you need to call memset on I/O memory, this function does what you need, while keeping the semantics of the original memset.)

Эти функции перемещают блоки данных в/из памяти ввода/вывода и похожи на функцию memcpy() из библиотеки языка Си.

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

Некоторые, 64-разрядные платформы, предлагают еще функции readq() и writeq() для восьмибайтовых операций с памятью на шине PCI. Суффикс «Q» образовался от термина «quad-word», который является историческим пережитком со времен, когда процессоры оперировали 16-ти битовыми словами (Один «word» — шестнадцать бит). Суффикс «L», образованный от «long word» также является некорректным в этом смысле. Однако, переименование суффиксов, используя какую-нибудь более современную нотацию, приведет, наверное, еще к большей путанице.

Использование драйвера «short» для операций с памятью ввода/вывода

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

Приведем пример такого использования драйвера short для засветки нашего светодиодного устройства на платформе MIPS:

Использование драйвера short для памяти ввода/вывода и для портов ввода/вывода одинаково, с той лишь разницей, что использование файловых интерфейсов /dev/short0p и /dev/short0s полностью аналогично использованию /dev/short0. Т.е. для памяти ввода/вывода не существует инструкций выполняющих строковую передачу данных, или передачу данных с временными задержками между атомами данных.

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

Заметьте, использование в коде операции «write memory barrier — wmb()». Так как, на многих архитектурах, операция writeb() может превратиться в операцию обычного присвоения, использование wmb() гарантирует нам, что записать будет произведена ожидаемым способом.

Программное отображение памяти ввода/вывода

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

Для аппаратных и программных подсистем использования памяти ввода/вывода, в наиболее общем рассмотрении, мы можем сказать следующее: устройства «живут» по хорошо известным адресам, но процессор не имеет предопределенного виртуального адреса для обращения к ним. Эти «хорошо известные» физические адреса либо «прошиты» в устройство, либо назначаются системным программным обеспечением во время загрузки системы. Первое, справедливо, например, для ISA-устройств, адреса которых либо прошиваются в электрические цепи устройства, либо назначаются через физические джамперы на устройстве. Второй вариант справедлив для устройств PCI, чьи адреса назначаются системным программным обеспечением во время загрузки системы, или горячего подключения устройства, прописываются в устройство и сохраняют заданное значение на время работы устройства.

Для программного доступа к памяти ввода/вывода необходимо назначить устройству виртуальный адрес. Эту роль выполняет функция ioremap(), описанная в разделе «Семейство функций vmalloc» («vmalloc and Friends». Эта функция, рассмотренная в предыдущей главе посвященной вопросам использования памяти, специально разработана для назначения виртуальных адресов областям памяти ввода/вывода. Кроме того, разработчики ядра реализовали функцию ioremap() таким образом, что она не делает ничего, если применяется к прямо отображенному адресному пространству ввода/вывода.

После применения функции ioremap() (и iounmap()), драйвер устройства может получить доступ к памяти ввода/вывода независимо от того, является ли это отображение в виртуальное адресное пространство прямым, или нет. Однако помните, что прямое разыменование этих адресов на некоторых платформах недопустимо. Вместо прямого разыменования желательно использовать функции семейства readb(). Таким образом, мы можем приспособить драйвер short для работы как с памятью ввода/вывода в архитектуре MIPS, так и с наиболее общим случаем использования памяти устройств ISA/PCI на платформе x86, добавлением в код модуля вызовов ioremap()/iounmap() везде где анализируется параметр use_mem.

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

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

Прежде всего, обратим ваше внимание на новую функцию ioremap_nocache(). Мы не обсуждали ее в главе 7 «Работа с памятью», потому что ее значение во многом зависит от используемого «железа» Приведем цитату из одного заголовочного файла ядра: «It’s useful if some control registers are in such an area and write combining or read caching is not desirable.» В действительности, реализация этой функции идентична реализации ioremap() для большинства компьютерных платформ: если вся память ввода/вывода видна через некэшируемые адреса — нет причины делать отдельную реализацию для некэшируемого варианта ioremap().

Другим важным замечанием по функции ioremap() является ее особое поведение для версии ядра 2.0. Для ядра Linux 2.0 эта функция, называемая тогда vremap(), отказывала в отображении областям памяти, которые не были выравнены по странице. Разумность этого выбора основывалась на том, что процессор работает только в контексте страничных квантов. Однако, иногда возникает необходимость в отображении маленьких областей регистров, чьи физические адреса не выравнены по странице. Поэтому, начиная с версии ядра 2.1.131, ядро может отображать не выравненные по странице адреса.

Для обратной совместимости с версией ядра 2.0, в модуле short, для доступа к невыравненным по странице множествам регистров, используется следующий код, вместо прямого вызова ioremap():

Работа с памятью ISA ниже 1МБт

Одной из хорошо известных областей памяти ввода/вывода является память устройств на шине ISA. Это диапазон адресов памяти между 640КБ (0xA0000) и 1МБт (0x100000). Т.е. она расположена, как-бы внутри, обычного системного ОЗУ. Такое позиционирование адресов может показаться немного странным, и в настоящее время является артефактом относящемуся к началу 1980-х годов, когда объем памяти в 640КБ казался заведомо больше того, что может кому-то понадобиться.

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

Примечание: В действительности это не совсем верно. Этот диапазон памяти настолько мал и так часто используется, что ядро, во время загрузки, строит таблицы страниц для доступа к этим адресам. Однако, виртуальный адрес, используемый для доступа к этой области, не тоже самое, что физический адрес, и, вызов ioremap(), по прежнему, необходим. Кроме того, версия ядра 2.0 использовала прямое отображение этой области. См. раздел «Вопросы обратной совместимости».

Хотя ISA память ввода/вывода присутствует только в компьютерах класса x86, мы думаем, что имеет смысл, рассказать несколько слов о простом драйвере, использующем эту память.

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

Для демонстрации доступа к памяти ISA, мы будем использовать еще один простенький модуль, который, также, можно найти в каталоге примеров к оригинальному английскому варианту данной книги. Мы назвали этот модуль «silly» — как синоним к акрониму «Simple Tool for Unloading and Printing ISA Data». Примечание переводчика: в английском языке слова «stupid» и «silly» являются синонимами, означающими: глупый, тупой, бестолковый; дурацкий.

Данный модуль реализует функциональность модуля «short», предоставляя доступ ко всему 384-х килобайтному пространству ISA, с использованием различных функций ввода/вывода. Т.е. обращаясь к четырем различным файловым интерфейсам драйвера, мы можем выполнить одну и ту же задачу, используя разные функции передачи данных. Эти четыре файловых интерфейса к драйверу «silly» можно воспринимать как окно в память ввода/вывода, примерно как интерфейс /dev/mem. Используя драйвер, вы можете выполнять операции read, write и lseek в адресном пространстве ввода/вывода.

Так как наш драйвер должен обеспечивать доступ к памяти ISA, то он должен начать с отображения физической адресации ISA в виртуальное адресное пространство ядра. В старых версиях ядра Linux, мы могли просто назначить указатель на интересующий нас адрес ISA, и использовать прямое разыменование этого указателя. В современных ядрах, мы должны работать с системой виртуальных адресов, поэтому, для начала, мы должны отобразить требуемый диапазон адресов. Это отображение производится функцией ioremap(), как уже пояснялось для драйвера «short»:

Илон Маск рекомендует:  Директивы компилятора в Delphi

Функция ioremap() возвращает указатель, который мы можем использовать для функций семейства readb, рассмотренных в разделе «Прямо отображенная память».

Вернемся к коду нашего модуля для рассмотрения использования этих функций. Через файловый интерфейс /dev/sillyb, имеющий младший номер (номер устройства) равный 0, мы можем получить доступ к памяти ввода/вывода через функции readb() и writeb(). Следующий пример показывает реализацию чтения адресов в диапазоне 0xA0000-0xFFFFF, доступных как виртуальный файл с пространством адресов 0x0-0x5FFFF. Запрос на чтение структурирован в драйвере оператором switch для различных режимов доступа. Вариант для sillyb выглядит следующим образом:

Следующие два устройства /dev/sillyw (номер устройства 1) и /dev/sillyl (номер устройства 2) предоставляют доступ к нашему диапазону адресов используя 16-ти и 32-х битовые функции. Приведем в качестве примера реализацию записи для устройства sillyl (вариант оператора switch):

Последнее устройство /dev/sillycp (номер устройства 3) использует memcpy_*io() функции для выполнения тех же самых задач. Приведем пример реализации операции чтения:

Так как для получения доступа к адресному пространству ISA, мы использовали функцию ioremap(), то для снятия этого отображения адресов, при выгрузке модуля необходимо вызвать функцию iounmap():

Семейство функций isa_readb

Просматривая источники ядра, мы можем увидеть множество процедур семейства isa_readb. Т.е. каждая из рассмотренных нами функций чтения/записи пространства памяти ввода/вывода имеет свой isa_* эквивалент. Эти функции обеспечивают доступ к памяти ISA без предварительного отображения требуемых адресов через ioremap(). Однако, разработчики ядра предупреждают, что использование этих функций следует избегать в будущем, так как они могут не поддерживаться в следующих версиях ядра, и предназначены были только для экспериментальных целей доступа.

Поиск свободной области в памяти ISA

Хотя большинство современных устройств предпочитают более современные шинные архитектуры (например, PCI), однако, некоторым программистам приходится иметь дело с устройствами ISA, и их памятью ввода/вывода. Поэтому, мы посвятим еще одну страницу работе с этими устройствами. Мы не будем касаться верхней памяти ISA (так называемая «дыра» в физическом адресном пространстве в диапазоне 14-16МБт), так как в настоящее время этот тип памяти крайне редок, и не поддерживается большинством современных материнских плат и ядер операционных систем. Для доступа к этому диапазону памяти вам потребуется изменить последовательность инициализации ядра, что лежит за пределами данной книги.

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

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

Вспомним код нашего драйвера scull. Так как он не имеет прямого отношения к какому-либо физическому устройству, то он просто выводит информацию об используемости диапазона адресов от 640КБ до 1МБт. Код драйвера scull показывает, как может быть выполнен анализ занятости памяти,

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

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

Пример отображения: рисуем из модуля ядра

Данная глава является полностью дополнением переводчика и отсуствует в оригинальном источнике.

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

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

Приведу простейший пример реализации рисования из ядра:

Ниже приведен пример makefile для компиляции данного модуля под ядро 2.4.x. В ключе -I определите свой путь к заголовочным файлам ядра.

Если вы используете X-сервер в режиме 1280×1024 с двухбайтовой глубиной цвета, то при установке такого модуля в ядро часть вашего рабочего стола будет закрашено синим квадратом. На моем домашнем Celeron 1.7ГГц рисование одной точки занимает всего 60нс времени, с редким разбросом в одну наносекунду. Т.е потери таких интервалов времени не критичны для моего примера управления станками, зато процесс обработки становится более информативным.

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

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

Возникает следующая проблема. Как сделать так, чтобы, во-первых, ядро рисовало свои графики в теже самые окна, что и соответсвующие функции пространства пользователя, и, во-вторых, после завершения процесса ядра изображение в этих окнах стало «родным» с точки зрения интерфейсной части программы. Первая проблема решается путем передачи в ядро глобальных координат ваших графических окон, а вторая — путем передачи из ядра в пользовательское пространство всех тех данных, которые были отображены ядром в ваших окнах. Передачу данных можно выполнить как путем простого копирования данных из адресного пространства ядра в адресное пространство пользователя, используя семейство функций copy_to_user(), или путем переотображения блока данных в пространство пользователя. В моих программах объем накапливаемой истории обработки очень большой. Это может быть и 10, и 30 и 100 и более мегабайт, поэтому я использую механизм отбражения блока истории процесса в адресное пространство пользователя, для того, чтобы обеспечить возможность детального анализа всего того, что происходило в системе во время обработки. Это необходимо как для настройки системы, так и для анализа сбоев обработки.

Вопросы обратной совместимости

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

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

Кроме того, не все функции доступа к портам (семейство inb) были поддержаны для всех архитектур в старых ядрах. Строковые функции, например, были забыты. Мы не предложили замену для этих функций в заголовочном файле sysdep.h, так как это не простая задача, реализация которой, скорее всего, не принесет выгоды.

В ядре Linux 2.0 функции ioremap() и iounmap() назывались vremap() и vfree() соответственно. Передаваемые параметры и функциональность совпадали. Таким образом, пара определений, отображающих новые функции на старых двойников, будет вполне достаточно.

К несчастью, в то время как функция vremap() работает также как и ioremap() для обеспечения доступа к верхней («high») памяти (такой как, например, память на картах PCI), она не сможет отобразить диапазон памяти ISA. В те далекие времена, доступ к этой памяти выполнялся через разыменование прямых указателей, и не было необходимости для отображения этого адресного пространства. Таким образом, наиболее полное решение для реализации ioremap() для ядра Linux 2.0 на платформе x86 выглядит следующим образом:

Если вы включите заголовочный файл sysdep.h в код вашего драйвера, то вы сможете использовать функцию ioremap() без проблем, даже для доступа к памяти ISA.

Распределение областей памяти (семейство функций check_mem_region) было введено в ядре 2.3.17. В ядрах 2.0 и 2.2, не было централизованного механизма распределения ресурсов памяти. Включив sysdep.h в код драйвера, вы сможете использовать эти макросы — они аннулируются при компиляции для ядер 2.0 и 2.2.

Краткий справочник определений

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

#include void barrier(void) Такой «программный» барьер памяти заставляет компилятор полагать, что вся память после этой инструкции является «volatile» (изменяемой). #include void rmb(void); void wmb(void); void mb(void); Аппаратные барьеры памяти. Они требуют от процессора (и компилятора) устанавливать контроль всех операций чтения/записи после этой инструкции. #include unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); unsigned inl(unsigned port); void outl(unsigned doubleword, unsigned port); Эти функции используются для чтения и записи портов ввода/вывода. Они, также, могут быть вызваны из адресного пространства пользователя, при условии, что имеются права доступа к портам (см. функции ioperm() и iopl()). unsigned inb_p(unsigned port); Иногда, при работе с медленной шиной ISA на платформе x86, требуется небольшая временная задержка между операциями ввода/вывода. Для этой цели вы можете использовать шесть специальных функций (двойников функций из предыдущего пункта списка), которые реализуют такую задержку. Такие «pausing functions» (функции реализующие временную задержку между атомами данных во время передачи) имеют имена, заканчивающиеся на _p. void insb(unsigned port, void *addr, unsigned long count); void outsb(unsigned port, void *addr, unsigned long count); void insw(unsigned port, void *addr, unsigned long count); void outsw(unsigned port, void *addr, unsigned long count); void insl(unsigned port, void *addr, unsigned long count); void outsl(unsigned port, void *addr, unsigned long count); Строковые функции оптимизированы для передачи данных из портов в памяти и обратно. Такая передача выполняется чтением/записью порта count раз. #include int check_region(unsigned long start, unsigned long len); void request_region(unsigned long start, unsigned long len, char *name); void release_region(unsigned long start, unsigned long len); Функции, связанные с системой распределения портов ввода/вывода. Функция check_region() возвращает 0 в случае успеха, и значение меньшее нуля в противном случае. int check_mem_region(unsigned long start, unsigned long len); void request_mem_region(unsigned long start, unsigned long len, char *name); void release_mem_region(unsigned long start, unsigned long len); Функции связанные с системой распределения памяти. #include void *ioremap(unsigned long phys_addr, unsigned long size); void *ioremap_nocache(unsigned long phys_addr, unsigned long size); void iounmap(void *virt_addr); Функция ioremap() отображает диапазон физических адресов в виртуальное адресное пространство процессора, делая его доступным для ядра. Функция iounmap() снимает это отображение. #include unsigned readb(address); unsigned readw(address); unsigned readl(address); void writeb(unsigned value, address); void writew(unsigned value, address); void writel(unsigned value, address); memset_io(address, value, count); memcpy_fromio(dest, source, nbytes); memcpy_toio(dest, source, nbytes); Эти функции используются для доступа к областям памяти ввода/вывода, как к нижней памяти ISA, так и верхней памяти PCI.

Использование порта

Существуют две операции, которые могут выполняться с помощью порта: микропроцессор 8088 может послать информацию в подсоединенное устройство или прочитать данные из него. В языке Асемблера эти операции выполняются при помощи команд OUT и IN, а в языке Си использование указанных средств зависит от компилятора. Некоторые из них предоставляют возможность вызова специальных функций (в соответствии с тем, как это обычно делается в языке Си). В компиляторах Lattice С и Supersoft С, например с этой целью применяются функции outp( ) и inp( ), в других же аналогичные функции могут носить другие имена. Если вы работаете с компилятором, в котором такие возможности отсутствуют для задания указанных функций можно либо воспользоваться ассемблером, либо просто включить в свою программу соответствующий ассемблерный код (что очень просто). В любом случае вам необходимо ознакомиться с документацией по вашему компилятору. Пока же будем предполагать, что у вас имеется возможность вызова функций outp( ) и inp( ).

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


/* заставляет громкоговоритель подавать сигнал */

store = inp (97); /* запоминание начального значения с помощью порта 97 */

printf(«пopт 97 = %d «, store); /* проверка результатов*/

outp(97, 79); /* посылает 79 в порт 97; включение громкоговорителя */

outp(97, store); /* восстановление начального значения */

Несмотря на то что, по-видимому, вы и сами можете догадаться, что выполняют функции inp( ) и outp( ), ниже приведем их формальное описание:

inр(номер порта)

Эта функция возвращает (т. е. формирует) 8-разрядное целое значение (которое преобразуется в 16-разрядное число типа int путем добавления нулей слева), полученное из порта ввода с указанным номером. Обращение к ней не зависит от номера подключенного порта.

оuр(номер порта, значение)

Эта функция передает 8-разрядное целое значение в порт вывода с указанным номером.

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

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

/* более длинный сигнал */

#define LIMIT 10000

int count = 0; /* счетчик для организации задержки */

Программирование STM32. Часть 5: Порты ввода-вывода GPIO

В этой части мы разберемся с порами ввода-вывода GPIO микроконтроллера STM32F103C8 и напишем «Hello, World!» с мигающим светодиодом, а так же научимся читать состояние выводов микроконтроллера и использовать встроенный подтягивающий резистор. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1.

Введение

General-purpose input/output (GPIO) — важный компонент любого микроконтроллера, с помощью которого он взаимодействует с окружающим миром. В микроконтроллерах STM32 порты именуются буквами A, B, C и так далее: GPIOA, GPIOB, GPIOC... Каждый GPIO имеет 16 линий ввода/вывода, причем каждая линия может быть настроена независимо от других. Вот варианты настройки:

  • Input floating — вход с отключенными подтягивающими резисторами
  • Input pull-up — вход с подтяжкой к логической единице
  • Input-pull-down — вход с подтяжкой к логическому нулю
  • Analog — аналоговый вход (например, для АЦП)
  • Output open-drain — выход с открытым коллектором (записали 1 — выход в высокоимпедансном состоянии, записали 0 — выход прижат внутренним транзистором к земле)
  • Output push-pull — выход «тяни-толкай» (записали 1 — на выходе лог. 1, записали 0 — на выходе лог. 0)
  • Alternate function push-pull — альтернативная функция в режиме «тяни-толкай»
  • Alternate function open-drain — альтернативная функция в режиме открытого коллектора

Дам несколько пояснений по поводу режимов.

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

Alternate function. В этом режиме ножкой микроконтроллера управляет внутренняя цифровая периферия, например, модуль USART.

Регистры GPIO

Давайте рассмотрим регистры портов GPIO.

Port configuration register low (GPIOx_CRL)

Рис. 1. Регистр CRL

Это конфигурационный регистр для выводов порта с номерами от 0 до 7. Каждому выводу предоставлено 4-ре бита конфигурации: 2 бита MODEy и 2 бита CNFy.

MODEy[1:0]: Режим ножки порта, вход или выход. В режиме выхода нужно выбрать максимальную частоту переключения данной ножки, насколько понял это является оптимизацией энергопотребления порта.

  • 00: Вход (значение после сброса)
  • 01: Выход, максимальная частота 10 MHz.
  • 10: Выход, максимальная частота 2 MHz.
  • 11: Выход, максимальная частота 50 MHz.

CNFy[1:0]: Конфигурация режима.

В режиме входа (MODEy[1:0]=00):

  • 00: Analog mode — аналоговый режим (подключен к АЦП или ЦАП-у)
  • 01: Floating input — вход с отключенными подтягивающими резисторами (значение после сброса)
  • 10: Input with pull-up / pull-down — вход с подтяжкой вверх или вниз
  • 11: Reserved — не используется

В режиме выхода (MODEy[1:0]>00):

  • 00: General purpose output push-pull — выход в режиме тяни/толкай
  • 01: General purpose output Open-drain — выход с открытым коллектором
  • 10: Alternate function output Push-pull — выход альтернативной функции режиме тяни/толкай
  • 11: Alternate function output Open-drain — выход альтернативной функции с открытым коллектором

Port configuration register high (GPIOx_CRH)

Рис. 2. Регистр CRH

Это конфигурационный регистр для выводов порта с номерами от 8 до 15. Тут все то же, что и для регистра GPIOx_CRL.

Port input data register (GPIOx_IDR)

Рис. 3. Регистр IDR

IDRy: в этих битах содержится входное значение соответствующего порта ввода-вывода.

Port output data register (GPIOx_ODR)

Рис. 4. Регистр ODR

ODRy: выходные данные порта.

Port bit set/reset register (GPIOx_BSRR)

Рис. 5. Регистр BSRR

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

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

BSy: Установить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Устанавливает в единицу соответствующий бит ODRx

Port bit reset register (GPIOx_BRR)

Рис. 6. Регистр BRR

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

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

Port configuration lock register (GPIOx_LCKR)

Рис. 7. Регистр LCKR

Этот регистр используется для блокировки конфигурационных битов порта после записи корректной последовательности в 16 бит (LCKK) регистра. Значения битов [15:0] используется для блокировки конфигурации GPIO. Во время блокирующей последовательности в LCKK значения LCKR [15: 0] не должны меняться. Когда блокирующая последовательность была записана, конфигурация выбранных портов ввода/вывода может быть изменена только после сброса микроконтроллера. Каждый LCKy бит блокирует возможность изменения четырех битов конфигурации порта (CRL, CRH).

LCKK[16]: Ключ блокировки.

  • 0: Блокировка конфигурации порта не активна.
  • 1: Блокировка конфигурации порта активна. GPIOx_LCKR заблокирован до следующего сброса микроконтроллера.

Для блокировки необходимо выполнить следующую последовательность:

  • Записать 1
  • Записать 0
  • Записать 1
  • Прочитать 0
  • Прочитать 1 (эта операция чтения не является обязательной, а всего лишь подтверждает успешность установки блокировки)

LCKy: Эти биты могут быть прочитаны и записаны, но запись можно произвести только если бит LCKK равен нулю.

  • 0: Конфигурация пина номер y не заблокирована
  • 1: Конфигурация пина номер y заблокирована

Настройка порта GPIO

Итак, с регистрами разобрались, настало время практики. Все примеры в этой статье для микроконтроллера STM32F103C8. В моем распоряжении есть вот такая отладочная плата:

На ней установлен кварцевый резонатор на 8 МГц и светодиод на порту PB12. Вот с помощью этого светодиода мы и устроим Hello, World! ��

Задача ясна: настраиваем PB12 на выход в режиме push-pull и с помощью регистра ODR дергаем 12-й пин порта GPIOB туда-сюда! Но мы забыли об одной маленько детали: RCC. Дело в том, что по-умолчанию после сброса микроконтроллера все периферийные модули отключены от источника тактового сигнала, в том числе и GPIO. А подать тактирование можно с помощью регистров RCC. В 3-ей части я про это говорил. Для начала нужно определить, к какой шине у нас подключен GPIOB. Открываем даташит на микроконтроллер, ищем вот эту таблицу:

Рис. 8. Таблица шин и периферийных устройств

GPIOB у нас подключен к шине APB2. Идем в Reference manual, открываем раздел про RCC, переходим к пункту 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). С помощью этого регистра можно подать тактовый сигнал на устройства шины APB2:

Рис. 9. Регистр RCC_APB2ENR

В регистре RCC_APB2ENR много флагов для разной периферии, в том числе и для нашего GPIOB, флаг называется IOPBEN. Перед началом инициализации PB12 нам надо установить этот бит в единицу.

Конфигурирование портов ввода/вывода

Как уже было отмечено, порты имеют всего две возможности по кон­фигурированию:

• задание направления передачи данных (вход или выход);

• подключение/отключение внутреннего подтягивающего резистора.

Направление передачи данных определяется содержимым регистра пе­редачи данных DDRx. Если разряд DDRxn этого регистра установлен в «1», соответствующий n-й вывод порта является выходом. Если же разряд DDRxn этого регистра сброшен в «0», соответствующий вывод порта явля­ется входом.

Управление подтягивающим резистором осуществляется с помо­щью регистра данных порта PORTx. Если разряд Рхп регистра PORTx уста­новлен в «1» и соответствующий вывод порта является входом, между этим выводом и проводом питания подключается подтягивающий резистор. Что­бы отключить подтягивающий резистор, необходимо либо сбросить соот­ветствующий разряд регистра PORTx, либо сделать вывод порта выходом.

В табл.12.1 приведены все возможные конфигурации выводов портов ввода/вывода. При рассмотрении таблицы рекомендуется еще раз обратить внимание на схе­му, представленную на рис.5.1.

Таблица 12.1

Влияние регистров DDRx и PORTx на конфигурацию выводов портов

DDRxn Pxn Функция вывода Резистор Примечание
Вход Отключен Третье состояние (Z)
Вход Подключен При подключении вывода к общему проводу он является источником тока
Выход Отключен Выход установлен в «0»
Выход Отключен Выход установлен в «1»

1) n = 7. 0 — номер вывода (разряд порта).

2) Если вывод используется каким-либо периферийным ус­тройством микроконтроллера, то соответствующие этому выводу разряды регистров DDRx и PORTx, как правило, должны быть установлены в соот­ветствии с дополнительной функцией вывода. Однако некоторые перифе­рийные устройства при активизации самостоятельно переопределяют кон­фигурацию используемых выводов. Об этом упомянуто при описании конкретных периферийных устройств.

Таблица 12.2

Альтернативные функции линий портов

Вывод Альт. функция Описание
PORTA
PA0 ADC0 Вход АЦП, канал 0
PA1 ADC1 Вход АЦП, канал 1
PA2 ADC2 Вход АЦП, канал 2
PA3 ADC3 Вход АЦП, канал 3
PA4 ADC4 Вход АЦП, канал 4
PA5 ADC5 Вход АЦП, канал 5
PA6 ADC6 Вход АЦП, канал 6
PA7 ADC7 Вход АЦП, канал 7
PORTB
PB0 T0 Счетный вход таймера-счетчика 0
PB1 T1 Счетный вход таймера-счетчика 1
PB2 AIN0 Прямой вход аналогового компаратора
PB3 AIN1 Инверсный вход аналогового компаратора
PB4 SS Вход выбора микросхемы для интерфейса SPI
PB5 MOSI Выход Master’а и вход Slave’а для SPI
PB6 MISO Вход Master’а и выход Slave’а для SPI
PB7 SCK Выход тактовых импульсов для SPI
PORTC
PC0
PC1
PC2
PC3
PC4
PC5
PC6 TOSC1 Входы для подключения кварцевого резонатора при работе таймера-счетчика 2 в асинхронном режиме
PC7 TOSC2
PORTD
PD0 RXD Вход приемника UART
PD1 TXD Выход передатчика UART
PD2 INT0 Вход запроса внешнего прерывания 0
PD3 INT1 Вход запроса внешнего прерывания 1
PD4 OC1B Выход схемы сравнения В таймера-счетчика 1
PD5 OC1A Выход схемы сравнения А таймера-счетчика 1
PD6 ICP Вход на схему захвата таймера-счетчика 1
PD7 OC2 Выход схемы сравнения таймера-счетчика 2

Не нашли то, что искали? Воспользуйтесь поиском:

Inp, inpw ввод из порта

Код входа (Input)

Иконка Input проверяет(слушает) указанный порт и/или бит(ы) и помещает полученное значение (BYTE) в указанную переменную.

Display Name
Название иконки Input

Variable
Имя переменной, которой будет присвоено значение из регистров порта.

Variables кнопка
Кнопка вызова списка заранее заготовленных имен переменных. Так же позволяет создать новую переменную.

Port
Выбор порта, который будет проверяться.

Single Bit
Можно выбрать не весь порт, а только один регистр этого порта. Остальные регистры могут быть использованы по другим назначениям. Удобно, когда порт считает какие нибудь импульсы и одновременно контролирует нажатие кнопок.

Entire Port
Контролируется весь порт.

Use Masking
Используя маску. Можно выбрать несколько регистров порта для контроля сигнала в них. Удобно, когда кнопок несколько. Например 3 кнопки подключены к регистрам 0-2, а остальные рагистры могут контролировать другие входные данные.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL