какой функцией в программе можно назначить выводу порт ввода
Порты ввода-вывода общего назначения могут:
использоваться просто как порты на выход и вход, например, логических «0» и «1»;
иметь дополнительные, например, аналоговые функции для работы с АЦП;
использоваться аппаратно для реализации различных интерфейсов взаимодействия, например UART, SPI и т.д.
Количество портов в каждом процессоре разное, как и распределение по ним дополнительных функций. Информацию по данному вопросу необходимо смотреть в спецификации в разделе «Порты ввода вывода MDP_PORTx»
Работа порта рассмотрена в статье Схемотехника портов GPIO.
Управление портами на примере функций SPL
Функции SPL реализуют настройку и управление портами через именованные поля, структуры и значения. Каждый параметр настройки имеет свое отдельное поле в структуре, в большинстве своем имеет перечислимый тип значения этого поля и краткое описание в комментариях. Это очень удобно и позволяет избежать ошибок при работе. Кроме этого, одна и та же функция SPL реализует одинаковое поведение для всех поддерживаемых SPL процессоров. То есть поля в регистрах или сами регистры, их адреса могут отличаться от процессора к процессору, но функция, вызванная с одинаковым аргументом, выполнит одинаковое действие. Таким образом реализуется стандартное поведение функций при различии реализации в железе.
Минусом использования SPL является большой размер программы (прошивки) и сниженное быстродействие. При этом сводится к минимуму возможность совершить ошибку. Поэтому для освоения программирования рекомендуется начинать работу с функциями SPL, добиваться работоспособности проекта, а затем модифицировать критические куски кода к регистровому исполнению.
Инициализация порта
// Инициализируем структуру конфигурации вывода(-ов) порта значениями по умолчанию
PORT_StructInit(&GPIOInitStruct);
Функция PORT_StructInit заполняет структуру конфигурации порта значениями по умолчанию:
void PORT_StructInit(PORT_InitTypeDef* PORT_InitStruct)
<
/* Reset PORT initialization structure parameters values */
PORT_InitStruct->PORT_Pin = PORT_Pin_All;
PORT_InitStruct->PORT_OE = PORT_OE_IN;
PORT_InitStruct->PORT_PULL_UP = PORT_PULL_UP_OFF;
PORT_InitStruct->PORT_PULL_DOWN = PORT_PULL_DOWN_OFF;
PORT_InitStruct->PORT_PD_SHM = PORT_PD_SHM_OFF;
PORT_InitStruct->PORT_PD = PORT_PD_DRIVER;
PORT_InitStruct->PORT_GFEN = PORT_GFEN_OFF;
PORT_InitStruct->PORT_FUNC = PORT_FUNC_PORT;
PORT_InitStruct->PORT_SPEED = PORT_OUTPUT_OFF;
PORT_InitStruct->PORT_MODE = PORT_MODE_ANALOG;
>
PORT_Pin
PORT_OE
PORT_PULL_UP и PORT_PULL_DOWN
Параметры PORT_PULL_UP и PORT_PULL_DOWN задают подтяжку вывода к линии питания или земли. Обычно это используется, когда вывод используется как вход. Сопротивление резистора подтяжки составляет порядка
50КОм, точное значение необходимо уточнять в спецификации на микроконтроллер.
Очень часто значения являются определенными битовыми масками, которые в итоге должны расположиться по нужному смещению в регистре. Поэтому PORT_PULL_UP_OFF не равно PORT_PULL_DOWN_OFF, и важно их не путать.
PORT_PD_SHM
Это поле может принимать следующие значения, при PORT_PD_SHM_OFF dU = 200 мВ, а при PORT_PD_SHM_On = 400 мВ.
PORT_PD
В режиме PORT_PD_OPEN вывод отключается от транзистора коммутирующего его с цепью питания, то есть мы может только просаживать внешнюю линию на 0, а при записи 1 это «просаживание» отключается. В таком режиме подразумевается, что используется внешняя подтяжка к питанию, как это происходит например на шине I2C.
PORT_GFEN
PORT_MODE
Аналоговый режим используется, например, когда через вывод заводится аналоговый сигнал для оцифровки внутренним АЦП. При выводе аналогового сигнала от встроенного ЦАП используется такой же режим.
PORT_FUNC
Вывод | Analog | DIGITAL | |||
---|---|---|---|---|---|
PORT | MAIN | ALTER | OVERRID | ||
РЕ12 | — | РЕ12 | ADDR28 | SSP1_RXD | UART1_RXD |
В аналоговом режиме данный порт не имеет применения.
Если для разных выводов выбрать одну и ту же функцию, например, TMR2_CH2, то реально будет задействован только один вывод, причём выбор активного осуществляется в порядке приоритета по названию порта, т.е. сначала порт А, далее порт B и т.д.
PORT_SPEED
Параметр PORT_SPEED определяет время переключения логических состояний, по существу, длительность фронтов. Возможные варианты:
При быстрых фронтах возрастает потребление микроконтроллера, но зато можно увеличить скорость работы с подключенной периферией. Для мигания светодиодом скорость не важна и лучше использовать вариант с PORT_SPEED_SLOW.
Управление портами через регистры
SPL делает настройку портов более читаемой и защищает от случайных ошибок. В конечном итоге функции SPL задают параметры в регистры настроек выводов портов. Эти регистры представлены на следующей картинке, описание же находится в Схемотехника портов GPIO.
Электроника для всех
Блог о электронике
Работа с портами ввода-вывода микроконтроллеров на Си++
При разработке программ для микроконтроллеров (МК) работа с внутренний и внешней периферией является очень важной частью (а иногда и единственной) программы. Это своего рода фундамент, на котором основывается более высокоуровневая логика программы. От эффективности взаимодействия с периферией напрямую зависит эффективность программы в целом. Под эффективностью здесь следует понимать не только скорость выполнения и минимальный размер кода, но и эффективность написания и сопровождения кода.
Многие внешние устройства подключаются к МК через порты ввода-вывода общего назначения (GPIO). Эффективность взаимодействия с этими устройствами во многом зависит от способа работы с портами ввода-вывода.
Тут возникают два, на первый взгляд, противоречивых требования:
Ситуация осложняется огромным количеством способов подключения одного внешнего устройства к одному контроллеру и в большинстве случаев определяется удобством изготовления печатной платы наличием свободных линий в порту, необходимостью использовать некоторые линии портов для их специальных возможностей и т.д. Устройство может быть подключено как к одному порту, так и к нескольким, линии порта могут быть как упорядочены по их логическому весу в подключенном устройстве, так и нет. Оно вообще может быть подключено через какой-нибудь расширитель ввода-вывода, например, сдвиговый регистр. При таком разнообразии очень сложно, чтобы драйвер устройства не был заточен под конкретный способ подключения этого устройства, или-же не был слишком громоздким и медленным в случае более универсального решения.
Кажется, что найти универсальное и эффективное решение здесь невозможно. Очень часто в пользу скорости и компактности кода жертвуют и универсальностью и логической структурой программы и как следствие читаемостью кода, просто не отделяя способ подключения устройства от логики его работы. При этом код драйвера модифицируется и подгоняется вручную под каждый проект, под каждый способ подключения.
Давайте разберемся, какие методы работы с портами ввода-вывода традиционно применяются при программировании на чистом Си.
Можно выделить следующие подходы (вариант с жестко заданными в коде именами портов и номерами ножек не рассматриваем):
Примеры приведены для МК семейства AVR. Компилятор avr-gcc, но описываемые подходы могут быть применены к любым другим МК, для которых имеется стандартный Си/Си++ компилятор.
1. Препроцессор
Способов использования препроцессора для работы с портами в МК существует великое множество. В самом простом и самом распространенном случае просто объявляем порт и номера ножек, к которым подключено наше устройство с помощью директивы #define, не забыв, конечно, про DDR и PIN регистры, если они нужны.
Нет ничего проще, чем помигать светодиодом, подключенным к одному из выводов МК:
reg->data_pin_bm; //тактовый импульс *reg->port |= reg->clk_pin_bm; value >>= 1; *reg->port &=
reg->clk_pin_bm; > //защёлкиваем данные в регистр *reg->port |= reg->latch_pin_bm; *reg->port &=
Фрагмент ассемблерного листинга WriteShiftReg
LD r30, X+ LD r31, X SBIW r26, 0x01 ; 1 LD r24, Z ADIW r26, 0x04 ; 4 LD r25, X SBIW r26, 0x04 ; 4 OR r24, r25 ST Z, r24
Как видно, компилятор не может теперь сопримизировать обращение к порту и установка бита теперь занимает 9 инструкций вместо одной.
Чтобы несколько оптимизировать код можно ввести дополнительные ограничения, например, задать номера ножек константами, и выкинуть соответствующие им битовые маски. В примере со сдвиговым регистром можно заменить константами data_pin_bm и clk_pin_bm и исключить их из структуры, а latch_pin_bm оставить как есть для универсальности:
>data_pin_bm; //тактовый импульс *reg->port |= clk_pin_bm; value >>= 1; *reg->port &= clk_pin_bm; > //защёлкиваем данные в регистр *reg->port |= reg->latch_pin_bm; *reg->port &=
Такая оптимизация сократит код WriteShiftReg примерно на 25 % с незначительной потерей в удобстве.
3. Виртуальные порты
Нужно подключить к МК несколько устройств требующих достаточно много линий ввода-вывода, драйвер которых обладает достаточно сложной и объёмной логикой, например, тот-же дисплей HD44780 (при использовании 4х битного интерфейса требует 7 линий). К тому-же устройства могут быть подключены различными способами – к разным линиям портов, или через сдвиговый регистр. Дублировать код драйвера и подгонять его под каждый способ подключения устройства – нет уж, спасибо. Да и размер скомпилированного кода рискует не поместится в целевой МК. Передавать порты драйверу в через указатели? Слишком большие накладные расходы при работе с портами через указатели, много памяти, медленно и громоздко.
Здесь лучше применить, так называемые виртуальные порты. На языке Си они могут быть реализованы как группа функций, принимающих входное значение и выполняющих соответствующие операции ввода-вывода:
Теперь становится не важно как именно подключен дисплей к МК. Всё тяжёлая работа по манипуляции портами возложена на функцию VPort1Write. Для каждого способа подключения дисплея нам нужно только написать соответствующую реализацию виртуального порта, тут уже насколько фантазии хватит. Используя, например, уже написанную функцию WriteShiftReg, легко подключить дисплей через сдвиговый регистр:
ShiftReg reg1 = <&PORTA, 1>; void VPort2Write(uint8_t value)
Чего-же мы добились с помощью виртуальных портов:
Подход Си++ к работе с портами.
Что может нам предложить язык Си++ при работе с портами ввода-вывода по сравнению чистым Си? Давайте сначала сформулируем, что мы хотим получить в результате наших изысканий:
От динамической конфигурации линий ввода-вывода сразу отказываемся из-за необходимости доступа к портам через указатель со всеми вытекающими последствиями.
Удобно было бы описать линию ввода-вывода в виде отдельной сущности, т.е. класса. В Си++ даже если в классе не объявлено ни одного поля, переменная этого класса всё равно будет иметь размер как минимум один байт, потому, что переменная должна иметь адрес. Значит, нам не надо создавать объекты этого класса, а все функции в нем сделать статическими. А как тогда различать разные линии? Можно сделать этот класс шаблоном, а порт и номер бита передавать в виде параметров шаблона. С номером бита всё ясно – это целое число, его легко передать как нетиповой параметр шаблона. А как быть с портом? Посмотрим, как определены порты ввода-вывода в заголовочных файлах avr-gcc:
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr)) #define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr + __SFR_OFFSET) #define PORTB _SFR_IO8(0x18)
Параметры шаблона могут быть только типовыми или целочисленными константными выражениями, вычисляемыми во время компиляции. Ни указатель, ни ссылку нельзя предать как параметр шаблона:
template //ошибка class Pin <. >;
Может быть попробовать передавать адрес порта в виде целого числа, а потом его преобразовывать в указатель:
Взять адрес PORTB по имени не получится потому, что операция взятия адреса не может появляться в константных выражениях, коими должны быть параметры шаблона:
typedef Pin Pin1; // ошибка
Однако, нам нужно передавать не только адрес порта, ещё нужны PINx и DDRx регистры. К тому-же, в таком виде о переносимости не может быть и речи. Можно, конечно, написать макрос, которому передаём соответствующие имена регистров, и он генерирует соответствующий класс. Но тогда Pin будет слишком жестко завязан на конкретную реализацию портов.
Можно написать перечисление в котором объявлены все порты с удобочитаемыми именами и функцию, которая возвращает нужный порт в зависимости от переданного параметра шаблона.
() < return PORTA; >template<> volatile uint8_t & GetPort
Постой пример его использования:
Вызов Reg1::Write компилируется в следующий ассемблерный листинг:
Сгенерированный листинг не уступает написанному вручную на ассемблере. И кто говорит, что Си++ избыточен при программировании для МК? Попробуйте переписать этот пример на чистом Си, сохранив чистоту и понятность кода, разделение логики работы устройства от конкретной реализации портов В/В и такую-же эффективность.
Списки линий ввода-вывода
Это только начало. Теперь нам предстоит самое интересное — реализовать эффективный вывод многобитных значений. Для этого нам нужна сущность объединяющая группу линий В/В – своеобразный список линий В/В. Поскольку и порты и отдельные линии у нас представлены различными классами, то логично реализовывать список линий с помощью шаблонов. Но здесь есть одна проблема: список линий может содержать различное число линий, а шаблоны в Си++ имеют фиксированное число параметров (а стандарте Cxx03, в следующей версии появятся Variadic templates). Нам поможет библиотека Loki, написанная Андреем Александреску. В ней реализовано множество шаблонных алгоритмов для манипуляций со списками типов произвольной длинны. Это нам подойдёт – списки типов превращаются в списки линий ввода-вывода. Что, собственно, такое списки типов лучше всего почитать у их автора Андрея Александреску в книге Современное проектирование на С++. Очень рекомендую прочитать, хотя-бы мельком, главу «Списки типов» в этой книге. Без этого будет мало понятно, что происходит дальше.
Не во всех МК предусмотрены команду для манипуляций с отдельными битами в портах В/В. В семействе MegaAVR тоже есть порты для которых недоступны битовые операции. Поэтому чтобы сделать операции с портами максимально эффективными нам нужно отказаться от побитового вывода – одно чтение, модификация значения и запись.
То есть нужно записать N битов из входного значения в N битов произвольно расположенных в нескольких портах В/В. Или по другому говоря, сгруппировать записываемые биты по портам и вывести их за раз.
В упрощенном виде алгоритм записи значения в произвольный список линий В/В будет выглядеть так:
1. Определить список портов к которых подключены линии из списка.
2. Для каждого порта:
Выглядит всё это очень сложно. Когда мы пишем реализацию виртуальных портов на Си, то все эти операции проделываем вручную, а сейчас наша задача заставить компилятор выполнять эту работу. Для этого в нашем распоряжении есть списки типов и техника шаблонного метапрограммирования. Пусть компилятор сам тасует биты и считает битовые маски! Приступим.
У каждой линии в списке есть два номера:
Оба они понадобятся для того, чтобы спроецировать биты из входного значения в порт.
Второй номер класс TPin помнит сам, оно хранится в enum-е:
Чтобы запомнить первый номер понадобится дополнительный шаблон:
Хотя можно было этого и не делать, а вычислять битовыю позицию потом с помощью алгоритма IndexOf.
Этот шаблон хранит тип линии В/В и ее битовую позицию в списке. Теперь приступим к генерации собственно списка линий. Для определённости ограничим длину списка 16-ю линиями, ели надо можно добавить и больше, потом. Для этого возьмём шаблон MakeTypelist из библиотеки Loki и модифицируем его под свои нужды:
В результате на выходе имеем «голый» список типов наших линий ввода вывода. Мы уже можем объявить список из нескольких линий, сделать с ним пока ничего нельзя – для него не определены никакие операции:
Зато его можно передать в другой шаблон как один параметр. Воспользуемся этим и напишем класс реализующий операции с этим списком:
Далее напишем алгоритм для преобразования списка линий в список соответствующих им портам:
//шаблон принимает список линий в качестве параметра template struct GetPorts; // для пустого списка результат – пустой тип template <> struct GetPorts < typedef NullType Result; >; // для непустого списка // конкретизируем, что это должен быть список типов // содержащий голову Head и хвост Tail template struct GetPorts > < private: // класс TPin помнит свой порт // запоминаем этот тип порта typedef typename Head::Pin::Port Port; //рекурсивно генерируем хвост typedef typename GetPorts ::Result L1; public: // определяем список портов из текущего порта (Port) и хвоста (L1) typedef Typelist
Теперь мы можем конвертировать список линий в соответствующий список портов, однако один и тот-же порт может содержаться в нем несколько раз. Нам нужны не повторяющиеся порты, по этому воспользуемся алгоритмом NoDuplicates из библиотеки Loki:
// конвертируем список линий в соответствующий список портов typedef typename GetPorts
::Result PinsToPorts; // генерируем список портов без дудликатов typedef typename NoDuplicates
Чтобы организовать рекурсивный проход по списку портов понадобится еще один шаблон класса. Назовём его PortWriteIterator. В качестве шаблонных параметров он принимает список портов и исходный список линий:
template struct PortWriteIterator;
В этом классе и будет находиться реализация операций с отдельными портами. Определим специализацию этого класса для пустого списка линий.
Далее необходимо выбрать из списка линий, те которые принадлежат определённому порту.
Теперь вычислим битовую маску для порта.
//Параметр TList должен быть список линий template struct GetPortMask; // Для пустого списка возвращаем 0. template <> struct GetPortMask < enum
Теперь напишем реализацию для функции записи в порт:
::Result Pins; // Посчитаем битовую маску для порта enum ::UppendValue(value); // если кол-во бит в записываемом значении совпадает с шириной порта, // то записываем порт целиком. // это условие вычислится во время компиляции if((int)Length ::value == (int)Port::Width) Port::Write(result); else < // PORT = PORT & Mask | result; Port::ClearAndSet(Mask, result); >// рекурсивно обрабатываем остальные порты в списке PortWriteIterator ::Write(value); > > Функция PinWriteIterator::UppendValue отображает биты во входном значении в соответствующие им биты в порту. Эффективность списков линий в целом зависит в первую очередь именно от реализации этого отображения, поскольку оно, в общем случае, должно выполняться динамически и не всегда может быть вычислено во время компиляции. В зависимости от распределения записываемых бит в результирующем порту применим несколько стратегий отображения бит: Для определения того, что линии в порту расположены последовательно напишем следующий шаблон. С учётом всего выше написанного класс PinSet будет выглядеть так: ::Result PinsToPorts; public: typedef PINS PinTypeList; // генерируем список портов без дудликатов typedef typename NoDuplicates ::Result Ports; // длинна списка линий enum ::value>; // выбираем тип данных записываемый в список линий // если длинна списка меньше или равна 8 берём тип uint8_t, // если больше – uint16_t typedef typename IoPrivate::SelectSize ::Result DataType; //записать значение в список линий static void Write(DataType value) < PortWriteIterator Собственно списки линий уже должны работать: Однако, пользоваться ими пока не очень удобно. Поэтому сделаем вокруг нашей реализации списков линий прозрачную и удобную обёртку: Теперь можно объявлять списки линий следующим образом: Стало достаточно удобно – можно один раз объявить такой список линий, а потом использовать его где угодно, передавать его как шаблонный параметр в классы и функции реализующие управление периферией. А для того, чтобы изменить способ подключения достаточно подправить только одну строку. Настало время протестировать списки линий с различными конфигурациями и на разных МК. //вывод в PORTA in r24, 0x1b andi r24, 0xF1 ori r24, 0x0A out 0x1b, r24 //вывод в PORTB in r24, 0x18 andi r24, 0xE7 ori r24, 0x10 out 0x18, r24 Как видно, компилятору все значения были известны и он благополучно посчитал все битовые маски и логические операции, не оставив ничего лишнего на время выполнения. А как он поведёт себя если записываемое значение не известно во время компиляции? Рассмотрим следующий пример (список линий тот-же самый): // MCU AtMega16 MyPins::Write(PORTC); // читаем PORTC in r18, 0x15 ; 21 //вывод в PORTA in r25, 0x1b ; 27 mov r24, r18 add r24, r24 andi r24, 0x0E ; 14 andi r25, 0xF1 ; 241 or r24, r25 out 0x1b, r24 ; 27 //вывод в PORTB in r24, 0x18 ; 24 andi r18, 0x18 ; 24 andi r24, 0xE7 ; 231 or r18, r24 out 0x18, r18 ; 24 В качестве значения неизвестного используем PORTC. Результат впечатляет, неправда-ли? Даже на ассемблере сложно написать эффективнее. К тому-же если вручную считать все эти битовые маски, то очень велик риск ошибиться. // читаем PORTC.IN lds r18, 0x0648 mov r25, r18 add r25, r25 andi r25, 0x0E ; 14 //вывод в PORTA ldi r30, 0x00 ; 0 ldi r31, 0x06 ; 6 ldi r24, 0x0E ; 14 std Z+6, r24 ; 0x06 std Z+5, r25 ; 0x05 andi r18, 0x18 ; 24 //вывод в PORTB ldi r30, 0x20 ; 32 ldi r31, 0x06 ; 6 ldi r24, 0x18 ; 24 std Z+6, r24 ; 0x06 std Z+5, r18 ; 0x05 Как видно, говорить об избыточности и накладных расходах, которые может повлечь использование Си++ здесь не приходится. Аналогичным образом реализуем запись направления линий. Единственное отличие это то, что теперь значение записывается в регистры управления направлением линий (DDRx). ::Write(value); > //запись направления static void DirWrite(DataType value) < PortWriteIterator И соответствующие дополнения в класс PortWriteIterator. ::value == (int)Port::Width) Port::DirWrite(result); else < Port::DirClear(Mask); Port::DirSet(result); >PortWriteIterator ::DirWrite(value); > >; Для полноты добавим, аналогичным образом операции Set, Clear, DirSet, DirClear для установки и обнуления битов соответственно в регистрах порта и регистрах направления. Остаётся реализовать чтение состояния порта. Это несколько проще, чем запись: Подробно на чтении останавливаться не будем. Итак у нас уже есть средство для эффективной манипуляции портами ввода-вывода МК. Но ведь задача была изначально шире – полностью изолировать драйвера от с способа подключения устройств. И эта цель уже достигнута. Класс TPin, реализующий линию ввода-вывода, как и списки линий полностью изолированы от реализации портов В/В. Это было видно на том примере, что у нас уже реализованы порты как для семейств Tiny/Mega AVR, так и для XMega. Так вот, достаточно написать класс, реализующий интерфейс порта В/В, и списки линий будут с ним прекрасно работать. Для примера возьмём, уже рассмотренный ранее, сдвиговый регистр и будем его использовать для расширения количества доступных линий В/В. template class ThreePinLatch < public: typedef T DataT; // нужен для сортировки портов в списках линий enum clearMask) | value); > static void Set(DataT value) < Write(_currentValue |= value); >static void Clear(DataT value) < Write(_currentValue &= value); > static void Togle(DataT value) < Write(_currentValue ^= value); >static void DirWrite(DataT value) < /*не можем менять направление*/ >static DataT DirRead() < return 0xff; //всегда выход >static void DirSet(DataT value)*> static void DirClear(DataT value)*> static void DirTogle(DataT value)*> protected: // текущее значение в регистре static DataT _currentValue; >; template T ThreePinLatch ::_currentValue = 0; Мы не можем прочитать состояние выходных линий регистра – он всегда работает на выход, поэтому функцию чтения состояния не реализуем и не объявляем. Попытка прочитать состояние такого пора вызовет ошибку компиляции. Зато на запись нет никаких ограничения. И мы свободно можем использовать этот «порт» со списками линий. Причем нет никаких ограничений на состав списка линий – можно смешивать в одном списке линии сдвигового регистра и линии обычных портов. В наших целях значится не только работа с многобитными значениями, но и манипуляции отдельными битами, поэтому добавим в интерфейс списков линий функциональность для этого. Во первых, это доступ к отдельным линиям из списка. Для этого в класс PinSet допишем следующий код: template class Pin :public TypeAt Здесь мы воспользовались алгоритмом TypeAt из библиотеки Loki, который возвращает из списка типов указанную в параметре позицию. Пользоваться этим достаточно просто: Обобщая доступ к отдельным битам в списке линий приходим к концепции среза. Не буду вдаваться в подробности их реализации, приведу лишь пример использования: // берём срез из MyPins начиная с 4-го бита, длинной 4 бита. typedef MyPins::Slice OtherPins; В результате получаем новый список линий OtherPins, который будет содержать: Этот механизм удобен для того, чтобы изменить значения отдельной группы линий, не изменяя остальных. Компилятор для этого случая может сгенерировать более эффективный код. В приведенном примере вызов OtherPins::Write(что-нибудь) запишет значение в 4 бита PORTB, не затронув линии в сдвиговом регистре. Подведём итоги © Константин Чижов Спасибо. Вы потрясающие! Всего за месяц мы собрали нужную сумму в 500000 на хоккейную коробку для детского дома Аистенок. Из которых 125000+ было от вас, читателей EasyElectronics. Были даже переводы на 25000+ и просто поток платежей на 251 рубль. Это невероятно круто. Сейчас идет заключение договора и подготовка к строительству! А я встрял на три года, как минимум, ежемесячной пахоты над статьями :)))))))))))) Спасибо вам за такой мощный пинок. О_о 🙂
Выше приведённый пример компилируется в следующий ассемблерный листинг:
Рассмотрим тот-же пример скомпилированный для AtXMega128a1. В этом МК порты В/В находятся в расширенном пространстве ввода-вывода и недоступны для операций in и out. Реализация портов, учитывающая особенности семейства XMega у нас уже написана.
Цели поставленные перед реализацией ввода-вывода для МК на Си++ были достигнуты в полной мере. Достигнута как эффективность манипуляций с многобитовыми значениями, так и отдельными линиями ввода-вывода. Списки линий, как и код их использующий, полностью изолированы от конкретной реализации портов. Чтобы добавить новый способ подключения (например через SPI расширитель портов) устройства использующего списки линий, или портировать его на другое семейство МК, достаточно написать соответствующий класс реализующий интерфейс порта ввода-вывода.
Единственными существенными недостатками этого подхода является относительная сложность реализации списков линий (но ведь они уже написаны) и необходимость изучения языка Си++.
Email: klen1@mail.ru Сентябрь 201086 thoughts on “Работа с портами ввода-вывода микроконтроллеров на Си++”
Тёска:)
Ппц такая большая статья для работы с портами ВВ))