Введение
Выводы микроконтроллеров 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
Я как бы представлял будущие макроса немного по другому так что пока ничего сказать не могу. Ни хорошего не плохого так как считаю что только на практике можно будет оценить все плюсы и возможно найти минусы. В любом случае огромная работа проделана и Паше за это респект и уважуха.
Ладно, пока будем тэстить, о обнаруженных багах незамедлительно отпишусь.
А для установки/сброс а нескольких бит порта это самоубийственно . Нет, кто хочет - пусть пробует))
1 поток - функция
{запретить прерывания
операции с портом
разрешить прерывания}
2 поток - прерывание
{операции с портом}
Вариант 2
1 поток
{если мьютекс не захвачен, установить его
операции с портом
снять мьютекс}
2 поток
{если мьютекс не захвачен, установить его
операции с портом
снять мьютекс}
Планируется ли портировать на другие МК?(не AVR)
Благодаря твоим макросам, я в них очень хорошо разобрался, очень удобный и качественный пример для обучения работы с макросами. Пока разбирался почему не работает PM_DirPort на CodeVisionAVR, делал работу компилятора вручную.
У кого не работает на CVAVR, замените в либе "uint8_t" на "unsigned char"
Code:
#include <stdint.h>
PM_DirPin(LED1, _OUT);
Зачем.
Если есть DDRx |=и т.д... :roll:
Интересно посмотреть.
RSS feed for comments to this post