Лабораторная №3. Прямое обращение к регистрам ввода-вывода. Побитовые операции. Создание библиотек Arduino
Цель: Научиться конфигурировать порты микроконтроллера с помощью прямой записи в регистры портов ввода-вывода. Научиться использовать битовые операции при работе с параллельными портами ввода-вывода.
Необходимое оборудование
Плата Arduino – 1шт
Беспаечная макетная плата – 1шт
Светодиоды – 10шт
Резистори 300 Ом – 10шт
Резистори 4.7 кОм – 2 шт
Кнопка – 1шт
Набор перемичек для макетной платы
Документация для микроконтроллера ATmega328 (datasheet)
Варианты задания
Используя набор из десяти светодиодов, организовать следующие режимы индикации:
Змейка вперёд – включение линейки светодиодов слева направо, и выключение в том же порядке. Змейка назад – включение линейки светодиодов справа налево, и выключение в том же порядке. «Бегущий диод» – загорается только один диод, который «перемещается» из одного края линейки в другой, и обратно. Режим схождения-расхождения – включение светодиодов поочерёдно, от краёв к центру и выключение в обратном порядке. Заполнение линейки светодиодов слева направо (по одному диоду). Заполнение линейки светодиодов справа налево (по одному диоду). Поочерёдное включение чётных и нечётных (по номеру в линейке) светодиодов.При выполнении задания каждый режим организовать функцией. При написании программы использовать циклы, прямое обращение к регистрам ввода-вывода микроконтроллера и побитовые операции для организации вывода по вариантам.
Выполнение работы
Теоретическая часть
Прямое обращение к регистрам микроконтроллера на примере регистров ввода-вывода.В предыдущей лабораторной работе был описан способ взаимодействия с портами ввода-вывода при помощи стандартных библиотек Arduino, которые позволяли подавать высокий или низкий логический уровень на выбранный вывод микросхемы.
В этой лабораторной работе рассматривается способ вывода и считывания информации с помощью всего параллельного порта, а не по отдельным выводам.
На отладочных платах Arduino могут использоваться различные микроконтроллеры. Ниже описана работа с Atmega 8.
При работе с этим контроллером программисту доступно три параллельных порта: B, C и D.

Рис 1.1 Расположение портов в корпусе микросхемы
Параллельные порты обозначены буквами Pxy(где x – буква имени порта, y – номер пина в порте). Как показано на рисунке 1.1 Atmega имеет два восьмиразрядных порта: B (PB0..PB7) и D (PD0..PD7) а также один шестиразрядный порт С (PC0..PC5). Пин PC6 также является служебным пином Reset (перезапуск микроконтроллера), поэтому для ввода-вывода его использовать не рекомендуется, т. к. это может привести к некорректной работе и случайным перезапускам. Помимо этого, в связи с наличием у некоторых выводов дополнительных функций (указаны в скобках на рисунке 1.1), доступ к ним может быть ограничен.
На Рис. 1.2 продемонстрированы все доступные порты на отладочной плате Arduino. Необходимо знать, что выводы PD0 и PD1 использовать не выйдет, если в программе также используется передача данных по последовательному порту.
ARDUINO/AVR | |||
AREF | |||
GND | |||
13 | PB5 | ||
12 | PB4 | ||
RESET | 11 | PB3 | |
— | 10 | PB2 | |
5V | 9 | PB1 | |
GND | 8 | PB0 | |
GND | 7 | PD7 | |
Vin | 6 | PD6 | |
PC0 | 14 | 5 | PD5 |
PC1 | 15 | 4 | PD4 |
PC2 | 16 | 3 | PD3 |
PC3 | 17 | 2 | PD2 |
PC4 | 18 | 1 | PD1(TX) |
PC5 | 19 | 0 | PD0(RX) |
Рис. 1.2 Схема соответствия выводов микросхемы Atmega8 и пинов на плате Arduino
Для операций с каждым из параллельных портов ввода-вывода используется три регистра: DDRx, PORTx, PINx (замість х вказується літера відповідного порту, наприклад: DDRB)
DDRx — Data Direction Register. Регистр, определяющий направление, т. е. приём или передачу сигналов этим пином. 1 – передача, 0 – приём.
PORTx — регистр, который содержит логические значения, выводимые на соответствующие пины, в случае, если они настроены на вывод.
PINx — регистр, который содержит логические значения, полученные при считывании данных из порта.
На Рис. 1.3 приведено графическое изображение всех трёх регистров для порта В.

Рис. 1.3 Регистры параллельного порта ввода-вывода В
Номер бита в регистре соответствует номеру пина контроллера.
Рассмотрим настройку, запись и чтение значений в порт на примере:
void setup()
{
DDRC = 0b11111111; //настройка всего порта С на передачу сигналов
// 1 – пин настроен на передачу
// 0 – пин настроен на приём
// значения можно задавать в двоичной или десятичной системе исчисления
// Т. е. записи:
// DDRC = 0xFF; и DDRC = 255 - эквивалентны.
// запись значения в двоичной системе наиболее наглядная
// запись значения в шестнадцатеричной системе наиболее распространённая
DDRB = 0b00000000; //настройка всего порта В на приём сигналов
DDRD = 0b00000101; //в нулевой и второй биты регистра записаны "1"
// это означает, что PD0 и PD2 настроены на передачу,
//а все остальные пины этого порта — на прием сигналов
}
void loop()
{
PORTC = 0b00000011; //подача высокого логического уровня на PC0 та PC1
if (PINB > 0) //если данные на порт В получены
PORTD = 1; //подать высокий уровень на PD0;
else
PORTD = 0;
}
Битовыми операциями называют операции над цепочками битов. В программировании, как правило, рассматриваются только некоторые виды этих операций, а именно операции сдвига.
К побитовым логическим операциям принадлежат:
~ Побитовое отрицание (NOT) — унарна операция, действие которой эквивалентно применению логического отрицания к каждому биту двоичного представления операнда.
i | ~i |
1 | 0 |
0 | 1 |
byte i = 0b00001010;
i = ~ i; // i примет значение 0b11110101
& Побитовое И (AND) — бинарная операция, действие которой эквивалентно применению логического AND к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Бит результата будет равен 1 только в том случае, если оба соответствующих бита операндов равны 1
i | j | j & i |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
byte i = 0b00001010;
byte j = 0b00001011;
i = j & i; // i примет значение 0b00001010
| Побитовое ИЛИ (OR) — это бинарная операция, действие которой эквивалентно применению логического ИЛИ к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Если хоть один из соответствующих битов операндов 1 - бит результат будет равен 1.
i | j | j | i |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
byte i = 0b00001010;
byte j = 0b00001011;
i = j | i; // i примет значение 0b00001011
^ Исключающее ИЛИ (XOR) — это бинарная операция, результат действия которой равен 1, если число складываемых единичных битов нечетное, если же их число четное, то результат равен 0. Иными словами, сложение по модулю два.
i | j | j ^ i |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
byte i = 0b00001010;
byte j = 0b00001011;
i = j ^ i; // i примет значение 0b00000001
К операциям битовых сдвигов принадлежат:
Битовый сдвиг влево <<

byte i = 0b00001010;
i = i << 1; // i примет значение 0b00010100
Битовый сдвиг вправо >>

byte i = 0b00001010;
i = i >> 1; // i примет значение 0b00000101
Использование масок для считывания и вывода данных на параллельные порты
void setup()
{
DDRC = 0b11111111; //настройка всего порта С на подачу сигналов
// 1 - пин настроен на передачу,
// 0 - пин настроен на приём
// значения можно задавать в двоичной или десятичной системе исчисления
// Т. е. записи:
// DDRC = 0xFF; и DDRC = 255 - эквивалентны.
// запись значения в двоичной системе наиболее наглядная
// запись значения в шестнадцатеричной системе наиболее распространённая
DDRB = 0b00000000; //настройка всего порта В на приём сигналов
DDRD = 0b00000101; //в нулевой и второй биты регистра записаны "1"
// это означает, что PD0 и PD2 настроены на передачу,
//а все остальные пины этого порта — на прием сигналов
}
void loop()
{
PORTC = 0b00000011; //подача высокого логического уровня на РС0 и РС1
PORTC | = 0b00001000; // PORTC принимает значение 0b00001011
//Т. е. мы подаём высокий логический уровень сигнала на РС3
//независимо от того, что уже было записано в регистр раньше
PORTC & = 0b11111110; // PORTC принимает значение 0b00001010
// подача низкого логического уровня на РС0
}
Дополнительно: Написание библиотеки Arduino
Написание собственной библиотеки позволяет с легкостью использовать, обновлять и дополнять созданный вами код другими пользователями.
Процесс создания библиотеки показан на примере программы, передает сигнал SOS с помощью кода Морзе.
Этапы создания библиотеки:
• Написание скетча (программы) в Arduino IDE
• Конвертация скетча в библиотеку
• Подключение и использование библиотеки
Пример кода:
int pin = 13;
void setup()
{
pinMode(pin, OUTPUT);
}
void loop()
{
dot(); dot(); dot();
dash(); dash(); dash();
dot(); dot(); dot();
delay(3000);
}
void dot()
{
digitalWrite(pin, HIGH);
delay(250);
digitalWrite(pin, LOW);
delay(250);
}
void dash()
{
digitalWrite(pin, HIGH);
delay(1000);
digitalWrite(pin, LOW);
delay(250);
}
Данная программа будет выводить путем мигания светодиода на 13м выводе сигнал SOS.
Эта программа содержит несколько частей кода, которые необходимо перевести в библиотеку. Во-первых, это функции dot () и dash (), которые управляют включением светодиода, во-вторых это переменная ledPin, определяющий используемый порт ввода-вывода и, наконец, вызов функции pinMode () которая настраивает выбранный пин на подачу сигнала.
Процесс конвертации кода программы в библиотеку.
Библиотека содержит два файла: заголовочные файл "filename. h" и файл реализации "filename. cpp". В заголовочный файл помещаются характеристики библиотеки, то есть список всего содержимого. Создаваемый файл будет называться Morse. h. Для дальнейшей работы с заголовочным файлом необходимо пересмотреть файлы реализации.
Содержимое файла Morse. h
class Morse
{
public:
Morse(int pin);
void dot();
void dash();
private:
int _pin;
};
Класс в данном случае выступает набором функций и переменных, которые объединены в одном месте. Функции и переменные могут иметь тип доступа public, что позволяет общий доступ к ним всех, кто использует данную библиотеку, или private, что позволяет доступ к этим переменным только с середины класса. Каждый класс имеет специальную конструкцию и называется конструктором. Конструктор имеет такое же имя что и класс. Он используется для создания экземпляра этого класса.
Также в заголовочном файле содержится и несколько дополнительных строк: директива # include, предоставляющая доступ к стандартным типов и константам языка программирования Arduino
Готовый заголовочный файл выглядит примерно так:
/*
Morse. h - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Released into the public domain.
*/
#ifndef Morse_h //принято помещать содержимое заголовочного файла
#define Morse_h //в такую конструкцию
#include "WProgram. h"
#include "Arduino. h"
class Morse
{
public:
Morse(int pin);
void dot();
void dash();
private:
int _pin;
};
#endif //конец конструкции
Реализация файла Morse. cpp
/*
Morse. cpp - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Released into the public domain.
*/
#include "WProgram. h"
#include "Morse. h" //Необходимо присоединить заголовочный файл
//Morse:: означает, что функция принадлежит классу Morse
Morse::Morse(int pin) //
{
pinMode(pin, OUTPUT);
_pin = pin; // подчёркивание – часто используемое обозначение локальных переменных
}
//Конвертируемый в библиотеку код из начальной программы
void Morse::dot()
{
digitalWrite(_pin, HIGH);
delay(250);
digitalWrite(_pin, LOW);
delay(250);
}
void Morse::dash()
{
digitalWrite(_pin, HIGH);
delay(1000);
digitalWrite(_pin, LOW);
delay(250);
}
Использование библиотеки
Необходимо создать папку Morse в подпапке libraries.
Перенести созданные файлы в эту папку.
Библиотека компилируется вместе с программой, которая её использует.
Начальная программа, с использованием созданной библиотеки будет выглядеть следующим образом:
#include <Morse. h>
Morse morse(13);
void setup()
{
}
void loop()
{
morse. dot(); morse. dot(); morse. dot();
morse. dash(); morse. dash(); morse. dash();
morse. dot(); morse. dot(); morse. dot();
delay(3000);
}


