Лабораторная №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

К операциям битовых сдвигов принадлежат:

Битовый сдвиг влево <<

http://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Rotate_left_logically.svg/210px-Rotate_left_logically.svg.png

byte i = 0b00001010;

i = i << 1; // i примет значение 0b00010100

Битовый сдвиг вправо >>

http://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Rotate_right_logically.svg/210px-Rotate_right_logically.svg.png

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);

}