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

26/05/2013 - 15:32 Павел Бобков

Введение

   Выводы микроконтроллеров 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

Comments   

# foxit 2013-05-26 19:10
Спасибо. Полезная штука.
# frolikum 2013-05-27 07:50
Согласен с последним предложением в статье.
Я как бы представлял будущие макроса немного по другому так что пока ничего сказать не могу. Ни хорошего не плохого так как считаю что только на практике можно будет оценить все плюсы и возможно найти минусы. В любом случае огромная работа проделана и Паше за это респект и уважуха.
# Pashgan 2013-05-27 09:28
А как ты их себе представлял?
# frolikum 2013-05-27 09:40
Думал о том что можно будет определить виртуальный порт назначив ему букву не существующего физического порта, скажем "Z", а дальше работать с ним как с реально существующим физическим портом. Без большого набора функций и макросов которые по началу все сразу не запомнить.
# Pashgan 2013-05-27 10:37
Я не знаю, как на Си написать такие макросы. Мне кажется это невозможно. Для работы с портом там всего три основных макроса - запись в порт, чтение, установка направления.
# frolikum 2013-05-27 10:53
Как это сделать говорилось в той статье где "много букв" но там на С++.
Ладно, пока будем тэстить, о обнаруженных багах незамедлительно отпишусь.
# Pashgan 2013-05-27 14:40
Такого там нет. Для работы с виртуальными портами там используются специальные методы классов.
# Zliva 2013-05-27 21:14
Статья супер. Давно искал что-то подобное. Автору респект. Очень нужна библиотека с такой реализацией на 7 сегментник. Просто горю. Помоги :cry:
# Pashgan 2013-05-28 09:33
Я свою еще не переделал.
# Peter 2013-05-28 08:52
Что, прямо так и работать с операциями |= и &= без обеспечения атомарности доступа к пину? Даже для одиночной установки/сброс а бит это опасно (не все IO-порты поддерживают битовый доступ, например порт F в mega128).
А для установки/сброс а нескольких бит порта это самоубийственно . Нет, кто хочет - пусть пробует))
# Pashgan 2013-05-28 09:33
Если нужна атомарность операций, обеспечивай ее отдельно. Во многих приложениях это вообще не критично.
# Peter 2013-05-28 09:41
Спасибо. То есть я, используя Вашу библиотеку, всё равно должен помнить об особенностях портов F и G по сравнению с другими портами? Что LED на порту F надо отличать от LED на порту B? А не для того ли мы ведем речь о виртуальных портах чтобы забыть об различиях и скрыть все эти нехорошие особенности внутри макросов?
# Pashgan 2013-05-28 10:06
Нужно только учитывать, что атомарность не соблюдается в любом случае. Для atmega128 можно использовать эту реализацию виртуального порта, просто для портов, не поддерживающих операции битового доступа, код будет более громоздким.
# Peter 2013-05-28 16:43
Если можно, приведите пожалуйста пример вызывающего кода как мне, имея LED на порту PORTF.1 безопасно для других потоков применять Вашу библиотеку.
# Pashgan 2013-05-29 19:42
Что то типа этого:

1 поток - функция
{запретить прерывания
операции с портом
разрешить прерывания}

2 поток - прерывание
{операции с портом}

Вариант 2

1 поток
{если мьютекс не захвачен, установить его
операции с портом
снять мьютекс}

2 поток
{если мьютекс не захвачен, установить его
операции с портом
снять мьютекс}
# Владимир 2013-05-29 13:01
Спасибо, забавная реализация!
Планируется ли портировать на другие МК?(не AVR)
# Pashgan 2013-05-29 19:44
Если понадобится мне или кому-то еще можно сделать.
# TERMIN 2013-06-20 11:24
Pashgan, огромное тебе спасибо!
Благодаря твоим макросам, я в них очень хорошо разобрался, очень удобный и качественный пример для обучения работы с макросами. Пока разбирался почему не работает PM_DirPort на CodeVisionAVR, делал работу компилятора вручную.

У кого не работает на CVAVR, замените в либе "uint8_t" на "unsigned char"
# Pashgan 2013-07-13 22:26
Нужно было просто добавить строчку
Code:
#include <stdint.h>
# InvisibleKid 2014-05-16 06:43
Кстати, а в чем соль использовать вместо int, char, float и т.д. такие странные имена типо "uint8_t"?
# #Сергей 2014-12-11 21:18
//настраиваем вывод со светодиодом на выход
PM_DirPin(LED1, _OUT);
Зачем.
Если есть DDRx |=и т.д... :roll:
# Din 2014-12-21 08:03
а что надо изменить/добави ть в либе что бы можно было определять виртуальные порты размером больше 8 пинов(10...16)?
# Pashgan 2015-03-06 17:44
Нужно править код между циклами do{.. }while(0). Это долго и муторно.
# Pingwin 2015-10-01 11:16
Здравствуйте Pashgan!Немного доработал ваши макросы теперь позволяет -порт создавать с количеством линий от 1 до 16 и настраивать активный уровень непосредственно при работе с портами .Как добавить код?
# foxit 2015-10-02 10:35
Положите на файлообменник и дайте ссылку.
Интересно посмотреть.
# Pingwin 2015-10-05 07:57
https://cloud.mail.ru/public/7xxm/b61aNYDTk
# foxit 2015-10-06 18:53
Спасибо
# Станислав 2016-12-11 16:01
Спасибо за библиотеку то что нужно)))!!!
# DartV 2018-06-12 15:04
Не знаю актуально ли в 2018 году, но codevision не смог прожевать INLINE static void IND_Conv и еще где то переменная была объявлена по c99. Полдня искал почему не работает.

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