Виртуальный порт. Макросы для управления выводами микроконтроллера

Введение

   Выводы микроконтроллеров AVR сгруппированы в порты. Как правило порты состоят из восьми выводов, но также они могут быть и урезанными, как, например, у некоторых микроконтроллеров семейства Tiny. Порт позволяет одновременно выполнять операции над всеми выводами, входящими в его состав. Это такие операции как установка направления передачи данных, установка логических уровней на выводах и чтение состояния выводов. 
   Многие выводы микроконтроллеров AVR помимо основного назначения, имеют дополнительные функции, которые часто востребованы. Это обстоятельство осложняет использование порта в качестве параллельной шины данных, а иногда и делает полностью невозможным. 

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

   В этом материале пойдет речь о программных виртуальных портах.

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

Особенности данных макросов

- быстрый и компактный код,
- позволяют объединять выводы разных физических портов в виртуальные порты,
- позволяют работать с обычными портами,
- позволяют создавать порты с количеством выводов от 1 до 8,
- количество виртуальных и "копий" реальных портов неограниченно.

Подключение к проекту

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

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

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

//описание порта
#define псевдоним_порта имя_порта, буква_порта, тип_порта

//описание выводов
#define имя_порта_0 буква_порта, номер_вывода, активный_уровень
#define имя_порта_1 буква_порта, номер_вывода, активный_уровень
#define имя_порта_2 буква_порта, номер_вывода, активный_уровень
#define имя_порта_3 буква_порта, номер_вывода, активный_уровень
#define имя_порта_4 буква_порта, номер_вывода, активный_уровень
#define имя_порта_5 буква_порта, номер_вывода, активный_уровень
#define имя_порта_6 буква_порта, номер_вывода, активный_уровень
#define имя_порта_7 буква_порта, номер_вывода, активный_уровень

Псевдоним_порта - произвольное имя, которое используется в макросах при обращении к порту.

Имя_порта - произвольное имя, которое используется в описании выводов порта.

Буква_порта
- для витуального порта это произвольная буква, так как в макросах она игнорируется. Для реального порта это его действительное буквенное обозначение. Например, A, B, C и т.д.

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

Имя порта_0.. имя_порта_7 - описание выводов порта. Их должно быть всегда 8 штук, но с помощью атрибута "активный_уровень" некоторое выводы могут быть исключены из порта.

Буква_порта
- соответствует букве реального порта, к которому относится описываемый вывод.

Номер_вывода
- принимает значение от 0 до 7 и соответствует номеру вывода реального порта.

Активный_уровень
- может принимать значения _HI, _LOW и _NONE. Первые два атрибута используются только в макросах включения/выключения. Последний может использоваться для исключения некоторых выводов из состава порта, как виртуального, так и реального.

Чтобы было понятно все выше описанное, рассмотрим пример.

//описываем виртуальный порт
#define LED_PORT LED, F, _VIRT

//описываем выводы виртуального порта
#define LED_0 D, 0, _HI
#define LED_1 D, 1, _HI
#define LED_2 D, 2, _HI
#define LED_3 A, 2, _HI
#define LED_4 A, 3, _HI
#define LED_5 A, 4, _HI
#define LED_6 B, 4, _HI
#define LED_7 B, 5, _HI

//описываем реальный порт С
#define LED_PORT2 LED2, C, _REAL

//описываем выводы реального порта
#define LED2_0 C, 0, _HI
#define LED2_1 C, 1, _HI
#define LED2_2 C, 2, _HI
#define LED2_3 C, 3, _HI
#define LED2_4 C, 4, _NONE
#define LED2_5 C, 5, _NONE
#define LED2_6 C, 6, _NONE
#define LED2_7 C, 7, _NONE

Также можно описывать одиночные выводы.

#define BUT C, 0, _HI

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

PM_WritePort(port, value) - запись в порт. Производит запись значения value в регистры PORTx

PM_DirPort(port, value)
- установка направления порта. Производит запись значения value в регистры DDRx.

PM_ReadPort(port, var)
- чтение состояния выводов порта. Читает значение регистра PINx и записывает его в переменную var.

PM_SetBitsPort(port, value)
- групповая установка разрядов порта. Запись производится в регистры PORTx.

PM_ClearBitsPort(port, value)
- групповой сброс разрядов порта. Запись производится в регистры PORTx.

Примеры

//записать 0xff в виртуальный порт
PM_WritePort(LED_PORT, 0xff); 

//установить выводы реального порта на выход
PM_DirPort(LED_PORT2, 0xff);

//записываем в порт C 0xff
PORTC = 0xff;
//записываем в LED_PORT, который по описанию является PORTC, значение 0x00
PM_WritePort(LED_PORT, 0x00); 
//после выполнения предыдущего макроса в порте C будет значение 0xf0
//потому что значения выводов с атрибутом _NONE меняться не будут.

Также в данном файле реализованы макросы для работы с битами и одиночными выводами портов. 

Макросы для манипулирования битами

BM_Bit(bit) - заменяется выражением 1<<bit 

BM_SetBit(var, bit) - установка разряда в переменной 

BM_ClearBit(var, bit) - сброс разряда в переменной 

BM_InvBit(var, bit) - инвертировать разряд в переменной 

BM_TestBit(var, bit) - заменяется выражением (var & (1<<bit)) 

BM_BitIsClear(var, bit) - заменяется выражением (var & (1<<bit) == 0) 

BM_BitIsSet(var, bit) - заменяется выражением (var & (1<<bit) != 0) 

BM_SetBitVal(var, bit, val) - устанавливает бит в переменной var в зависимости от значения val

Примеры

uint8_t contr_reg = 0;
...
//установить 1 и четвертые биты в переменной
contr_reg |= BM_Pin(1)|BM_Bit(4);

//установить 0-й бит в переменной
BM_SetBit(contr_reg, 0); 

//сбросить 7-й разряд в порту
BM_ClearBit(PORTB, 7);

//инвертировать 1-й бит в порту
BM_InvBit(PORTC, 1);

// если установлен 2-й разряд в переменной, вызывать функцию
if (BM_TestBit(contr_reg, 2)){ func();}

// если 1-й разряд в переменной сброшен, вызывать функцию
if (BM_BitIsClear(contr_reg, 1)){ func();}

//вызывать функцию, если установлен 1-й разряд в переменной
if (BM_BitIsSet(contr_reg, 1)){ func();}

//сбросить 1-й бит в переменной 
BM_SetBitVal(contr_reg, 1, 0);

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

PM_Pin(x) - заменяется выражением (1<<pin)

PM_SetPin(x) - установить разряд в регистре PORTx

PM_ClearPin(x) - сбросить разряд в регистре PORTx

PM_InvPin(x) - инвертировать разряд в регистре PORTx

PM_TestPin(x) - заменяется выражением (PINx & (1<<bit))

PM_PinIsSet(x) - заменяется выражением ((PINx & (1<<bit)) != 0)

PM_PinIsClear(x) - заменяется выражением ((PINx & (1<<bit)) == 0)

PM_OnPin(x) - установливает разряд в регистре PORTx, если активный уровень вывода _HI, сбрасывает, если активный уровень _LOW

PM_OffPin(x) - сбросывает разряд в регистре PORTx, если активный уровень вывода _HI, установливает, если активный уровень _LOW

PM_DirPin(x, y) - установить направление вывода. "y" может принимать значения _IN или _OUT. Запись производится в регистры DDRx.

Примеры

//описываем выводы 
#define LED1 C, 0, _HI
#define LED1 C, 1, _LOW
#define LED2 B, 4, _NONE
#define BUT D, 0, _HI

//настраиваем вывод со светодиодом на выход
PM_DirPin(LED1, _OUT);

//настраиваем вывод с кнопкой на вход
PM_DirPin(BUT, _IN);

//и включаем подтягивающий резистор 
PM_SetPin(BUT);

//зажигаем светодиод
PM_OnPin(LED1);

//гасим светодиод
PM_OffPin(LED1);
//проверяем нажата ли кнопка
if (PM_PinIsClear(BUT)) { ....}

//проверяем отпущена ли кнопка
if (PM_PinIsSet(BUT)) { ....}

Тестовые проекты

   Чтобы протестировать макросы я сделал тестовый проект в IAR`e и Proteus`е. Проект предельно простой. В коде определены два порта - реальный и виртуальный. К портам подключены светодиоды и микроконтроллер формирует на этих светодиодах "бегущий огонек". Также к микроконтроллеру подключена кнопка и два одиночных светодиода с разными активными уровнями и схемой подключения.
   Под другие компиляторы проекты делать не стал, так как не вижу в этом необходимости.
   Под эти макросы уже практически переделана библиотека для символьного LCD, следующая на очереди библиотека для 7-ми сегментного индикатора.
   Скорее всего этот вариант макросов не окончательный и будет дорабатываться.

vp_test_iar.rar
vp_test_proteus.rar
port_macros.rar

У вас недостаточно прав для комментирования.