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

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

Комментарии   

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

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

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

Вариант 2

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

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

У кого не работает на CVAVR, замените в либе "uint8_t" на "unsigned char"
Ответить | Ответить с цитатой | Цитировать
# Pashgan 13.07.2013 22:26
Нужно было просто добавить строчку
Код:
#include <stdint.h>
Ответить | Ответить с цитатой | Цитировать
# InvisibleKid 16.05.2014 06:43
Кстати, а в чем соль использовать вместо int, char, float и т.д. такие странные имена типо "uint8_t"?
Ответить | Ответить с цитатой | Цитировать
# #Сергей 11.12.2014 21:18
//настраиваем вывод со светодиодом на выход
PM_DirPin(LED1, _OUT);
Зачем.
Если есть DDRx |=и т.д... :roll:
Ответить | Ответить с цитатой | Цитировать
# Din 21.12.2014 08:03
а что надо изменить/добави ть в либе что бы можно было определять виртуальные порты размером больше 8 пинов(10...16)?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 06.03.2015 17:44
Нужно править код между циклами do{.. }while(0). Это долго и муторно.
Ответить | Ответить с цитатой | Цитировать
# Pingwin 01.10.2015 11:16
Здравствуйте Pashgan!Немного доработал ваши макросы теперь позволяет -порт создавать с количеством линий от 1 до 16 и настраивать активный уровень непосредственно при работе с портами .Как добавить код?
Ответить | Ответить с цитатой | Цитировать
# foxit 02.10.2015 10:35
Положите на файлообменник и дайте ссылку.
Интересно посмотреть.
Ответить | Ответить с цитатой | Цитировать
# Pingwin 05.10.2015 07:57
https://cloud.mail.ru/public/7xxm/b61aNYDTk
Ответить | Ответить с цитатой | Цитировать
# Станислав 11.12.2016 16:01
Спасибо за библиотеку то что нужно)))!!!
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий

Защитный код
Обновить