Skip to content

Глава 3. Практическая часть

3.1 Лабораторная работа 1. Светодиод

Цель работы:

Ознакомление с основами радиоэлектронными компонентами (резистор, светодиод) и принципами их подключения к микроконтроллеру. Изучение основ программирования ввода/вывода в графической среде программирования и в Arduino IDE для управления светодиодом.

Оборудование: микроконтроллер, макетная плата, светодиод (1 шт), резистор на 220 Ом (1 шт.), провода.

Теоретическая часть:

Первая лабораторная работа посвящена знакомству с такими важными в электронике элементами, как резистор и светодиод, часто встречающимися во многих электрических схемах. Также, эти элементы достаточно просты, поэтому мы начнем изучение именно с них.

Обратите внимание, что длинная ножка светодиода - это анод, а короткая - катод, поэтому важно соблюдать полярность. Анод подключен к цифровому выводу, а катод через резистор к выводу GND.

Пины (или порты) — это ножки (выводы) на плате, через которые она взаимодействует с внешними устройствами: кнопками, светодиодами, датчиками и т.д.

Пины бывают двух типов:

1.Цифровые пины (Digital pins)

Обозначаются как D0, D1, D2... или просто (0, 1, 2, и тд), могут быть только в двух состояниях:

cpp
HIGH (вкл = 1 = 5V)
LOW (выкл = 0 = 0V)

Используются, например, чтобы включать/выключать светодиод, считывать нажатие кнопки и т.д.

Пример:

digitalWrite(13, HIGH); // включить светодиод на пине 13

  1. Аналоговые пины (Analog pins)

Обозначаются как А0, A1, A2… или просто (0, 1, 2, и тд), используются для измерения напряжения от 0 до 5 В.

Значение считывается функцией analogRead(), и возвращается число от 0 до 1023.

Подходят для подключения датчиков: температуры, освещённости, потенциометров и т.п.

Пример:

int value = analogRead(A0); // получить значение с датчика, подключенного к A0

Практическая часть:

В данной работе необходимо разработать программу для управления включением и выключением светодиода.

Прежде чем приступить к написанию управляющей программы, необходимо рассмотреть работу основных частей её кода. Сначала мы рассмотрим программирование в графической среде программирования, а затем в Arduino IDE.

Arduino IDE

  1. setup() — это функция, которая запускается один раз, когда Arduino включается или перезагружается. Здесь обычно пишут команды для настройки пинов или запуска серийного порта (Serial.begin()). Всё, что нужно выполнить только один раз — пишем сюда.

  2. loop() — это главная часть программы — она выполняется вечно. Код внутри loop() запускается снова и снова, пока Arduino включена (есть питание). Именно здесь происходит основная работа: мигание, чтение данных, управление мотором и т.д.

  3. pinMode(pin, mode) - используется в setup(), чтобы задать, как будет использоваться пин: как вход (INPUT) или выход (OUTPUT). Также есть INPUT_PULLUP — внутренний подтягивающий резистор к питанию (для кнопок, например).

  4. Функция для подачи цифрового сигнала digitalWrite(pin, value).

Данная функция позволяет подать или принять, в зависимости от работы пина, цифровой сигнал. При подаче сигнала пином, функция передает либо логическую “единицу” (HIGH), либо логический “ноль” (LOW). Логическая “единица” реализуется подачей напряжения в 5В, а логический “ноль” - отсутствие напряжения. При приеме сигнала пином логическая “единица” активирует внутренний нагрузочный резистор (20КоМ), а логический “ноль” отключает его.

pin - номер пина

value - значение (HIGH или LOW)

  1. Функция digitalRead(pin) данная функция считывает значение с заданного входа. Возвращает значение HIGH или LOW с пина, которого считывается значение.

  2. Функция delay(ms) останавливает выполнение программы на заданное в параметре количество миллисекунд (1000 миллисекунд в 1 секунде).

ms - количество миллисекунд, на которое останавливается программа.

  1. Тип данных int (integer - целое число).

Тип данных int является одним из наиболее часто используемых типов данных для хранения чисел. В памяти переменная типа int занимает 2 байта памяти и может хранить в себе значения в диапазоне от -32768 до 32767.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.1.1).

  1. Организуйте цепь питания: подключите шину GND макетной платы к выводу GND микроконтроллера. Это создаст общую точку («землю») для всех компонентов схемы.

  2. Установите нагрузку (светодиод): вставьте светодиод в макетную плату. Для работы светодиода ток должен течь от анода (+) к катоду (-).

  3. Добавьте ограничительный резистор: подключите резистор 220 Ом последовательно в цепь катода светодиода. Второй вывод резистора подключите к шине GND. Резистор можно установить и между анодом и управляющим пином — принцип «последовательного включения» сохраняется.

  4. Подайте управляющий сигнал: подключите анод светодиода к цифровому выходу микроконтроллера (пин 13). Именно этот пин по команде программы будет подавать напряжение +5В (HIGH) или 0В (LOW), замыкая или размыкая цепь.

Изображение 58

Рисунок 3.1.1. Внешний вид схемы сборки лабораторной работы 1

Рабочая программа на графическом языке программирования:

Изображение 59

Рисунок 3.1.2. Программа лабораторной работы 1 на графическом языке программирования

Блок "Выполнить разово» инициализирует пин 13 как выход (OUTPUT).

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Установить пин 13 в состояние HIGH (то есть подать питание);

  2. Задержка 1000 мс (=1 с);

  3. Установить пин 13 в состояние LOW (прекратить подачу питания);

  4. Задержка 1000 мс (=1 с).

Рабочая программа Arduino IDE:

cpp
void setup() {
pinMode(13, OUTPUT); } // настраиваем светодиод (пин 13) на выход
void loop() {
digitalWrite(13, HIGH); // включаем светодиод (то есть подаем логическую "единицу", рабочее напряжение 5В)
delay(1000); // Ждем 1000 мс (1 секунду)
digitalWrite(13, LOW); // выключаем светодиод (подается логический "ноль")
delay(1000); // ждем еще 1 секунду
}

3.2 Лабораторная работа 2. Светофор

Цель работы:

Изучение принципов управления несколькими независимыми устройствами (светодиодами) с помощью микроконтроллера. Применение циклов для создания программы работы светофора.

Оборудование: микроконтроллер, макетная плата, светодиод (3 шт.), резистор на 220 Ом (3 шт.), провода.

Теоретическая часть:

Для дальнейшего изучения программирования на Arduino IDE необходимо разобраться с циклами. Цикл — это управляющая конструкция языка программирования, которая позволяет многократно выполнять указанный блок кода (тело цикла) до выполнения определенного условия.

Arduino использует обычные циклы языка C/C++:

  1. Цикл for

Назначение: Выполнение блока кода заданное количество раз. Используется, когда известно, сколько раз нужно повторить действие.

cpp
for (инициализация; условие; изменение) {
//Тело цикла: выполняемые операторы
}

Инициализация выполняется один раз перед началом цикла. Обычно объявляется и инициализируется переменная-счетчик (int i=0).

Перед каждой итерацией проверяется условие продолжения (например i<5). Если условие истинно (true), выполняется тело цикла. Если ложно (false), цикл завершается. Далее выполняются все операторы внутри тела цикла.

После выполнения тела цикла выполняется изменение счетчика (например i++).

Пример программы, реализующей мигание светодиодом 5 раз:

cpp
for (int i = 0; i < 5; i++) {
digitalWrite(13, HIGH); //Включить светодиод (13 пин)
delay(500); //Пауза 500 мс
digitalWrite(13, LOW); //Выключить светодиод
delay(500);//Пауза 500 мс
}

Описание: int i=0: создается счетчик i, начальное значение 0;

i<5: Цикл выполняется, пока значение переменной i меньше 5;

i++ - После каждой итерации i увеличивается на 1.

Тело цикла выполняется ровно 5 раз (i=0,1,2,3,4)

  1. Цикл while

Повторяет блок кода, пока некоторое условие истинно.

Может быть бесконечным, если не остановить условие.

cpp
while (условие) {
//Тело цикла: выполняемые операторы
}

Проверка условия происходит перед каждой итерацией (включая первую). Если условие истинно выполняется тело цикла. После выполнения тела цикла возвращается к проверке условия. Как только условие становится ложным выполнение цикла завершается.

Пример программы, реализующей горение светодиода, пока на 2 пин не подается питание

cpp
while (digitalRead(2) == LOW) {//проверка подано ли на 2 пин напряжение (== это оператор сравнения)
digitalWrite(13, HIGH); //включение светодиода
}
  1. do...while
cpp
do{
//Тело цикла: выполнение оператора
} while(условие);

Выполняет блок кода хотя бы один раз, а затем продолжает пока условие истинно. Гарантируется выполнение тела перед первой условия.

Пример кода: Цикл будет выполнять задержку в 100 мс и затем проверять, что значение с аналогового пина меньше 100. Тело цикла будет выполняться до тех пор, пока не станет >=100.

cpp
do {
delay(100);
} while (analogRead(A0) < 100);

Практическая часть:

В данной лабораторной работе необходимо разработать программу, которая будет моделировать работу светофора.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.2.1).

Для подключения трёх светодиодов к макетной плате выполните следующие шаги:

  1. Организуйте общую "землю": подключите шину GND макетной платы к выводу GND микроконтроллера.

  2. Установите светодиоды: вставьте три светодиода в макетную плату в один вертикальный ряд (например, красный сверху, жёлтый посередине, зелёный снизу). Обращайте внимание на полярность: длинная ножка (анод, +) должна быть сверху в каждом ряду.

  3. Подключите резисторы: к катоду (короткой ножке) каждого светодиода подключите резистор 220 Ом. Второй вывод каждого резистора соедините с шиной GND макетной платы.

  4. Подайте управляющие сигналы: подключите аноды светодиодов к цифровым пинам микроконтроллера согласно программе:

Красный светодиод (анод) → пин 2 (через провод)

Жёлтый светодиод (анод) → пин 3

Зелёный светодиод (анод) → пин 4

Изображение 60

Рисунок 3.2.1. Внешний вид схемы сборки лабораторной работы 2

Рабочая программа на графическом языке программирования:

Изображение 61

Рисунок 3.2.2. Программа лабораторной работы 2 на графическом языке программирования

Для того, чтобы активировать чтение с порта выберем установленную скорость обмена данными и нажмем на кнопку чтения порта:

Изображение 62

После состояние порта изменится на открытое:

Изображение 63

Чтобы закончить чтение порта нажмем соответствующую кнопку:

Изображение 64

Блок "Выполнить разово» инициализирует 2,3,4 пины как выход (OUTPUT) и устанавливает соответствующие пинам светодиоды выключенными.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Установить пин 2 в состояние HIGH (то есть подать питание) = зажигается красный светодиод;

  2. Задержка 5000 мс (=5 с);

  3. Установить пин 2 в состояние LOW (прекратить подачу питания);

  4. Задержка 1000 мс (=1 с);

  5. Установить пины 2 и 3 в состояние HIGH (то есть подать питание) = зажигаются красный и желтый светодиод вместе;

  6. Задержка 1000 мс (=1 с);

  7. Установить пины 2 и 3 в состояние LOW (прекратить подачу питания);

  8. Задержка 1000 мс (=1 с);

  9. Установить пин 4 в состояние HIGH (то есть подать питание) = зажигается зеленый светодиод;

  10. Задержка 1000 мс (=1 с);

  11. Установить пин 4 в состояние LOW (прекратить подачу питания);

  12. Задержка 1000 мс (=1 с);

  13. Блок «повторить 3 раза» подобен циклу for с заданным количеством повторений. Внутри него пин 3, соответствующий желтому светодиоду загорается на 0,5 с и гаснет на 1с.

Рабочая программа Arduino IDE:

cpp
// Объявление пинов светодиодов
const int redPin = 2; // объявляем Красный-2 пин
const int yellowPin = 3; // объявляем Желтый-3 пин
const int greenPin = 4; // объявляем Зеленый- 4 пин
void setup() {
pinMode(redPin, OUTPUT); //2 пин используется как выход
pinMode(yellowPin, OUTPUT);//3 пин используется как выход
pinMode(greenPin, OUTPUT);//4 пин используется как выход
// начинаем с выключенного состояния
digitalWrite(redPin, LOW);
digitalWrite(yellowPin, LOW);
digitalWrite(greenPin, LOW);
}
void loop() {
// режим 1: Красный горит 5 секунд
digitalWrite(redPin, HIGH);
delay(5000);
digitalWrite(redPin, LOW);
// режим 2: Совместно горят красный + желтый 1 секунду
digitalWrite(redPin, HIGH);
digitalWrite(yellowPin, HIGH);
delay(1000);
digitalWrite(redPin, LOW);
digitalWrite(yellowPin, LOW);
// режим 3: Зеленый горит 5 сек
digitalWrite(greenPin, HIGH);
delay(5000);
digitalWrite(greenPin, LOW);
// режим 4: Мигающий Желтый (3 сек)
for (int i = 0; i < 3; i++) {//цикл на 3 итерации подачи и прекращения подачи питания на желтый светодиод
digitalWrite(yellowPin, HIGH);
delay(500);
digitalWrite(yellowPin, LOW);
delay(500);
}
}

3.3 Лабораторная работа 3. Тактовая кнопка

Цель работы:

Знакомство с принципом работы тактовой кнопки, а также применение полученных знаний и навыков для создания программы по управлению включением и выключением светодиода с помощью тактовой кнопки.

Оборудование: микроконтроллер со встроенной кнопкой и светодиодом.

Теоретическая часть:

Тактовая кнопка - простейший механический переключатель, который замыкает или размыкает цепь при нажатии.

На контроллере имеется 3 тактовые кнопки (позиции 15,16,17 на плате (смотри рисунок 1.1)). При этом кнопка на позиции 17 является схемотехнически включенной в цепь перезагрузки (Reset) контроллера. Таким образом, нажатие на нее приведет к принудительному прерыванию выполняющейся программы и полной перезагрузки системы. Кнопки, располагающиеся на позициях 15 и 16, являются программируемыми. Другими словами, программным способом можно реализовать процедуру их опроса, и в зависимости от их состояния (нажаты или нет), реагировать определенным образом, включать или выключать встроенный светодиод.

Практическая часть:

В данной лабораторной работе необходимо разработать программу управления встроенными программируемыми кнопкой и светодиодом, выведенные на цифровую линию D11 и D13. При нажатии кнопки в монитор порта выводится “1” и загорается светодиод, а в остальном случае выводится “0”.

pinMode(pin,INPUT_PULLUP) - встроенный подтягивающий резистор обеспечивает стабильный сигнал.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.3.1).

Изображение 65

Рисунок 3.3.1. Внешний вид схемы сборки лабораторной работы 3

Рабочая программа на графическом языке программирования:

Изображение 66

Рисунок 3.3.2. Программа лабораторной работы 3 на графическом языке программирования

Блок "Выполнить разово» инициализирует 13 пин встроенного светодиода как выход (OUTPUT), 11 пин кнопки со встроенным подтягивающим резистором (INPUT_PULLUP) и Serial порт на скорости 9600.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. «Считать цифровой пин» - чтение состояния кнопки на 11 пине в переменную intg;

  2. Цикл «если иначе» аналогичен «if else». Если состояние кнопки LOW, то в Serial-порт выводится «0», иначе светодиод на пине 13 загорается (устанавливается HIGH) и в Serial-порт выводится «1»;

  3. Задержка 100 мс.

Рабочая программа для Arduino IDE:

cpp
const int button = 11; //пин встроенной кнопки (D11)
const int led = 13; //пин встроенного светодиода (D13)
void setup() {
pinMode(button, INPUT_PULLUP); //кнопка с подтягивающим резистором
pinMode(led,OUTPUT);
Serial.begin(9600); //инициализация Serial порта
}
void loop() {
int buttonState = digitalRead(button); //чтение состояния кнопки
if (buttonState==LOW){ //если кнопка не нажата
Serial.println(0); //вывод в монитор порта "0"
digitalWrite(led,LOW);
}
else {
Serial.println(1); //если кнопка нажата - вывод в монитор порта "1"
digitalWrite(led,HIGH);
}
delay(100); //пауза
}

3.4 Лабораторная работа 4. Регулируемый светофор

Цель работы:

Изучение принципов обработки цифровых сигналов от кнопки и создание системы управления несколькими светодиодами по внешнему событию (нажатие кнопки).

Оборудование: микроконтроллер, макетная плата, светодиод (3 шт), резистор на 220 Ом (3 шт), резистор на 10 кОм (1 шт), тактовая кнопка (1 шт), провода.

Теоретическая часть:

В данной работе воссоздается модель “вызывного” светофора, который часто устанавливается на нерегулируемых пешеходных переходах в городских улицах. Для водителя на таком светофоре постоянно горит разрешающий сигнал. Если пешеходу нужно перейти дорогу, он должен нажать на кнопку на светофоре и подождать, пока для него загорится разрешающий зеленый сигнал.

Для опроса состояния кнопки в среде Arduino IDE используется функция digitalRead.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.4.1).

Для подключения трёх светодиодов выполните следующие шаги:

  1. Организуйте общую "землю": подключите шину GND макетной платы к выводу GND микроконтроллера.

  2. Установите светодиоды: вставьте три светодиода в макетную плату в один горизонтальный ряд. Обращайте внимание на полярность: длинная ножка (анод, +) должна быть в одном ряду, а короткая (катод, -) в соседнем ряду.

  3. Подключите токоограничивающие резисторы: к катоду (короткой ножке) каждого светодиода подключите резистор 220 Ом. Второй вывод каждого резистора соедините с шиной GND макетной платы.

  4. Подайте управляющие сигналы: подключите аноды светодиодов к цифровым пинам микроконтроллера согласно программе:

Красный светодиод (анод) → пин 2

Жёлтый светодиод (анод) → пин 3

Зелёный светодиод (анод) → пин 4

Для подключения кнопки используйте резистор 10 кОм в качестве подтягивающего к земле резистора:

  1. Подключите кнопку к питанию: один вывод кнопки подключите к шине +5V макетной платы. Предварительно соедините шину +5V макетной платы с выводом 5V микроконтроллера.

  2. Организуйте земную цепь: второй вывод кнопки подключите одновременно к: цифровому пину 7 микроконтроллера (сигнальный провод) и резистору 10 кОм

  3. Завершите цепь: второй вывод резистора 10 кОм подключите к шине GND макетной платы.

Пояснение к работе схемы с кнопкой:

  • Когда кнопка не нажата: пин 7 через резистор 10 кОм соединен с GND → digitalRead (7) возвращает LOW (0)

  • Когда кнопка нажата: пин 7 напрямую соединяется с +5V → digitalRead (7) возвращает HIGH (1)

Внимание: без резистора 10 кОм на GND вход А7 будет "плавать" между HIGH и LOW, и Arduino может случайно считать ложные нажатия. Резистор "притягивает" сигнал к LOW, когда кнопка не нажата.

Изображение 67

Рисунок 3.4.1. Внешний вид схемы сборки лабораторной работы 4

Рабочая программа на графическом языке программирования:

Изображение 68

Рисунок 3.4.2. Программа лабораторной работы 4 на графическом языке программирования

Блок "Выполнить разово» инициализирует 2,3,4 пины как выход (OUTPUT), 7 пин тактовой кнопки (INPUT).

Блок "Выполнять в цикле" содержит последовательность действий:

  1. «Считать цифровой пин» - чтение состояния кнопки на 7 пине в переменную button;

  2. Цикл «если иначе» аналогичен «if else». Если состояние кнопки HIGH, то:

  3. красный светодиод на пине 4 загорается (устанавливается HIGH);

  4. задержка в 5000 мс (=5с);

  5. красный светодиод отключается (LOW), зеленый светодиод, соответствующий 2 пину, загорается;

  6. задержка в 6000 мс (=6с);

  7. зеленый светодиод отключается (LOW), жёлтый светодиод, соответствующий 3 пину, загорается;

  8. задержка в 2000 мс (=2с);

  9. желтый светодиод отключается (LOW).

Рабочая программа для Arduino IDE:

cpp
// пины для светодиодов
const int greenPin = 2; // зеленый
const int yellowPin = 3; // жёлтый
const int redPin = 4; // красный
// пин для кнопки
const int buttonPin = 7;
void setup() {
// настраиваем светодиоды как выходы
pinMode(redPin, OUTPUT);
pinMode(yellowPin, OUTPUT);
pinMode(greenPin, OUTPUT);
// настраиваем кнопку как вход
pinMode(buttonPin, INPUT);
// выключаем все светодиоды, то есть не подаем на них питание
digitalWrite(redPin, LOW);
digitalWrite(yellowPin, LOW);
digitalWrite(greenPin, LOW);
}
void loop() {
// проверяем, нажата ли кнопка
if (digitalRead(buttonPin) == HIGH) {
// если да, запускаем цикл светофора
// 1) красный (стоп!) — 5 секунд
digitalWrite(redPin, HIGH);//загорается красный светодиод
delay(5000);
digitalWrite(redPin, LOW);
// 2) зелёный (можно идти) — 6 секунды
digitalWrite(greenPin, HIGH);//загорается зеленый светодиод
delay(6000);
digitalWrite(greenPin, LOW);
// 3) Жёлтый (внимание!) — 2 секунда
digitalWrite(yellowPin, HIGH);//загорается желтый светодиод
delay(2000);
digitalWrite(yellowPin, LOW);
}
}

3.5 Лабораторная работа 5. Пьезодинамик

Цель работы:

Изучение принципа работы пьезодинамика и генерации звуковых сигналов. Создание системы светофора со звуковым сопровождением, управляемой кнопкой.

Оборудование: микроконтроллер, макетная плата, светодиод (3 шт.), резистор на 220 Ом (3 шт.), резистор на 10 кОм (1 шт.), пьезодинамик (1 шт.), тактовая кнопка (1 шт.), провода.

Теоретическая часть:

Пьезодинамик - электроакустическое устройство, преобразующее электрические сигналы в звуковые колебания. Основу составляет пьезокерамический элемент, который изменяет свою форму при подаче напряжения. При подаче переменного напряжения с определенной частотой элемент вибрирует, создавая звуковые волны.

Практическая часть:

В данной лабораторной работе необходимо разработать программу, которая будет при нажатии на кнопку будет зажигать светодиод и сопровождать это звуковым сигналом. Для этого необходимо использовать функцию для создания прямоугольной волны tone().

  1. Функция генерации прямоугольной волны заданной частоты tone().

Описание:

Данная функция генерирует на порту вход / выход прямоугольную волну, заданной частоты с 50% рабочим циклом. К порту подключается пьезодинамик для воспроизведения сигнала. Воспроизводиться одновременно может только один сигнал. При повторном вызове tone() с тем же номером порта будет устанавливаться новая частота сигнала.

Синтаксис:

tone (pin, frequency) либо tone (pin, frequency, duration)

Параметры:

pin - номер пина,

frequency - частота сигнала [Гц],

duration – длительность сигнала [мс] (может быть, не задана, в этом случае сигнал будет генерироваться, пока не будет вызвана функция noTone),

  1. Функция пропорционального переноса – map().

Описание:

Функция пропорционально переносит значение value из текущего диапазона значений (fromLow, fromHigh) в новый диапазон (toLow, toHigh), заданный параметрами.

Функция map() оперирует целыми числами. При пропорциональном переносе дробная часть числа не округляется по правилам, а просто отбрасывается.

Синтаксис:

map (value, fromLow, fromHigh, toLow, toHigh )

Параметры:

value - значение для переноса,

fromLow - нижняя граница текущего диапазона,

fromHigh – верхняя граница текущего диапазона,

toLow - нижняя граница нового диапазона, в который переносится значение,

toHigh - верхняя граница нового диапазона, в который переносится значение. Функция возвращает значение в новом диапазоне.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.5.1.).

Подключение светодиодов и резисторов:

Красный светодиод (redPin = 4):

Анод (длинная ножка) → пин 4 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Жёлтый светодиод (yellowPin = 3):

Анод (длинная ножка) → пин 3 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Зелёный светодиод (greenPin = 2):

Анод (длинная ножка) → пин 2 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Подключение тактовой кнопки (buttonPin = 7):

Схема с подтягивающим резистором 10 кОм к земле:

  1. Первый вывод кнопки → шина +5V макетной платы

  2. Второй вывод кнопки подключается одновременно к: пину 7 Arduino (сигнальный провод) и резистору 10 кОм

  3. Второй вывод резистора 10 кОм → шина GND макетной платы

Подключение пьезодинамика (buzzerPin = 8):

Анод пьезодинамика (длинная ножка) → пин 8 Arduino

Катод пьезодинамика (короткая ножка) → шина GND макетной платы.

Изображение 69

Рисунок 3.5.1. Внешний вид схемы сборки лабораторной работы 5

Рабочая программа на графическом языке программирования:

Изображение 70

Рисунок 3.5.10. Программа лабораторной работы 5 на графическом языке программирования

Блок "Выполнить разово» инициализирует 2,3,4,8 пины как выход (OUTPUT), 7 пин тактовой кнопки (INPUT_PULLUP) и устанавливает светодиоды в выключенное состояние.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. «Считать цифровой пин» - чтение состояния кнопки на 7 пине в переменную buttonPin;

  2. Цикл «если иначе» аналогичен «if else». Если состояние кнопки 1=HIGH, то:

  3. Красный светодиод на 4 пине загорается (устанавливается HIGH);

  4. Пьезодинамик генерирует низкий тон на 1 секунду;

  5. Задержка в 3000 мс (=3с);

  6. Красный светодиод отключается (LOW), зелёный светодиод, соответствующий 2 пину, загорается (HIGH);

  7. Пьезодинамик генерирует высокий тон на 0.5 секунд;

  8. Задержка в 3000 мс (=3с);

  9. Зеленый светодиод отключается (LOW);

  10. Внутри цикла «Повторить» 3 раза: загорается желтый светодиод (HIGH), соответствующий 3 пину; генерируется звук на пьезодинамике; задержка в 300 мс; желтый светодиод отключается; задержка в 300 мс;

  11. Задержка в 500 мс (=0.5 с).

Рабочая программа для Arduino IDE:

cpp
// Пины для светодиодов
const int greenPin = 2; //2 пин для зелёного светодиода
const int yellowPin = 3; //3 пин для желтого светодиода
const int redPin = 4; //4 пин для красного светодиода
const int buttonPin = 7; //устанавливаем 7 пин для кнопки
const int buzzerPin = 8; //устанавливаем 8 пин для пьезопищалки
void setup() {
// настройка светодиодов
pinMode(redPin, OUTPUT);
pinMode(yellowPin, OUTPUT);
pinMode(greenPin, OUTPUT);
// настройка кнопки
pinMode(buttonPin, INPUT_PULLUP);
// настройка пьезопищалки
pinMode(buzzerPin, OUTPUT);
// выключить всё вначале, то есть не подаем напряжение на светодиоды
digitalWrite(redPin, LOW);
digitalWrite(yellowPin, LOW);
digitalWrite(greenPin, LOW);
}
void loop() {
// если кнопка нажата
if (digitalRead(buttonPin) == HIGH) {
// 1. Красный + звук "Стоп!"
digitalWrite(redPin, HIGH);
tone(buzzerPin, 200, 1000); // низкий тон (400 Гц, 1 сек)
delay(3000); // Ждём 3 сек
digitalWrite(redPin, LOW);
// 2. Зелёный + звук "Идите!"
digitalWrite(greenPin, HIGH);
tone(buzzerPin, 800, 500); // высокий тон (800 Гц, 0.5 сек)
delay(3000);
digitalWrite(greenPin, LOW);
// 3. жёлтый + мигающий звук
for (int i = 0; i < 3; i++) {
digitalWrite(yellowPin, HIGH);
tone(buzzerPin, 600, 200); // Средний тон (600 Гц, 0.2 сек)
delay(300);
digitalWrite(yellowPin, LOW);
delay(300);
}
}
}

3.6 Лабораторная работа 6. ИК-датчик

Цель работы:

Практическое освоение принципов работы ИК-датчика и создание системы обнаружения движения с визуальной и текстовой индикацией состояния.

Оборудование: микроконтроллер, ИК-датчик (1 шт.), провода.

Теоретическая часть:

ИК-датчик обнаруживает изменения инфракрасного излучения в зоне наблюдения. Принцип работы основан на эффекте, что все тела с температурой выше абсолютного нуля испускают инфракрасное излучение. При обнаружении движения датчик изменяет состояние своего цифрового выхода с LOW на HIGH.

Практическая часть:

В данной работе разработана программа, которая считывает данные с ИК-датчика движения. При обнаружении движения программа должна включать встроенный светодиод на плате микроконтроллера и выводить соответствующее сообщение в монитор порта.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.6.1.).

У ИК-датчика есть 3 пина (VCC, GND, OUT). Соедините проводами соответственно OUT с цифровым пином 9 микроконтроллера, VCC с 5V на плате и GND с GND соответственно.

Изображение 71

Рисунок 3.6.1 Внешний вид схемы сборки лабораторной работы 6

Рабочая программа на графическом языке программирования:

Изображение 72

Рисунок 3.6.2. Программа лабораторной работы 6 на графическом языке программирования

Блок "Выполнить разово» инициализирует 9 пин ИК-датчика как вход (INPUT), 13 пин встроенного светодиода как выход (OUTPUT) и Serial-порт на скорость 9600.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. «Считать цифровой пин» - чтение состояния ИК-датчика на 9 пине в переменную sensorStatus;

  2. Цикл «если иначе» аналогичен «if else». Если состояние датчика HIGH, то:

  3. Загорается встроенный светодиод (HIGH), соответствующий пину 13;

  4. В Serial-порт выводится сообщение, что обнаружено движение;

  5. Иначе, если состояние датчика LOW, то светодиод не горит;

  6. Задержка в 100 мс.

Рабочая программа для Arduino IDE:

cpp
int IKSensor = 9; // пин ИК-датчика
int LED = 13; // пин встроенного светодиода в контроллере
void setup() {
Serial.begin(9600); // инициализация последовательного порта
pinMode(IKSensor, INPUT);
pinMode(LED, OUTPUT);
Serial.println("ИК-датчик активирован");
}
void loop() {
int sensorStatus = digitalRead(IKSensor);
if (sensorStatus == HIGH) {
digitalWrite(LED, HIGH); // включаем светодиод при движении
Serial.println("Движение обнаружено!");
} else {
digitalWrite(LED, LOW); // выключаем светодиод при отсутствии движения
}
delay(100); // небольшая задержка для стабильности
}

3.7 Лабораторная работа 7. Песня Jingle Bells

Цель работы:

Дальнейшее ознакомление с работой пьезодинамиком и светодиодами, а также использование полученных знаний и навыков для создания программы по воссозданию звучанию песни “Jingle bells”.

Оборудование: микроконтроллер, макетная плата, резистор на 220 Ом (5 шт.), светодиоды (5 шт.), пьезодинамик (1 шт.), провода.

Теоретическая часть:

Создание музыки на микроконтроллере основано на генерации звуковых волн определенной частоты. Каждая музыкальная нота соответствует конкретной частоте:

До (С) - 523 Гц (С5)

Ре (D) - 587 Гц (D5)

Ми (E) - 659 Гц (E5)

Фа (F) - 698 Гц (F5)

Соль (G) - 784 Гц (G5)

Практическая часть:

В данной лабораторной работе необходимо разработать программу, которая будет играть песню и мигать светодиодами с эффектом гирлянды.

Изучим функции void. Мы уже знаем void setup(), void loop() — это базовые функции, которые автоматически создаются в Arduino. Также мы сами можем создавать функции. Отдельные функции упрощают void setup() или void loop() в программу, структурируют её.

void имя функции(параметры) {

cpp
//тело функции
}

имя функции — это уникальный идентификатор(имя) функции, по которому в дальнейшем можно вызывать функцию.

(параметры) - входные данные (например, передаем пины различных устройств, переменные и тд.)

{...} - блок кода, выполняемый при вызове функции.

random() - встроенная функция генерации случайных чисел

Синтаксис:

cpp
random(max) //числа от 0 до max-1
random(min,max) //числа от min до max-1
random(5) - делает выборку из чисел 0,1,2,3,4
random(2,7) - делает выборку из чисел 2,3,4,5,6

Существует заголовочный файл pitches.

Заголовочный файл (header file) — это файл с расширением .h, содержащий объявления функций, классов, переменных, которые используются в файлах программы.

pitches.h - это вспомогательный заголовочный файл, содержащий определения частот для музыкальных нот. Он используется совместно с функцией tone() в Arduino для упрощения записи мелодий. Вместо ввода числовых значений частот вручную, можно использовать понятные музыкальные обозначения NOTE_C4, NOTE_G4 и т.д. Это повышает читаемость кода и облегчает работу с музыкальными проектами на Arduino. Библиотеку можно открыть по ссылке

Так как пьезодинамик работает с частотами, которые соответствуют стандартным музыкальным нотам, то:

Например: 659 Гц=Е5 (Ми пятой октавы)

784 Гц=G5 (Соль пятой октавы)

587 Гц=D5 (Ре пятой октавы)

Для упрощения записи пинов для светодиодов в программе будем использовать массив значений.

Массивы - именованный набор однотипных переменных с доступом к отдельным элементам по их индексу.

Все элементы массива должны быть одного типа, например все элементы могут быть числами, буквами, словами или другими объектами.

Массив имеет определенный размер, который задается при его создании, и в некоторых случаях, может изменяться (динамический массив).

Массивы используются в программировании для решения различных задач, например:

Хранение данных (списков чисел, строк или объектов).

Реализация алгоритмов. Массивы служат основой для многих алгоритмов, таких как сортировка или поиск.

Обработка данных. С помощью массивов можно фильтровать, преобразовывать данные (находить среднее значение, исключать дублирующиеся элементы).

Пример объявления массива:

int numbers = [10,20,30,40,50]

numbers — это имя массива.

10,20,30,40,50 - элементы массива.

Индексы элементов начинаются с нуля, то есть 0 это элемент массива 10, 1 - 20, 2 - 30, 3 - 40,4 - 50.

Чтобы получить доступ к элементу 30 мы должны использовать индекс 2.

number = numbers[2] // переменная number теперь равна 30.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.7.1.).

Подключение 5 светодиодов и резисторов:

Светодиод 1 (подключен к пину 2 Arduino):

Анод (длинная ножка) → пин 2 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Светодиод 2 (подключен к пину 3 Arduino):

Анод (длинная ножка) → пин 3 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Светодиод 3 (подключен к пину 4 Arduino):

Анод (длинная ножка) → пин 4 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Светодиод 4 (подключен к пину 5 Arduino):

Анод (длинная ножка) → пин 5 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Светодиод 5 (подключен к пину 6 Arduino):

Анод (длинная ножка) → пин 6 Arduino

Катод (короткая ножка) → резистор 220 Ом → GND

Подключение пьезодинамика (buzzerPin = 8):

Анод пьезодинамика (длинная ножка/красный провод) → пин 8 Arduino

Катод пьезодинамика (короткая ножка/черный провод) → шина GND макетной платы

Изображение 73

Рисунок 3.7.1 Внешний вид схемы сборки лабораторной работы 7

Рабочая программа на графическом языке программирования:

Изображение 74

Изображение 75

Изображение 76

Изображение 77

Рисунок 3.7.2 Программа лабораторной работы 7 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Создается целочисленная переменная tonePin для хранения 8 номера пина пьезодинамика;

  2. Создается список типа int leds из 5 элементов, содержащий пины светодиодов (2,3,4,5,6);

  3. Запускается цикл по переменной I от 0 до 5 с шагом 1. Внутри цикла каждый пин из списка leds поочередно настраивается как выход (OUTPUT) для управления светодиодами.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Вызывается метод jingleBells, который отвечает за воспроизведение мелодии;

  2. Вызывается метод sparkleEffect, который создает эффект мигания светодиодами;

  3. Выполняется задержка в 2000 мс перед повторением цикла.

Блок "Метод playNote" содержит последовательность действий:

  1. Создается переменная play, в которую записывается случайное целое число от 0 до 5 (номер светодиода из списка);

  2. Выбранный светодиод из списка leds включается (HIGH);

  3. На пин tonePin подается звуковой сигнал с частотой note на время duration;

  4. Выполняется задержка duration мс (длительность ноты);

  5. Запускается цикл по переменной i от 0 до 5 с шагом 1;

  6. Внутри цикла все светодиоды из списка leds выключаются (LOW);

  7. Выполняется дополнительная задержка, равная duration*0,3 мс.

Блок "Метод sparkleEffect" содержит последовательность действий:

  1. Запускается цикл по переменой i от 0 до 50 с шагом 1;

  2. Внутри цикла создается переменная led, в которую записывается случайное число от 0 до 5 (номер светодиода);

  3. Выбранный светодиод из списка leds включается (HIGH);

  4. Выполняется задержка 50 мс;

  5. Тот же светодиод выключается (LOW);

  6. Цикл повторяется, создавая эффект случайного мерцания светодиодов.

Блок "Метод jingleBells":

Вызывает созданный ранее метод «playNote», в котором переменная note – частота в Гц для пьезодинамика, а переменная duration – задержка по времени мс (то есть сколько будет длиться подаваемый импульс для различных нот). Таким образом при вызове метода проигрывания «playNote» с задержками в 200 мс получается узнаваемая мелодия.

Рабочая программа для Arduino IDE:

cpp
int tonePin = 8; // пьезодинамик подключена к 8 цифровому пину
int leds[] = {2, 3, 4, 5, 6}; // пины светодиодов, задается массивом(0-пин 2, 1-пин 3, 2-пин 4, 3-пин 5, 4-пин 6)
void setup() {
for(int i=0; i<5; i++) {
pinMode(leds[i], OUTPUT);//задаем пины 2,3,4,5,6 как выходы
}
}
void playNote(int note, int duration) {//создаем функцию, которую будем вызывать ниже. Создаем переменные note - частота звука в герцах, duration - длительность звука в миллисекундах.
// включаем случайный светодиод
int play = random(0,5);//генерация случайного числа 0-4
digitalWrite(leds[play], HIGH); //включение случайного светодиода, который выбран случайной генерацией сверху
tone(tonePin, note, duration); //генерация звука
delay(duration); //задержка по времени = duration, значение которой задается при вызове функции playNote
// выключаем все светодиоды
for(int i=0; i<5; i++) {
digitalWrite(leds[i], LOW);
}
delay(duration*0.3); // короткая пауза между нотами (уменьшаем duration на 30%)
}
void jingleBells() {//функция проигрывания песни "JingleBells"
// Первая фраза "Jingle Bells"
//вызываем ранее созданную функцию playNote.

playNote(659, 200); // частота note=659 Гц, длительность duration=200 мс (нота E5 из библиотеки pithes.h)

playNote(659, 200); // нота E5, длительность=200 мс

playNote(659, 400); // нота E5, длительность=400 мс

delay(200);

playNote(659, 200); // нота E5, длительность=200 мс

playNote(659, 200); // нота E5, длительность=200 мс

playNote(659, 400); // нота E5, длительность=400 мс

delay(200);

playNote(659, 200); // нота E5, длительность=200 мс

playNote(784, 200); // нота G5, длительность=200 мс

playNote(523, 300); // нота C5, длительность=300 мс

playNote(587, 100); // нота D5, длительность=100 мс

playNote(659, 800); // нота E5, длительность=800 мс

cpp
delay(400);
// вторая фраза

playNote(698, 200); // нота F5, длительность=200 мс

playNote(698, 200); // нота F5, длительность=200 мс

playNote(698, 200); // нота F5, длительность=200 мс

playNote(698, 200); // нота F5, длительность=200 мс

playNote(698, 200); // нота F5, длительность=200 мс

playNote(659, 200); // нота E5, длительность=200 мс

playNote(659, 200); // нота E5, длительность=200 мс

playNote(659, 200); // нота E5, длительность=200 мс

delay(200);

playNote(659, 200); // нота E5, длительность=200 мс

playNote(587, 200); // нота D5, длительность=200 мс

playNote(587, 200); // нота D5, длительность=200 мс

playNote(659, 200); // нота E5, длительность=200 мс

playNote(587, 400); // нота D5, длительность=400 мс

playNote(784, 400); // нота G5, длительность=400 мс

cpp
delay(400);
}
void loop() {

jingleBells(); //вызов функции проигрывания мелодии

sparkleEffect(); //вызов функции мерцания светодиодами

cpp
delay(2000); // пауза перед повторением
}
// создаем функцию для светодиодов эффекта мерцания гирлянды
void sparkleEffect() {
for (int i = 0; i < 50; i++) {//50 циклов включения/выключения светодиодов
int led = random(0, 5);//случайный выбор светодиода
digitalWrite(leds[led], HIGH);//присваивание "логической единицы" - включение светодиода
delay(50);//задержка 50 мс
digitalWrite(leds[led], LOW);//присваивание "логического нуля" - выключение светодиода
}
}

3.8 Лабораторная работа 8. Семисегментный индикатор

Цель работы:

Ознакомление с принципом работы семисегментного индикатора и разработать программу для отображения цифр на индикаторе.

Оборудование: микроконтроллер, макетная плата, резистор на 220 Ом (7 шт.), семисегментный индикатор (1 шт.), провода.

Теоретическая часть:

Семисегментный индикатор - устройство отображения цифровой информации, состоящее из семи светодиодных сегментов, расположенных в форме восьмерки. Дополнительно может иметь восьмой элемент - точку.

Каждый сегмент (обозначаемый буквами A-G) представляет собой отдельный светодиод. Для отображения цифры необходимо включить соответствующие сегменты.

Практическая часть:

В данной работе необходимо разработать программу, которая будет отображать данные на семисегментном индикаторе.

Для того, чтобы вывести цифру на индикаторе мы должны подать правильные сигналы (HIGH/LOW) на каждый из семи сегментов одновременно. Для этого удобно использовать массив, где для каждой цифры записано какие сегменты нужно включить (1) или выключить (0).

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.8.1).

Подключение сегментов (через резисторы 220 Ом):

Сегмент A → резистор 220 Ом → пин 2 Arduino

Сегмент B → резистор 220 Ом → пин 3 Arduino

Сегмент C → резистор 220 Ом → пин 4 Arduino

Сегмент D → резистор 220 Ом → пин 5 Arduino

Сегмент E → резистор 220 Ом → пин 6 Arduino

Сегмент F → резистор 220 Ом → пин 7 Arduino

Сегмент G → резистор 220 Ом → пин 8 Arduino

Подключение общего вывода:

Общий катод (COM) → GND Arduino

Каждый анодный вывод должен быть подключен через резистор 220 Ом, чтобы не сжечь светодиоды или выводы микроконтроллера.

Изображение 78

Рисунок 3.8.1 Внешний вид схемы сборки лабораторной работы 8

Рабочая программа на графическом языке программирования:

Изображение 79

Изображение 80

Рисунок 3.8.2. Программа лабораторной работы 8 на графическом языке программирования

Блок "Выполнить разово»:

  1. Инициализирует 2,3,4,5,6,7,8 пины как выход (OUTPUT);

  2. Задание двумерного массива 10 на 7 (то есть 10 строк и 7 столбцов) для написания цифр от 0 до 9 при зажигании различных сегментов семисегментного индикатора;

Метод num_write c: numberM:

  1. Устанавливается значение целочисленной переменной pin =2, то есть начинаем с пина, к которому подключен сегмент А

  2. Цикл по переменной i2 от 0 до 7 с шагом 1:

digitalWrite списка пинов (2,3,4,5,6,7,8) берется из созданного ранее двумерного массива состояние текущего выбранного пина (то есть на какой пин сейчас необходимо подать питание, чтобы написать цифру).

«Установить pin в pin+1» - цикл переходит к следующему пину (3,4,5,6,7,8). Выполняется задержка в 50 мс.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Цикл по переменной i от 10 до 0 с шагом -1 (запуск цикла обратного счетчика от 10 до 1, 10 соответствует выводимой цифре 9, 9 соответствует выводимой цифре 8 и тд.): сначала идет задержка в 1000 мс, после вызывается метод num_write, в который передается переменная i-1.

  2. Задержка в 3000 мс.

Рабочая программа для Arduino IDE:

cpp
// мы создаем специальную таблицу (двумерный массив) под названием `num_array`.
// в этой таблице 10 строк (от 0 до 9) и 7 колонок (по одной на каждый сегмент A, B, C, D, E, F, G).
// в каждой ячейке таблицы стоит либо 1 (сегмент ГОРИТ), либо 0 (сегмент НЕ ГОРИТ).
int num_array[10][7] = {

{1, 1, 1, 1, 1, 1, 0}, // строка 0: как вывести цифру "0" (горят все сегменты КРОМЕ G)

{0, 1, 1, 0, 0, 0, 0}, // строка 1: как вывести цифру "1" (горят только B и C)

{1, 1, 0, 1, 1, 0, 1}, // строка 2: как вывести цифру "2"

{1, 1, 1, 1, 0, 0, 1}, // строка 3: как вывести цифру "3"

{0, 1, 1, 0, 0, 1, 1}, // строка 4: как вывести цифру "4"

{1, 0, 1, 1, 0, 1, 1}, // строка 5: как вывести цифру "5"

{1, 0, 1, 1, 1, 1, 1}, // строка 6: как вывести цифру "6"

{1, 1, 1, 0, 0, 0, 0}, // строка 7: как вывести цифру "7"

{1, 1, 1, 1, 1, 1, 1}, // строка 8: как вывести цифру "8" (горят ВСЕ сегменты)

{1, 1, 1, 1, 0, 1, 1} // строка 9: как вывести цифру "9"

cpp
};
// Примечание: Этот массив рассчитан на индикатор с общим катодом (Common Cathode), какой представлен в нашем наборе!
// если у вас индикатор с общим АНОДОМ (Common Anode), нужно везде заменить 1 на 0 и 0 на 1!
// Объявляем функцию
// эта функция будет принимать одно целое число (цифру от 0 до 9) и ничего не возвращать (`void`)."
void Num_Write(int number);
void setup() {
// пины с номерами 2, 3, 4, 5, 6, 7, 8 будут использоваться как ВЫХОДЫ (OUTPUT).
// к этим выводам мы подключим сегменты индикатора (A, B, C, D, E, F, G соответственно).
pinMode(2, OUTPUT); // Предположим, пин 2 -> Сегмент A
pinMode(3, OUTPUT); // Пин 3 -> Сегмент B
pinMode(4, OUTPUT); // Пин 4 -> Сегмент C
pinMode(5, OUTPUT); // Пин 5 -> Сегмент D
pinMode(6, OUTPUT); // Пин 6 -> Сегмент E
pinMode(7, OUTPUT); // Пин 7 -> Сегмент F
pinMode(8, OUTPUT); // Пин 8 -> Сегмент G
// ПРИМЕЧАНИЕ: Общий вывод (катод или анод) индикатора подключается к GND или VCC через резистор!
}
void loop() {
// запускаем обратный счетчик от 10 вниз до 1 (counter = 10; counter> 0; --counter)
for (int counter = 10; counter > 0; --counter) {
delay(1000); // Ждем 1000 миллисекунд (1 секунду) - видим текущую цифру
// вызываем функцию Num_Write, чтобы показать цифру.
// внимание! Мы передаем `counter-1`:
// counter=10 -> покажи цифру 9 (10-1=9)
// counter=9 -> покажи цифру 8 (9-1=8)
// ...
// counter=1 -> покажи цифру 0 (1-1=0)
Num_Write(counter - 1);
}
delay(3000); // после показа всех цифр (9->0) ждем 3 секунды (экран пустой), потом цикл начнется снова.
}
// Функция Num_Write - "рисуем" цифру на индикаторе
// эта функция получает число `number` (от 0 до 9) и зажигает сегменты индикатора согласно нашему массиву `num_array`.
void Num_Write(int number) {
int pin = 2; // начинаем с первого пина, к которому подключен сегмент A (это пин 2)
// проходим по всем 7 сегментам (j от 0 до 6)
for (int j = 0; j <7; j++) {
// смотрим в нашу таблицу `num_array`:
// `number` - какая цифра (0...9) -> выбираем строку
// `j` - какой сегмент (0=A, 1=B, 2=C, 3=D, 4=E, 5=F, 6=G) -> выбираем колонку
// `num_array[number][j]` - значение из ячейки (1=HIGH/Зажечь, 0=LOW/Погасить)
// подаем это значение (HIGH или LOW) на текущий пин `pin`.
digitalWrite(pin, num_array[number][j]);
pin++; // переходим к следующему пину (3, 4, 5, 6, 7, 8)
}
}

3.9 Лабораторная работа 9. Фоторезистор

Цель работы:

Изучение принципа работы фоторезистора и создание системы автоматической регулировки яркости светодиода в зависимости от уровня освещенности с использованием ШИМ (PWM).

Оборудование: микроконтроллер, макетная плата, резистор на 220 Ом (2 шт.), светодиод (1 шт.), фоторезистор (1 шт.), провода.

Теоретическая часть:

Фоторезистор - полупроводниковый прибор, сопротивление которого изменяется в зависимости от интенсивности падающего света. Принцип работы основан на внутреннем фотоэффекте: под действием света в полупроводнике увеличивается количество свободных носителей заряда, что приводит к уменьшению сопротивления.

Широтно-импульсная модуляция (PWM) позволяет регулировать среднюю мощность, подаваемую на нагрузки, путем изменения импульсов.

Практическая часть

Для разработки программы, позволяющей управлять яркостью светодиода с помощью фоторезистора, необходимо прежде научиться использовать операторы ветвления, такие как if, else.

  1. Операторы ветвления if, else

Описание:

Оператор If используется вместе с операторами сравнения, проверяет выполнено ли условие. При выполнении условия, выполняются определенные операции внутри функции, в случае невыполнения условия, функция пропускается.

Синтаксис:

cpp
if (условие)
{

Действия.

}

Порой, при невыполнении условия пропустить код недостаточно, необходимо провести различные операции и при выполнении условия, и при невыполнении. Для таких случаев добавляется дополнительная конструкция else.

Синтаксис:

if (условие)

((условие выполнено))

{

Действия А.

cpp
}
else

((условие не выполнено))

{

Действия B.

}

Также можно использовать такие дополнительные конструкции, как else if. Они позволяют создать дополнительное условие при невыполнении главного условия оператора с выполнением определенных действий при выполнении этого дополнительного условия.

Синтаксис:

if (условие 1)

((условие выполнено))

{

Действия А.

cpp
}
else if (условие 2)

((условие 1 не выполнено, но условие 2 выполнено))

{

Действия B.

cpp
}
else

((ни одно из условий не выполнено))

{

Действия С.

}

Количество else if в коде не ограничено.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.9.1.).

Подключение фоторезистора:

Один вывод фоторезистора → шина +5V

Второй вывод фоторезистора → аналоговый вход A0 и резистор 220 Ом

Второй вывод резистора 220 Ом → шина GND

Подключение светодиода (с ШИМ-управлением):

Анод светодиода (длинная ножка) → пин 7 Arduino (ШИМ-выход)

Катод светодиода (короткая ножка) → резистор 220 Ом → шина GND

Изображение 81

Рисунок 3.9.1. Внешний вид схемы сборки лабораторной работы 9

Рабочая программа на графическом языке программирования:

Изображение 82

Рисунок 3.9.2. Программа лабораторной работы 9 на графическом языке программирования

Блок "Выполнить разово»:

Инициализирует аналоговый пин фоторезистора A0 в целочисленную переменную pinPhoto как вход (INPUT) и цифровой пин 7 светодиода в переменную led как выход (OUTPUT)

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Чтение освещенности с аналогового пина фоторезистора в переменную raw;

  2. Цикл проверки освещенности: если значение меньше 600, то загорается светодиод (HIGH), иначе светодиод выключен (LOW);

  3. Задержка в 200 мс.

Рабочая программа для Arduino IDE:

cpp
const int pinPhoto = A0;//объявление константы: пин фоторезистора (аналоговый вход А0)
const int led = 7;//объявление константы: пин светодиода (цифровой выход 7)
int raw = 0;//инициализация переменной освещенности
void setup() {
pinMode( pinPhoto, INPUT );//устанавливаем фоторезистор на получение данных
pinMode( led, OUTPUT );//устанавливаем светодиод на выход
}
void loop() {
raw = analogRead( pinPhoto ); //чтение аналогового значения освещенности
if( raw < 600) //проверка условия освещенности (можете взять другое значение в зависимости от вашей обстановки)
digitalWrite( led, HIGH ); //подаем на светодиод питание
else
digitalWrite( led, LOW ); //если освещенность>600, то не подаем на светодиод питание
delay(200);
}

3.10 Лабораторная работа 10. Ультразвуковой дальномер

Цель работы:

Изучение принципов работы ультразвукового дальномера, освоение измерения расстояния с использованием времени прохождения звуковой волны и создание системы измерения дистанции с визуальной индикацией.

Оборудование: микроконтроллер, макетная плата, УЗ-датчик (1 шт.), провода.

Теоретическая часть:

Ультразвуковой дальномер – устройство, предназначенное для определения расстояния от датчика до объекта с помощью ультразвуковых волн. Принцип работы основан на эхолокации: измеряется время, за которое звуковая волна доходит до объекта и возвращается обратно.

Практическая часть:

В данной работе создана система измерения расстояния с ультразвуковым датчиком и визуальной индикацией результатов с помощью вывода в монитор порта.

Алгоритм измерения:

  1. Генерация импульса

  2. Излучение импульсов

  3. Приемник ждет отраженный сигнал

  4. Измерение времени

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.10.1.).

Ультразвуковой дальномер имеет 4 вывода:

VCC - питание (+5V)

Trig (trigger) - запуск измерения

Echo - прием отраженного сигнала

GND - земля

Подключаем VCC к 5V микроконтроллера, GND к GND, Trig к 12 пину, Echo к 11 пину.

Изображение 83

Рисунок 3.10.1. Внешний вид схемы сборки лабораторной работы 10

Рабочая программа на графическом языке программирования:

Изображение 84

Рисунок 3.10.3. Программа лабораторной работы 10 на графическом языке программирования

Блок "Выполнить разово»: инициализирует 12 TRIG пин УЗ-дальномера как выход (OUTPUT), 11 ECHO пин УЗ-дальномера как вход (INPUT) и Serial-порт на скорость 9600. В Serial выводится сообщение о готовности работы УЗ-дальномера.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Установить пин 12 в состояние LOW;

  2. Задержка 2 мкс;

  3. Установить пин 12 в состояние HIGH (подать питание);

  4. Задержка 10 мкс;

  5. Считывание времени длительности сигнала с 11 пина

  6. Расчет дистанции = время*скорость звука/2

  7. В Serial выводится сообщение с рассчитанной дистанцией в см;

  8. Задержка в 300 мс.

Рабочая программа для Arduino IDE:

cpp
// Цифровые пины для УЗ-дальномера
const int trigPin = 12; // пин TRIG
const int echoPin = 11; // пин ECHO
void setup() {
// Настраиваем пины
pinMode(trigPin, OUTPUT); //задаем выход для генерации УЗ-сигнала
pinMode(echoPin, INPUT); //задаем вход для приема отраженного сигнала
Serial.begin(9600); //инициализация монитора порта
Serial.println("Дальномер готов!"); //вывод сообщения в монитор порта
}
void loop() {
// отправляем ультразвуковой импульс
digitalWrite(trigPin, LOW);
delayMicroseconds(2); //гасим триггер УЗ дальномера на 2 мкс
digitalWrite(trigPin, HIGH);
delayMicroseconds(10); //подаем 10 мкс импульс
digitalWrite(trigPin, LOW);
// Ждем ответный сигнал
long duration = pulseIn(echoPin, HIGH); //измеряем длительность на echoPin
// Вычисляем расстояние (в сантиметрах)
int distance = duration * 0.034 / 2; //расстояние=(время*скорость звука)/2. Скорость звука=340 м/с=0.034 см/мкс. Делим на 2, тк сигнал идет до объекта и обратно
// выводим результат в монитор порта
Serial.print("Расстояние: ");
Serial.print(distance);
Serial.println(" см");
// пауза между измерениями
delay(300);
}

3.11 Лабораторная работа 11. LCD дисплей

Цель работы:

Изучение принципов работы LCD дисплея и интерфейса I2C, создание программы для вывода текстовой и числовой информации на дисплей.

Оборудование: микроконтроллер, макетная плата, LCD дисплей (1 шт.) и провода.

Теоретическая часть:

LCD дисплей - устройство отображения информации, использующее жидкие кристаллы. Принцип работы основан на изменении ориентации молекул жидких кристаллов под воздействием электрического поля, что изменяет поляризацию проходящего света.

LCD дисплеи бывают разных размеров, входящий в наш набор имеет размерность на 32 символа. То есть для вывода информации доступны 2 строки по 16 столбцов.

I2C - последовательная шина данных, используемая для связи между интегральными схемами.

Практическая часть:

В данной работе необходимо разработать управляющую программу микроконтроллера, которая будет выводить данные на LCD дисплей. Для этого необходимо рассмотреть некоторые сторонние библиотеки и их подключение, а также познакомиться с интерфейсом I2C.

  1. Подключение сторонних библиотек

Многие программы работают с библиотеками. Библиотека в программировании — это сборник подпрограмм или объектов, используемых для разработки управляющих программ. Обычно, при разработке различных программ применяется много стандартных элементов и алгоритмов. Нет необходимости каждый раз писать их с нуля - можно воспользоваться готовой библиотекой. В общем, библиотеки представляют собой уже написанные ранее наборы проверенного кода. Это готовые решения, которые можно присоединять к своим разрабатываемым программам.

Библиотека облегчает работу с определенным модулем или одним из типов модулей. Например, если необходимо вывести текст на LCD дисплей без подключения библиотеки, то нужно передать ему несколько байт команд и данных, что займёт несколько строк кода. А также необходимо знать тип микроконтроллера, под управлением которого работает LCD дисплей, назначение команд, которыми он управляется и многое другое. В то время, как при написании кода с использованием библиотеки LiquidCrystal_I2C.h можно вывести текст на дисплей, вызвав всего одну функцию библиотеки: lcd.print(“текст”).

Чтобы использовать стороннюю библиотеку в вашей управляющей программе, ее нужно скачать по ссылке. Скачанная zip библиотека добавляется в Arduino (заходим в скетч -> подключить библиотеку -> добавить .zip библиотеку).

Изображение 85

Рисунок 3.11.1. Подключение zip библиотеки

Описание некоторых методов библиотеки LiquidCrystal_I2C.h:

LiquidCrystall() - создает переменную типа LiquidCrystal и принимает параметры подключенного дисплея (количество строк и символов);

clear() - очистка экрана и возврат курсора в исходное положение;

setCursor() - установка курсора в позицию;

print() - выводит текст на дисплей;

createChar() - создает пользовательский символ для ЖК-дисплея.

  1. Интерфейс I2C

Последовательный протокол обмена данными IIC (также называемый I2C - Inter-Integrated Circuits, меж микросхемное соединение) использует для передачи данных две двунаправленные линии связи, которые называются шина последовательных данных SDA (Serial Data) и шина тактирования SCL (Serial Clock). Также имеются две линии для питания. Шины SDA и SCL подтягиваются к шине питания через резисторы.

В сети есть хотя бы одно ведущее устройство (Master), которое инициализирует передачу данных и генерирует сигналы синхронизации. Также в сети присутствуют ведомые устройства (Slave), которые передают данные по запросу ведущего. У каждого ведомого устройства есть уникальный адрес, по которому ведущий обращается к нему. Адрес устройства указывается в его паспорте. К одной шине I2C может быть подключено до 127 устройств, в том числе и несколько ведущих. К шине можно подключать устройства в процессе работы.

Для работы с Arduino по шине I2C предусмотрена стандартная библиотека Wire. Она имеет следующие функции:

Таблица 3.11.1. Функции библиотеки Wire

ФункцияНазначение
begin(address)Инициализация библиотеки и подключение к шине I2C; если не указан адрес, то присоединённое устройство считается ведущим; используется 7-битная адресация
requestFrom()Используется ведущим устройством для запроса определенного количества байтов от ведомого
beginTransmission(address)Начало передачи данных к ведомому устройству по определённому адресу
endTransmission()Прекращение передачи данных ведомому
write()Запись данных от ведомого в ответ на запрос
available()Возвращает количество байт информации,доступных для приема от ведомого
read()Чтения байта, переданного от ведомого ведущему или от ведущего к ведомому
onReceive()Указывает на функцию, которая должна быть вызвана, когда ведомое устройство получит передачу от ведущего
onRequest()Указывает на функцию, которая должна быть вызвана, когда ведущее устройство получит передачу от ведомого

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.11.2.).

Дисплей с шиной I2C имеет 4 вывода. Подключение VCC к 5V микроконтроллера, GND к GND, SDA и SCL к соответствующим на плате.

Изображение 86

Рисунок 3.11.2. Внешний вид схемы сборки лабораторной работы 11

Рабочая программа на графическом языке программирования:

Изображение 87

Рисунок 3.11.3. Программа лабораторной работы 11 на графическом языке программирования

Блок "Выполнить разово»:

  1. Инициализируется дисплей с адресом 0х27 на 16 ячеек и 2 строки;

  2. Устанавливается начальная позиция курсора первую строку (=0) на 4 символ (=3);

  3. Выводится сообщение message начиная с начальной позиции курсора;

  4. Устанавливается курсор на второю строку (=1) на 3 символ (=2);

  5. Выводится сообщение world!

Рабочая программа для Arduino IDE:

cpp
//Подключение библиотек для работы с ЖК экраном
#include <LiquidCrystal_I2C.h>
#include <Wire.h> //встроенная в Arduino библиотека
LiquidCrystal_I2C lcd(0x27,16,2) ;//задаем адрес дисплея как 0х27 и определяем в нем 2 строки и 16 символов на одну строку
void setup(){
lcd.init();//инициализируем LCD дисплей
lcd.backlight();//включаем подсветку дисплея
lcd.setCursor(3,0);//устанавливаем начальную позицию курсора на первой строке (0) и на 4 символ (3), так как отсчет ведем с нуля, то позиции указываются на единицу меньше
lcd.print("message");//пишем "message" начиная с начальной позиции курсора
lcd.setCursor(2,1);// устанавливаем курсор на второй нижней строке (1) и на третьей позиции в строке (2)
lcd.print("world!");
}
void loop(){
}

3.12 Лабораторная работа 12. Парктроник

Цель работы:

Разработка и программирование системы парковочного радара (парктроника). Ознакомление с принципами совместной работы различных компонентов микроконтроллерной системы для решения практической задачи.

Оборудование: микроконтроллер, макетная плата, УЗ дальномер (1 шт.), пьезодинамик (1 шт.), LCD дисплей (1 шт.) и провода.

Теоретическая часть:

Парковочный радар (парктроник) - вспомогательная система датчиков, устанавливаемая на автомобилях для облегчения маневрирования при парковке. Она предупреждает водителя о приближении к препятствию.

Ультразвуковые датчики устанавливаются в бампер автомобиля для измерения дистанции к ближайшим объектам по отраженному от них ультразвуку. Система работает по принципу эхолота: датчик генерирует ультразвуковой (порядка 40 кГц) импульс, а затем воспринимает отраженный окружающими объектами сигнал. Электронный блок измеряет время, прошедшее между излучением и приемом отраженного сигнала, и принимая скорость звука в воздухе за константу, вычисляет расстояние до объекта. На основании полученных сведений система издаёт прерывистый предупреждающий звук для индикации того, как далеко находится машина от препятствия.

Практическая часть:

В данной работе программируется парктроник, который будет измерять расстояние и производить звук, расстояние до объекта в см будет выводится на дисплей.

Необходимые библиотеки в Arduino IDE:

LiquidCrystal_I2C.h - это библиотека для Arduino, которая позволяет управлять жидкокристаллическими дисплеями (LCD) на основе контроллера HD44780 (или аналогов).

Wire.h — библиотека в Arduino, которая используется для работы с двухпроводным интерфейсом I2C.

С её помощью можно:

Инициализировать библиотеку и подключиться к шине I2C в качестве ведущего или ведомого устройства (команда Wire.begin()).

Считать байт, принятый от ведущего или ведомого устройства (функция Wire.read()).

Записать данные в устройство, являющееся ведомым или ведущим (функция Wire.write()).

У библиотеки Wire.h заранее определены имена объектов: Wire — для работы с основной (и единственной) аппаратной шиной I2C.

Также познакомимся с новым типом данных long. Он используется для хранения целых чисел в расширенном диапазоне от -2147483648 до 2147483647 и занимает 4 байта памяти.

Функция pulseIn() считывает длину сигнала на заданном порту (HIGH или LOW). Например, если задано считывание HIGH функцией pulseIn(), функция ожидает пока на заданном порту не появится HIGH. Когда HIGH получен, включается таймер, который будет остановлен, когда на порту вход/выход будет LOW. Функция pulseIn() возвращает длину сигнала в микросекундах. Функция возвращает 0, если в течение заданного времени не был зафиксирован сигнал на порту.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.12.1).

Дисплей подключается аналогично лабораторной работе 11.

Анод пьезодинамика (длинная ножка) → пин 8 контроллера

Катод пьезодинамика (короткая ножка) → линия GND макетной платы.

Подключаем УЗ-дальномер VCC общей линии питания от 5V микроконтроллера, GND к общей линии GND, Trig к 12 пину, Echo к 11 пину. микроконтроллера

Изображение 88

Рисунок 3.12.1. Внешний вид схемы сборки лабораторной работы 12

Рабочая программа на графическом языке программирования:

Изображение 89

Рисунок 3.12.2. Программа лабораторной работы 12 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Создается целочисленная переменная (int) trigPin для хранения адреса 12 пина УЗ-дальномера;

  2. Создается целочисленная переменная (int) echoPin для хранения 11 пина УЗ-дальномера;

  3. Создается целочисленная переменная (int) buzzerPin для хранения 8 цина пьезодинамика;

  4. Инициализация 12 пина как выход (OUTPUT), 11 как вход (INPUT), 8 как выход (OUTPUT);

  5. Инициализируется дисплей с адресом 0х27 на 16 ячеек и 2 строки;

  6. Выводится сообщение Parktronic начиная с начальной позиции, то есть на 1 строке с 1 символа;

  7. Задержка в 1000 мс;

  8. Очищается дисплей от написанных ранее символов.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Создаются переменные типа long duration и distance для хранения данных длительности импульса с УЗ-дальномера и измеренной дистанции;

  2. Установить пин trigPin в состояние LOW;

  3. Задержка 2 мкс;

  4. Установить пин trigPin в состояние HIGH (подать питание);

  5. Задержка 10 мкс;

  6. Установить пин trigPin в состояние LOW;

  7. В переменную duration записывается считанное время длительности с echoPin УЗ-дальномера;

  8. В переменную distance записывается рассчитанная дистанция = время длительности *скорость звука/2;

  9. Устанавливается позиция курсора дисплея на первую строку (=0) на 1 символ (=0);

  10. На дисплее выводится рассчитанная дистанция до объекта в см;

  11. Цикл «если иначе» аналогичен «if else». Если дистанция меньше 50 см, то:

  12. Создается переменная типа int beepDelay, которая зависит от значения переменной distance. С помощью блока «преобразования map» расстояние преобразуется в интервал задержки между звуковыми сигналами (то есть чем ближе объект, тем меньше задержка);

  13. Пьезодинамик издает звук с частотой 1000 Гц на 100 мс;

  14. Выполняется временная задержка beepDelay;

  15. Если же рассчитанная дистанция больше 50 см, то звук на пьезодинамике издаваться не будет;

  16. Задержка в 1000 мс.

Рабочая программа для Arduino IDE:

cpp
#include <Wire.h>
#include <LiquidCrystal_I2C.h> //Подключаем библиотеку для LCD (I2C)
// настройка LCD
LiquidCrystal_I2C lcd(0x27, 16, 2); // Адрес 0x27, 16 символов, 2 строки
// цифровые пины для УЗ-датчика
const int trigPin = 12;
const int echoPin = 11;
// устанавливаем цифровой пин для пьезодинамика
const int buzzerPin = 8;
// инициализируем константу максимального расстояния (см)
const int maxDistance = 200;
void setup() {
pinMode(trigPin, OUTPUT); //задаем выход для генерации УЗ-сигнала
pinMode(echoPin, INPUT); //задаем вход для приема отраженного сигнала
pinMode(buzzerPin, OUTPUT); //задаем выход для звукового сигнала
// инициализация LCD
lcd.init();
lcd.backlight(); //включение подсветки дисплея
lcd.print("Parktronic"); //выводим при инициализации дисплея сообщение
delay(1000);
lcd.clear(); //очищаем экран дисплея
}
void loop() {
// измеряем расстояние
long duration, distance; //объявляем переменные для длительности импульса и измеренного расстояния
digitalWrite(trigPin, LOW);
delayMicroseconds(2);//гасим триггер УЗ дальномера на 2 мкс
digitalWrite(trigPin, HIGH);
delayMicroseconds(10); //подаем 10 микросекундный импульс
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);//измеряем длительность на echoPin
distance = duration * 0.034 / 2; // Переводим в см (расстояние=(время*скорость звука)/2). Скорость звука=340 м/c=0,034 см/мкс, делим на 2 так как сигнал идет до объекта и обратно
// выводим на LCD
lcd.setCursor(0, 0);//устанавливаем курсор в начало первой строки
lcd.print("Distance: "); //выводим на дисплей сообщение "Distance:"
lcd.print(distance); //пишем после сообщения измеренное дальномером расстояние
lcd.print(" cm "); //выводим на дисплей сообщение "cm"
// управляем пьезопищалкой
if (distance < 50) { //если расстояние меньше 50 см, то включается пьезопищалка
int beepDelay = map(distance, 5, 50, 100, 500); // map преобразует расстояние в интервал задержки, то есть чем ближе объект, тем меньше задержка между звуковыми сигналами
tone(buzzerPin, 1000, 100); //звук на 1000 Гц длительностью 100 мс
delay(beepDelay); //пауза между звуковыми сигналами
} else {
noTone(buzzerPin); //если расстояние больше 50 см, то выключаем звук
}
delay(100);
}

3.13. Лабораторная работа 13. Игра Dino

Цель работы:

Разработка игры “Dino” с использованием LCD дисплея. Изучение принципов создания пользовательских символов, обработка ввода с кнопки и генерация звуковых эффектов.

Оборудование: микроконтроллер, макетная плата, тактовая кнопка (1 шт.), пьезодинамик (1 шт.), резистор на 10 кОм (1 шт.), LCD дисплей (1 шт.) и провода.

Теоретическая часть:

Графический LCD-дисплей состоит из сетки пикселей (маленьких точек), каждая из которых может быть включена (чёрная) или выключена (белая/прозрачная). Например, дисплей 128x64 содержит 128 столбцов и 64 строки пикселей.

Для управления этим дисплеем используется побайтовая передача данных. Один байт содержит 8 бит, и, соответственно, позволяет закрасить 8 пикселей по вертикали или горизонтали, в зависимости от архитектуры конкретного дисплея.

Для создания графики в игре (динозавр, кактус, птицы) используется механика пользовательских символов.

Как происходит рисование:

При выводе изображения или графики на LCD дисплей вся поверхность дисплея разбивается на ячейки по 8 пикселей (часто вертикально). Каждая ячейка соответствует одному байту памяти дисплея. Каждый байт определяет состояние 5 пикселей в строке (используются младшие 5 бит). Значение бита 1 означает включенный (темный) пиксель, 0 - выключенный.

Отправляя байт вида 0b11111111 (или 0xFF), мы включаем все 8 пикселей в этой ячейке (полностью закрашенная полоса).

Байт 0b00000000 — все пиксели выключены (прозрачные).

Байт 0b10101010 — включены через один (шахматный узор).

Таким образом, "рисование" на дисплее осуществляется через управление отдельными байтами, где каждый бит этого байта отвечает за один пиксель. Комбинируя различные байты, можно строить линии, фигуры, и даже простые изображения. Графический дисплей делится на страницы (pages) — горизонтальные полосы высотой в 8 пикселей. Таким образом, дисплей 128x64 имеет 8 страниц по 8 строк каждая. Каждую страницу можно рассматривать как массив из 128 байтов — по одному на каждый столбец.

Когда отправляется байт данных:

Необходимо сначала указывать координаты: номер страницы и столбца.

Затем отправляет байт, который закрашивает соответствующие 8 пикселей по вертикали в выбранной колонке.

Таким образом, управление дисплеем требует понимания структуры памяти дисплея, где каждая точка задается двумя координатами: X (столбец) и Y (бит в байте страницы).

Практическая часть:

Познакомимся с новым типом данных byte. Он представляет собой беззнаковые целые числа от 0 до 255 размером 8 бит.

Bool - логический тип данных. Переменная имеет два значения 0 или 1 или false или true.

В данной программе перед некоторыми переменными используется логический оператор НЕ (инвертор). В программе перед переменной он записывается в виде восклицательного знака (!). Оператор инверсии меняет значение на противоположное. Оператор ! работает только с переменными типа bool.

Если было true (истина), то станет false (ложь).

Если была false (ложь), то станет true (истина).

Пример:

lightOn = true (свет включен)

!lightOn = false (свет выключен).

Логика игры реализована в цикле loop(), который последовательно выполняет четыре ключевых этапа:

  1. Инициализация раунда: задается начальная позиция препятствия и его случайный тип (дерево, камень, птица).

  2. Обработка ввода: В цикле движения препятствия постоянно опрашивается состояние кнопки. При обнаружении нажатия (с фильтрацией дребезга) динозавру присваивается состояние "прыжок".

  3. Обновление игрового состояния (логика):

o Рассчитывается фаза прыжка (подъем и спуск), что определяет вертикальную позицию динозавра (0 — вверх, 1 — земля).

o Препятствие смещается на одну позицию влево.

o Проверяется столкновение: если координаты препятствия и динозавра совпали, игра завершается.

  1. Отрисовка: Экран очищается, и в рассчитанных координатах отображаются динозавр и движущееся препятствие.

  2. Синхронизация: Цикл замедляется на время pause, которое уменьшается с ростом уровня, создавая эффект ускорения игры.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.13.1.).

Дисплей подключается аналогично лабораторной работе 11.

Анод пьезодинамика (длинная ножка) → пин 10 контроллера

Катод пьезодинамика (короткая ножка) → линия GND макетной платы.

Тактовая кнопка одним выводом подключается к 8 пину, второй к линии GND с подтяжкой резистора в 10кОм.

Изображение 90

Рисунок 3.13.1. Внешний вид схемы сборки лабораторной работы 13

Рабочая программа на графическом языке программирования:

Изображение 91

Изображение 92

Изображение 93

Изображение 94

Изображение 95

Рисунок 3.13.2. Программа лабораторной работы 13 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Создается целочисленная переменная BUZZER для хранения 10 номера пина пьезодинамика;

  2. Создается целочисленная переменная BUTTON для хранения 8 номера пина кнопки;

  3. Инициализируется LCD-дисплей с адресом 0x27, размером 16 ячеек и 2 строки;

  4. Создается массив типа byte dino[8] — пользовательский символ динозавра (8 строк по 5 бит);

  5. Создается массив типа byte tree[8] — пользовательский символ дерева;

  6. Создается массив типа byte stone[8] — пользовательский символ камня;

  7. Создается массив типа byte bird[8] — пользовательский символ птицы;

  8. Устанавливается начальное состояние строковой переменной buttonWasUp = истина для детектирования состояния кнопки (кнопка начало считается отпущенной);

  9. Создается целочисленная переменная level и устанавливается начальное значение уровня = 1;

  10. Создается целочисленная переменная и устанавливается начальная задержка движения препятствий pause = 400;

  11. Создается строковая переменная и устанавливается начальная фаза прыжка jumpPhase = 0;

  12. Создается строковая переменная состояния прыжка isJumping = ложь;

  13. Инициализация 10 пина как выход (OUTPUT), 8 как вход (INPUT);

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

  15. В память дисплея записывается пользовательский символ дерева по адресу 1;

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

  17. В память дисплея записывается пользовательский символ птицы по адресу 3;

  18. Вызывается метод showStartMessage() для вывода стартового сообщения и звукового сигнала.

Блок "Метод showStartMessage» содержит последовательность действий:

  1. Курсор устанавливается в позицию (столбец 7, строка 0);

  2. На дисплей выводится текст "GO!";

  3. Выполняется задержка 400 мс;

  4. Подается звуковой сигнал на пьезодинамик (600 Гц);

  5. Выполняется задержка 100 мс;

  6. Звук выключается;

  7. Дисплей очищается;

Блок "Метод gameOver» содержит последовательность действий:

  1. Подается первый звуковой сигнал на пьезодинамик (50 Гц);

  2. Выполняется задержка 100 мс;

  3. Звук выключается;

  4. Выполняется задержка 100 мс;

  5. Подается второй звуковой сигнал (20 Гц);

  6. Выполняется задержка 300 мс;

  7. Звук выключается;

  8. Дисплей очищается;

  9. Курсор устанавливается в позицию: столбец 3, строка 0;

  10. На дисплей выводится сообщение "GAME OVER!";

  11. Курсор устанавливается в позицию: столбец 3, строка 1;

  12. На дисплей выводится текст "LEVEL: ";

  13. На дисплей выводится значение переменной level (уровень, на котором произошло столкновение);

  14. Выполняется задержка 3000 мс;

  15. Переменная level сбрасывается в 1;

  16. Переменная pause сбрасывается в 400 мс;

  17. Вызывается метод showStartMessage() для повторного запуска игры.

Блок "Выполнять в цикле» содержит последовательность действий:

  1. Подготовка новой «сцены» (нового препятствия):

а) Устанавливается положение динозавра по вертикали: dinoY = 1 (нижняя строка дисплея);

б) Устанавливается начальная X-координата препятствия: obstacleX = 15 (справа на экране);

в) Устанавливается начальная Y-координата препятствия: obstacleY = 1;

г) Случайным образом выбирается тип препятствия obstacleType:

  • 1 — дерево,

  • 2 — камень,

  • 3 — птица;

д) Если obstacleType == 3 (птица), то препятствие размещается на верхней строке: obstacleY = 0;

е) Иначе препятствие размещается на нижней строке: obstacleY = 1;

  1. Движение препятствия по экрану (внутренний цикл). Цикл выполняется, пока obstacleX > 0;

а) Обновление экрана и уровня:

  1. Очищается дисплей;

  2. Курсор устанавливается в начало первой строки (0,0);

  3. На дисплей выводится текст "Lvl:";

  4. На дисплей выводится текущее значение переменной level;

б) Обработка нажатия кнопки (прыжок):

  1. Считывается состояние кнопки с пина 8 в строковую переменную buttonIsUp;

2.Проверяется условие начала прыжка: ранее кнопка была отпущена (buttonWasUp = true), сейчас кнопка нажата (buttonIsUp), персонаж не прыгает (isJumping);

  1. Если условие выполнено:

  2. выполняется задержка 10 мс (антидребезг);

  3. повторно проверяется кнопка;

  4. устанавливается isJumping = истина;

  5. сбрасывается фаза прыжка jumpPhase = 0;

  6. подается короткий звуковой сигнал на пьезодинамик (200 Гц);

  7. задержка 100 мс;

  8. звук выключается;

  9. Сохраняется текущее состояние кнопки: buttonWasUp = buttonIsUp;

  10. Логика прыжка динозавра:

  11. Если isJumping = истина, выполняется анимация прыжка:

  12. Если jumpPhase < 3, то:

  • динозавр поднимается на верхнюю строку (dinoY = 0);

  • jumpPhase увеличивается на 1;

  1. Иначе (фаза прыжка 3 и более):
  • динозавр возвращается на нижнюю строку (dinoY = 1);

  • jumpPhase увеличивается на 1:

  • если jumpPhase > 6, то прыжок завершается: isJumping = ложь;

  1. Отрисовка персонажа и препятствия:

  2. Курсор устанавливается в позицию динозавра (столбец 4, строка dinoY);

  3. На дисплей выводится символ динозавра (адрес 0);

  4. Курсор устанавливается в позицию препятствия (столбец obstacleX, строка obstacleY);

  5. На дисплей выводится символ препятствия (obstacleType — адрес 1, 2 или 3);

  6. Проверка столкновения:

1.Проверяется условие столкновения на выполнение двух условий:

  • obstacleX ==4 (препятствие дошло до столбца динозавра),

  • obstacleY = dinoY (они находятся на одной строке).

    2.Если столкновение произошло:

  • вызывается метод gameOver(),

  • выполняется выход из функции «Выполнять в цикле» () ;

  1. Сдвиг препятствия:

  2. Выполняется задержка pause мс (скорость игры);

  3. Уменьшается координата препятствия (инкремент переменной) по X: obstacleX-- (препятствие движется влево);

  4. Действия после успешного прохождения препятствия:

  5. Подается короткий звуковой сигнал (800 Гц);

  6. Выполняется задержка 20 мс;

  7. Звук выключается;

  8. Увеличивается уровень игры: переменной level++;

  9. Уменьшается задержка pause на 20 мс для ускорения игры, но не ниже 100 мс.

Рабочая программа для Arduino IDE:

cpp
#include <LiquidCrystal_I2C.h>
#include "Wire.h"
LiquidCrystal_I2C LCD(0x27, 16, 2);
#define BUZZER 10 // 10 пин подключения пьезопищалки
#define BUTTON 8 // 8 пин подключения кнопки
// символы
byte dino[8] = //"Рисование" динозавра. Каждый символ задается массивом из 8 байтов

{0b01110,//строка 0: горят средние 2,3,4 пиксели из пяти

0b11011, //строка 1: горят первые два и последние два пикселя

0b11111, //строка 2 : горят все пять пикселей

0b11100, //строка 3: горят первые три пикселя

0b11111,//строка 4: горят все пять пикселей

0b01100, //строка 5 : горят 2 и 3 пиксели

0b10010, //строка 6: горят 1 и 4 пиксели

0b11011}; //строка 7: горят 1,2,4,5 пиксели

cpp
byte tree[8] = {0b00000, 0b00000, 0b00000, 0b11011, 0b11011, 0b11011, 0b01100, 0b01100};//"Рисование" дерева аналогично динозавру
byte stone[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b01110, 0b11111};//"Рисование" камня
byte bird[8] = {0b00100, 0b00101, 0b01111, 0b11111, 0b10100, 0b00100, 0b00000, 0b00000}; //"Рисование" птицы
bool buttonWasUp = true; //переменная детектирования нажатия кнопки (для начала устанавливаем true)
int level = 1; //инициализации уровня игры (начальное значение 1)
int pause = 400;//инициализация переменной паузы=400 мс
byte jumpPhase = 0; //текущая фаза прыжка
bool isJumping = false; //переменная состояния прыжка
void setup() {
pinMode(BUZZER, OUTPUT); //устанавливаем пьезо пищалку на выход
pinMode(BUTTON, INPUT_PULLUP); //устанавливаем кнопку на вход получения данных

LCD.init(); //инициализация дисплея

LCD.backlight(); //включение подсветки дисплея

LCD.createChar(0, dino); //регистрируем созданные нами символы для ЖК-дисплея. Нулевой символ - динозавр.

LCD.createChar(1, tree); //регистрируем созданные нами символы для ЖК-дисплея. Первый символ - дерево.

LCD.createChar(2, stone); //регистрируем созданные нами символы для ЖК-дисплея. Второй символ - камень.

LCD.createChar(3, bird); //регистрируем созданные нами символы для ЖК-дисплея. Третий символ - птица.

showStartMessage(); //вывод начального сообщения

cpp
}
void showStartMessage() { //создаем функция для вывода начального сообщения

LCD.setCursor(7, 0); //ставим курсор на первую строку в 7 положение

LCD.print("GO!"); //выводи сообщение

cpp
delay(400); //пауза в 400 мс
tone(BUZZER, 600); //подаем звуковой сигнал в 600 Гц
delay(100); //пауза в 100 мс
noTone(BUZZER); //отключаем пьезо пищалку

LCD.clear(); //очищаем экран дисплея

cpp
}
void loop() {
byte dinoY = 1; // стартовая позиция динозавра (1 = на земле)
byte obstacleX = 15; //стартовая Х позиция препятствия
byte obstacleY = 1; //стартовая У позиция препятствия
byte obstacleType = random(1, 4); //вывод случайного препятствия(1-дерево, 2-камень, 3-птица)
if (obstacleType == 3) obstacleY = 0; // если тип препятствия птица, то отправляем ее на первую строчку
else obstacleY = 1; // если тип препятствия дерево или камень, то выводим их на второй строчке
while (obstacleX > 0) { //пока позиция препятствия по Х больше 0

LCD.clear(); //очищаем экран

LCD.setCursor(0, 0); //ставим курсор в начало

LCD.print("Lvl:"); //выводим сообщение на экран об уровне игры

LCD.print(level);//выводим сообщение об уровне игры

cpp
// обработка прыжка
bool buttonIsUp = digitalRead(BUTTON);//переменная считывания нажатия на кнопку
if (buttonWasUp && !buttonIsUp && !isJumping) { //если на предыдущей итерации циклы кнопка была не нажата, и кнопка сейчас нажата, и динозавр в данный момент стоит на земле и не выполняет прыжок (! это логический оператор НЕ)
delay(10); // антидребезг
if (!digitalRead(BUTTON)) { //повторная проверка нажатия кнопки
isJumping = true; //переменная состояния прыжка - TRUE
jumpPhase = 0; //текущая фаза прыжка = 1
tone(BUZZER, 200); //подаем звуковой сигнал в 200 Гц
delay(100); //пауза в 100 мс
noTone(BUZZER); //отключаем пьезо пищалку
}
}
buttonWasUp = buttonIsUp; //фиксируем предыдущее состояние кнопки для корректной обработки нажатий
// анимация прыжка
if (isJumping) {
if (jumpPhase < 3) { //фаза 0-2
dinoY = 0; // динозавр в верхней позиции
jumpPhase++;
} else { //если фаза 3-6
dinoY = 1; // динозавр опускается вниз
jumpPhase++;
if (jumpPhase > 6) { //если фаза больше 6
isJumping = false; //завершение прыжка
}
}
}
// отрисовка динозавра и препятствия

LCD.setCursor(4, dinoY); //ставим курсор на 4 столбец, строку dinoY

LCD.write(0); //вывод на дисплей динозавра

LCD.setCursor(obstacleX, obstacleY); //ставим курсор на позицию препятствия

LCD.write(obstacleType); //отрисовываем препятствие

cpp
// Проверка столкновения
if (obstacleX == 4 && obstacleY == dinoY) { если препятствие на позиции динозавра и на строке dinoY

gameOver(); //вызов функции конца игры

cpp
return; //выход из цикла
}
delay(pause); //пауза в 400 мс
obstacleX--; //смещение позиции влево на 1 позицию
}
// уровень пройден
tone(BUZZER, 800); //подаем звуковой сигнал в 800 Гц
delay(20); //рауза в 20 мс
noTone(BUZZER); //отключаем пьезо пищалку
level++; //увеличение на 1 переменной уровня игры
pause = max(pause - 20, 100); // ускорение, но не менее 100ms
}
void gameOver() { //функция конца игры
tone(BUZZER, 50); //подаем звуковой сигнал в 50 Гц
delay(100); //пауза в 100 мс
noTone(BUZZER); //отключаем пьезо пищалку
delay(100); //пауза в 100 мс
tone(BUZZER, 20); //подаем звуковой сигнал в 20 Гц
delay(300); //пауза в 300 мс
noTone(BUZZER); //отключаем пьезо пищалку

LCD.clear(); //очищаем экран дисплея

LCD.setCursor(3, 0); //устанавливаем курсор на 1 строчку в позицию 3

LCD.print("GAME OVER!"); //выводим сообщение об окончании игры

LCD.setCursor(3, 1); //устанавливаем курсор на 2 строчку в позицию 3

LCD.print("LEVEL: ");

LCD.print(level); //выводим пройденный уровень игры

cpp
delay(3000); //пауза в 3000 мс
level = 1; //передаем переменной уровня игры 1
pause = 400; //пауза в 400 мс

showStartMessage(); //показываем на экране стартовое сообщение

}

3.14 Лабораторная работа 14. Сервопривод

Цель работы:

Освоение базовых принципов управления сервоприводом.

Оборудование: микроконтроллер, сервопривод (1 шт.).

Теоретическая часть:

Сервопривод управляется с помощью импульсно-модулированного сигнала (ШИМ). Подаются управляющие импульсы с фиксированной частотой, но с переменной длительностью.

Длительность импульса определяет угол поворота вала:

  • 1 мс = угол 0° (крайнее левое положение сервопривода)

  • 1.5 мс = угол 90° (среднее положение сервопривода)

  • 2 мс = 180° (крайнее правое положение сервопривода)

Внутри сервопривода есть схема сравнения, которая измеряет длительность полученного импульса и устанавливает мотор в соответствующее положение через систему обратной связи.

Библиотека Servo (функционал которой будет рассмотрен ниже) автоматически генерирует правильные ШИМ-сигналы, освобождая нас от необходимости рассчитывать временные интервалы вручную.

Практическая часть

В данной лабораторной работе разрабатывается программа для циклического перемещения сервопривода между тремя ключевыми позициями: 0°, 90°, 180°.

Для реализации этого проекта необходимо рассмотреть функционал библиотеки Servo.

  1. Библиотека Servo.

Описание:

Библиотека функций для Arduino-контроллера представляет собой набор функций для управления сервоприводами. Стандартные сервоприводы позволяют поворачивать привод на определенный угол, обычно от 0 до 180 градусов. Некоторые из сервоприводов позволяют совершать полные обороты на заданной скорости.

Библиотека Servo позволяет одновременно управлять 12-ю сервоприводами на большинстве плат Arduino и 48-ю - на Arduino Leonardo. На контроллерах, отличных от Mega, использование библиотеки отключает возможность использовать выходы 9 и 10 в режиме ШИМ, даже если привод не подключен к этим выводам. На плате могут быть использованы до 12 сервоприводов без потери функционала ШИМ. При использовании Arduino Leonardo для управления от 12 до 23 сервоприводов, нельзя будет использовать выходы 11 и 12 для ШИМ.

Основные функции:

  1. Servo.attach()

Описание:

Подключает Servo к указанному выходу, с которого осуществляется управление приводом.

Синтаксис:

cpp
servo.attach(pin)
servo.attach(pin, min, max)

Параметры:

servo: переменная типа Servo.

pin: номер выхода, к которому подключаем servo и с которого осуществляется управление приводом.

min: ширина импульса в микросекундах, соответствующая минимальному (угол 0 градусов) положению сервопривода (по умолчанию 544).

max: ширина импульса в микросекундах, соответствующая максимальному (угол 180 градусов) положению сервопривода.

  1. Servo.write(angle)

Описание:

Передает значения для управления приводом. Для стандартного сервопривода это угол поворота. Для привода постоянного вращения функция задает его скорость вращения (0 - для максимальной скорости вращения в одну сторону, 180 - для максимальной скорости в другую сторону и значение около 90 - для неподвижного состояния).

Синтаксис:

servo.write(angle)

Параметры:

servo: переменная типа Servo.

angle: значение, записываемое в servo, от 0 до 180.

  1. Servo.writeMicroseconds()

Описание:

Передает значение для управления сервоприводом в микросекундах (uS), устанавливая угол поворота на это значение. Для стандартного привода значение 1000 - максимальный поворот против часовой стрелки, 2000 - максимальный поворот по часовой стрелке, 1500 - посередине.

Замечание: некоторые производители не придерживаются стандартных значений, и такие приводы могут управляться значениями от 700 до 2300. Поэкспериментируйте со значениями до момента, пока привод не повернется и остановится в крайнем положении. Тем не менее, следует избегать постоянного использования привода на значениях, больше допустимых.

Приводы постоянного вращения реагируют на данную команду, подобно реакции на функцию write().

Синтаксис:

servo.writeMicroseconds(ms)

Параметры:

servo: переменная типа Servo.

ms: значение в микросекундах (int).

  1. Servo.read()

Описание:

Считывает значение текущего положения сервопривода (значение, записанное последним вызовом функции write()).

Синтаксис:

servo.read()

Параметры:

servo: переменная типа Servo.

Возвращаемое значение:

Положение (угол) сервопривода от 0 до 180.

  1. Servo.attached()

Описание:

Проверяет, подключена ли переменная Servo к выходу.

Синтаксис:

servo.attached()

Параметры:

servo: переменная типа Servo

Возвращаемое значение:

True («ИСТИНА»), если подключена; false («ЛОЖЬ») - в противном случае.

  1. Servo.detach()

Описание:

Отсоединяет переменную Servo от указанного выхода.

Синтаксис:

servo.detach()

Параметры:

servo: переменная типа Servo.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.14.1).

Для подключения сервоприводов к контроллеру КПМИС используются три провода (земля (GND), питание (+5V), сигнал (цифровой пин)). Подключите провода сервопривода к соответствующим пинам на контроллере, а сигнальный провод подключите к 9 пину. Для стабильной работы при питании от сети используйте сетевой адаптер.

Изображение 96

Рисунок 3.14.1. Внешний вид схемы сборки лабораторной работы 14

Рабочая программа на графическом языке программирования:

Изображение 97

Рисунок 3.14.2. Программа лабораторной работы 14 на графическом языке программирования

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Устанавливает сервопривод, подключенный к 9 пину в положение 90 градусов;

  2. Задержка в 500 мс;

  3. Устанавливает сервопривод, подключенный к 9 пину в положение 0 градусов;

  4. Задержка в 500 мс;

  5. Устанавливает сервопривод, подключенный к 9 пину в положение 180 градусов;

  6. Задержка в 500 мс.

Рабочая программа для Arduino IDE:

cpp
#include <Servo.h> //подключение библиотеки
Servo myservo; //создание объекта myservo для работы с сервоприводом
void setup() {

myservo.attach(9); //подключение сервопривода к 9 пину

cpp
}
void loop() {

myservo.write(90); //установка сервопривода в положение 90 градусов

delay(500); //пауза в 0,5 с

myservo.write(0); //установка сервопривода в положение 0 градусов

delay(500); //пауза в 0,5 с

myservo.write(180); //установка сервопривода в положение 180 градусов

cpp
delay(500); //пауза в 0,5 с
}

3.15 Лабораторная работа 15. Двигатель постоянного тока

Цель работы:

Изучить принцип управления направлением и скоростью вращения ДПТ с помощью ШИМ-сигнала и алгоритм дистанционного управления моделью машинки через монитор последовательного порта.

Оборудование: микроконтроллер, машинка в собранном виде и ДПТ (2 шт.).

Теоретическая часть:

Двигатель постоянного тока меняет направление вращения при смене полярности напряжения на его выводах. Цифровой вывод микроконтроллера задает уровень напряжение (HIGH/LOW) для коммутации полярности питания двигателя.

Для движения машинки используются два двигателя постоянного тока. Чтобы заставить такой двигатель вращаться, необходимо подать на его выводы постоянное напряжение. Чем выше напряжение, тем быстрее вращается вал двигателя.

Широтно-импульсная модуляция (ШИМ) используется для управления двигателем постоянного тока для регулирования скорости вращения. Этот метод позволяет минимизировать энергетические потери и обеспечить плавное изменение скорости посредством модуляции длительности импульсов при постоянной частоте.

На обмотку двигателя подаются импульсы напряжения с различной длительностью, но с постоянной частотой. Изменение скорости вращения достигается посредством изменения скважности импульсов - отношения периода импульсов к их длительности. Чем ниже скважность, тем выше проходит средний ток через обмотку, а соответственно, выше крутящий момент и скорость вращения вала двигателя.

ШИМ-пин - специализированный вывод микроконтроллера с аппаратной поддержкой таймеров-счетчиков, генерирующих ШИМ-сигнал.

Практическая часть

В данной программе реализовано управление движением машинки. Мы можем установить скорость вращения от -255 до 255, передаваемое в функцию analogwrite(). Значение 0 означает импульс нулевой ширины (двигатель остановлен), значение 255 - импульс максимальной ширины.

Для движения машинки задаем значение скорости в мониторе порта. При положительном значении машинка едет назад, при отрицательном значении-вперед.

Функция constrain() проверяет и возвращает число, так чтобы оно было в области допустимых значений, заданной параметрами.

constrain(x,a,b)

x-проверяемое значение, а-нижняя граница проверяемого диапазона, b-верхняя граница проверяемого диапазона.

Пример constrain(170,10,150) =150.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.15.1).

Изображение 1

Рисунок 3.15.1. Собранная модель машинки

Как правильно подключить к плате ДПТ:

  1. Необходимо найти силовые клеммы питания (рис. из макета платы) на плате.

  2. Красный кабель от мотора вставляется в клемму со знаком “+”, а черный к “-”.

  3. Затяните болты на клеммах для удержания кабеля.

Изображение 98

Рисунок 3.15.2. Внешний вид схемы сборки лабораторной работы 15

Рабочая программа на графическом языке программирования:

Изображение 99

Изображение 100

Рисунок 3.15.3. Программа лабораторной работы 15 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Объявляется целочисленная переменная MOT1_DIRECTION для хранения адреса клеммы (7), к которой подключен первый ДПТ. Данный пин управляет направлением вращения мотора;

  2. Объявляется целочисленная переменная MOT1_PWM для хранения адреса клеммы (9), к которой подключен первый ДПТ. Данный пин генерирует ШИМ-сигнал;

  3. Объявляется целочисленная переменная MOT2_DIRECTION для хранения адреса клеммы (8), к которой подключен второй ДПТ;

  4. Объявляется целочисленная переменная MOT2_PWM для хранения адреса клеммы (10), к которой подключен второй ДПТ;

  5. Инициализация Serial-порт на скорость 9600;

  6. Инициализируются пины MOT1_DIRECTION, MOT1_PWM, MOT2_DIRECTION, MOT2_PWM как выход (OUTPUT);

  7. На Serial-порт выводятся сообщения: «Введите скорость моторов», «Например: s100».

Блок "Метод setMotorSpeed" с атрибутом speed содержит последовательность действий:

  1. Входной параметр целочисленной переменной speed ограничивается диапазоном значений от -255 до 255;

  2. Цикл «если иначе» аналогичен «if else». Если скорость > 0, то на пин MOT1_DIRECTION подается питание (HIGH). Иначе питание не подается;

  3. Устанавливается скорость первого мотора через ШИМ. На пин MOT1_PWM подается модуль скорость (ABS), то есть величина всегда положительная;

  4. Определяется направление второго мотора. Если значение скорости > 0, то на пин MOT2_DIRECTION подается LOW, иначе HIGH. Направление второго мотора инвертировано относительно первого, когда моторы стоят зеркально, чтобы при одной и той же команде оба крутились вперед относительно машинки;

  5. Устанавливается скорость второго мотора через ШИМ, то есть аналогово, равная модулю переменной speed.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Проверяется, есть ли данные во входящем буфере Serial;

  2. Если данные есть, то считывается один первый символ;

  3. Проверяется, равен ли считанный символ букве s (переменная типа char). То есть команда должна начинаться с s, например s100, s-120, так реализуется защита от случайно введенных команд;

  4. Если символ ‘s’ получен, то считывается число после буквы и записывается в переменную check_speed;

  5. Вызывается метод setMotorSpeed с входной переменной check_speed;

  6. В Serial выводится сообщение: «Моторы установлены на:»

  7. В Serial выводится значение переменной check_speed.

Рабочая программа для Arduino IDE:

cpp
//Установка пинов для моторов
#define MOT1_DIRECTION 7 //устанавливаем 7 пин вращения первого ДПТ
#define MOT1_PWM 9 //устанавливаем 9 ШИМ-ПИН скорости первого ДПТ
#define MOT2_DIRECTION 8 //устанавливаем 8 пин вращения второго ДПТ
#define MOT2_PWM 10 //устанавливаем 10 ШИМ-ПИН скорости второго ДПТ
void setup(){
Serial.begin(9600); //инициализация последовательного порта
//Настройки пинов моторов как выходы
pinMode(MOT1_DIRECTION, OUTPUT);
pinMode(MOT1_PWM, OUTPUT);
pinMode(MOT2_DIRECTION, OUTPUT);
pinMode(MOT2_PWM, OUTPUT);
Serial.println("Введите скорость моторов "); //выводим в монитор порта сообщение
Serial.println("Например: s100. "); //вывод в монитор порта сообщения
}
void loop(){
if (Serial.available()>0){ //проверка данных в мониторе порта
if (Serial.read()=='s'){//выполнение программы только в случае корректного ввода данных с s
int check_speed=Serial.parseInt();//чтение скорости из монитора порта

setMotorSpeed(check_speed); //установка скорости мотора с монитора порта

cpp
Serial.print("Моторы установлены на:"); //вывод сообщения в монитор порта
Serial.print(check_speed); //вывод в монитор порта скорости мотора
}
while (Serial.available()>0) Serial.read();//очистили буфер от мусора
}
}
//функция установки скорости и направления движения моторов
void setMotorSpeed(int speed){
speed = constrain(speed, -255, 255);//ограничение скорости в диапазоне -255...255
if (speed>0){ //если скорость больше 0
digitalWrite(MOT1_DIRECTION, HIGH);//вперед ДПТ 1 при положительной скорости
}
else {
digitalWrite(MOT1_DIRECTION, LOW);//назад ДПТ 1 при отрицательной скорости
speed=speed;
}
analogWrite(MOT1_PWM, abs(speed)); //установка ШИМ(0-255) ДПТ 1 = модуль скорости
if (speed>0){ //если скорость больше 0
digitalWrite(MOT2_DIRECTION, LOW); //назад ДПТ 2 при положительной скорости

}else{

cpp
digitalWrite(MOT2_DIRECTION, HIGH); //вперед ДПТ 2 при отрицательной скорости
}
analogWrite(MOT2_PWM, abs(speed)); //установка ШИМ (0-255) ДПТ 2 = модуль скорости
}

3.16 Лабораторная работа 16. Езда по квадрату

Цель работы:

Продолжить изучать работу ДПТ, а также применить полученные знания и навыки в создании программы для движения машины по квадрату.

Оборудование: микроконтроллер, машинка в собранном виде и ДПТ (2 шт.).

Теоретическая часть:

В предыдущей лабораторной работе 15 управление скоростью и направлением задавалось вручную через монитор порта. Для создания автономного движения (например, по фигуре) программа должна самостоятельно формировать последовательность команд для моторов по заданному алгоритму.

Для компактного поворота необходимо, чтобы колеса с разных сторон вращались в противоположные стороны. В этом случае машинка вращается вокруг своей центральной оси, что позволяет точно повернуть на заданный угол на минимальной площади.

Практическая часть:

Движение по квадрату осуществляется с помощью delay.

Алгоритм:

Цикл (4 раза): Программа повторяет основную последовательность ровно 4 раза (по числу сторон квадрата).

Прямо + Поворот: Каждое повторение цикла состоит из:

Движения прямо вперед в течение фиксированного времени (MOVE_TIME).

Поворота направо на месте в течение фиксированного времени (TURN_TIME).

  1. Пауза: после завершения 4 циклов машинка останавливается и стоит 3 секунды перед новым запуском.

Управление моторами:

  1. moveForward(speed) – Движение прямо:

Оба мотора (MOT1 и MOT2) получают сигнал LOW на пины направления (DIRECTION), что означает вращение вперед.

На оба пина ШИМ (PWM) подается одинаковое значение speed (через analogWrite).

Результат: Оба колеса крутятся вперед с одинаковой скоростью → машинка движется прямо.

  1. turnRight(speed) - Поворот Направо:

Левый мотор (MOT1) получает сигнал LOW на направление (вращается вперед) и значение speed на ШИМ.

Правый мотор (MOT2) получает сигнал HIGH на направление (вращается назад) и значение speed на ШИМ.

Результат: Левое колесо тянет вперед, правое толкает назад → машинка резко поворачивается направо на месте (вращаясь вокруг своей оси).

  1. stopMotors() - Остановка:

На оба пина ШИМ (PWM) подается значение 0 → моторы останавливаются.

Используемые константы (подобраны экспериментально):

MOVE_SPEED 30: Значение ШИМ (0-255) для скорости движения прямо.

TURN_SPEED 50: Значение ШИМ (0-255) для скорости поворота.

MOVE_TIME 900: Время (миллисекунды) движения прямо для проезда одной стороны квадрата.

TURN_TIME 600: Время (миллисекунды) поворота направо для разворота на 90 градусов.

Почему получается квадрат:

  1. Прямой участок: Команда moveForward(MOVE_SPEED) и задержка delay(MOVE_TIME) гарантированно перемещают машинку вперед на заданное расстояние (длину стороны квадрата).

  2. Поворот на 90°: Команда turnRight(TURN_SPEED) и задержка delay(TURN_TIME) обеспечивают точный поворот машинки направо на 90 градусов на месте, подготавливая ее к движению по следующей стороне.

  3. Четыре повторения: Последовательность "прямо → поворот направо" выполняется 4 раза подряд, формируя замкнутую траекторию квадрата.

  4. Пауза: после четвертого поворота и остановки (stopMotors()) пауза delay(3000) дает время перед началом следующего квадрата.

Ключевые особенности реализации:

Поворот на месте: Поворот реализован через вращение моторов в противоположные стороны (LOW для левого вперед, HIGH для правого назад), что обеспечивает разворот на месте вокруг центра машинки.

Управление временем: Форма и размер квадрата полностью определяются временными интервалами MOVE_TIME (длина стороны) и TURN_TIME (угол поворота).

Фиксированные параметры: Скорости (MOVE_SPEED, TURN_SPEED) и времена (MOVE_TIME, TURN_TIME) заданы константами и не меняются в процессе выполнения. Точность квадрата зависит от правильности их подбора.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.16.1).

Изображение 98

Рисунок 3.16.1. Внешний вид схемы сборки лабораторной работы 16

Рабочая программа на графическом языке программирования:

Изображение 101

Изображение 102

Рисунок 3.16.2. Программа лабораторной работы 16 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Объявляется целочисленная переменная MOT1_DIRECTION для хранения адреса клеммы (7), к которой подключен первый ДПТ. Данный пин управляет направлением вращения мотора;

  2. Объявляется целочисленная переменная MOT1_PWM для хранения адреса клеммы (9), к которой подключен первый ДПТ. Данный пин генерирует ШИМ-сигнал;

  3. Объявляется целочисленная переменная MOT2_DIRECTION для хранения адреса клеммы (8), к которой подключен второй ДПТ;

  4. Объявляется целочисленная переменная MOT2_PWM для хранения адреса клеммы (10), к которой подключен второй ДПТ;

  5. Объявляется целочисленная переменная MOVE_SPEED, равная 30 для хранения скорости движения вперед;

  6. Объявляется целочисленная переменная MOVE_SPEED, равная 50 для хранения скорости поворота;

  7. Объявляется целочисленная переменная MOVE_TIME, равная 900 для хранения времени движения вперед (в мс);

  8. Объявляется целочисленная переменная TURN_TIME, равная 600 для хранения времени поворота (в мс);

  9. Инициализируются пины MOT1_DIRECTION, MOT1_PWM, MOT2_DIRECTION, MOT2_PWM как выход (OUTPUT);

Блок "Метод moveForward" с атрибутом speed содержит последовательность действий:

  1. Значение переменной скорости speed ограничивается диапазоном от 0 до 255;

  2. На пин MOT1_DIRECTION подается LOW (направление вперед) и на пин MOT1_PWM подается ШИМ сигнал скорости мотора speed;

  3. На пин MOT2_DIRECTION подается LOW (направление вперед) и на пин MOT2_PWM подается ШИМ сигнал скорости мотора speed.

Блок "Метод turnRight" с атрибутом speed1 содержит последовательность действий:

  1. Значение переменной скорости speed1 ограничивается диапазоном от 0 до 255;

  2. На пин MOT1_DIRECTION подается LOW (направление вперед) и на пин MOT1_PWM подается ШИМ сигнал скорости мотора speed1;

  3. На пин MOT2_DIRECTION подается HIGH (направление назад) и на пин MOT2_PWM подается ШИМ сигнал скорости мотора speed1;

Блок "Метод stopMotors" содержит последовательность действий:

  1. На MOT1_PWM подается 0;

  2. На MOT2_PWM подается 0.

Оба мотора перестают вращаться и останавливаются.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Запускается цикл for, который 4 раза (цикл по переменной i от 0 до 4 с шагом 1);

  2. Внутри каждого повторения цикла:

  3. Вызывается метод moveForward. Машинка начинает ехать вперед с заданной скоростью;

  4. Выполняется задержка со значением MOVE_TIME, чтобы робот ехал вперед заданное время;

  5. Вызывается метод turnRight. Машинка выполняет поворот направо с заданной скоростью;

  6. Выполняется задержка со значение переменной TURN_TIME, чтобы поворот длился заданное время;

  7. После того, как цикл на 4 повторения завершился: вызывается метод stopMotors для остановки мотора.

  8. Выполняется задержка в 3000 мс (3 с).

Рабочая программа для Arduino IDE:

cpp
#define MOT1_DIRECTION 7 //устанавливаем 7 пин вращения первого ДПТ
#define MOT1_PWM 9 //устанавливаем 9 ШИМ-ПИН скорости первого ДПТ
#define MOT2_DIRECTION 8 //устанавливаем 8 пин вращения второго ДПТ
#define MOT2_PWM 10 //устанавливаем 10 ШИМ-ПИН скорости второго ДПТ
#define MOVE_SPEED 30 //устанавливаем скорость движения (0-255)
#define TURN_SPEED 50 //устанавливаем скорость поворота (0-255)
#define MOVE_TIME 900 //время прямолинейного движения в мс
#define TURN_TIME 600 //время поворота в мс
void setup() {
//настройки пинов моторов как выходы
pinMode(MOT1_DIRECTION, OUTPUT);
pinMode(MOT1_PWM, OUTPUT);
pinMode(MOT2_DIRECTION, OUTPUT);
pinMode(MOT2_PWM, OUTPUT);
}
void loop() {
for (int i = 0; i < 4; i++) { //основной цикл выполняется 4 раза (4 стороны квадрата)

moveForward(MOVE_SPEED); //движение вперед

delay(MOVE_TIME); //задержка для прохождения прямой

turnRight(TURN_SPEED); //поворот направо

cpp
delay(TURN_TIME); //задержка для поворота
}

stopMotors(); //полная остановка моторов после прохождения квадрата

cpp
delay(3000); // пауза перед новым квадратом в 3 с
}
void moveForward(int speed) { //функция движения вперед
speed = constrain(speed, 0, 255); //ограничение скорости в диапазоне от 0 до 255
//управление первым мотором
digitalWrite(MOT1_DIRECTION, LOW); //направление вперед 1 мотора
analogWrite(MOT1_PWM, speed); //установка скорости 1 мотора
//управление вторым мотором
digitalWrite(MOT2_DIRECTION, LOW); //направление вперед 2 мотора
analogWrite(MOT2_PWM, speed); //установка скорости 2 мотора
}
void turnRight(int speed) { //функция поворота направо
speed = constrain(speed, 0, 255);
digitalWrite(MOT1_DIRECTION, LOW); //направление вперед 1 мотора
analogWrite(MOT1_PWM, speed);
digitalWrite(MOT2_DIRECTION, HIGH); //направление назад 2 мотора
analogWrite(MOT2_PWM, speed);
}
void stopMotors() {
analogWrite(MOT1_PWM, 0); //отключение питания 1 мотора
analogWrite(MOT2_PWM, 0); //отключение питания 2 мотора
}

3.17 Лабораторная работа 17. Энкодер

Цель работы:

Изучить устройство и принцип работы энкодера как датчика скорости вращения.

Оборудование: микроконтроллер, машинка в собранном виде, ДПТ (2 шт), энкодер (2 шт.).

Теоретическая часть:

Для получения информации о скорости вращения выходного вала двигателя постоянного тока, на котором крепится колесо используется электронный модуль, называемый энкодером.

Скорость вращения (обороты в минуту, RPM) рассчитывается по формуле:

Изображение 103

В данном случае время измерения t = 1 секунда, а количество отверстий = 20, тогда формула будет выглядеть так:

Изображение 104

(*)

Практическая часть:

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

Данная программа не управляет ДПТ, а только считывает импульсы с датчиков и переводит ее в скорость (об/мин). Вручную крутите колеса на машинке и наблюдайте за изменением скорости в мониторе порта.

Для программной реализации опроса датчика скорости понадобится использовать функции для работы с прерываниями, входящие в состав стандартной среды разработки Arduino IDE.

Познакомимся с квалификатором volatile. Он используется перед типом переменной чтобы изменить способ интерпретации и доступа к переменной компилятором и в дальнейшем программой. Квалификатор указывает компилятору загрузить переменную из ОЗУ, а не из временной ячейки памяти. Переменная должна быть объявлена volatile, когда её значение может быть изменено чем-либо за пределами того участка программы, где она объявлена, например, параллельно выполняющимся процессом. В Arduino единственным местом, где это используется, является участок программы, вызванный программой обработки прерываний.

Прерывания обычно используются для задач, которые должны быть выполнены автоматически при наступление какого-либо внешнего воздействия. Если мы хотим считывать значения энкодера без использования прерываний, то написать программу, которая бы не пропускала ни одного импульса, практически невозможно, такая программа должна будет практически все время считывать значения с датчика, чтобы не пропустить импульс. В таком случае на другие операции не останется процессорного времени. Это же относится и к другим датчикам, выдающим короткий импульс. Прерывания гарантируют точный подсчет.

Возможны следующие функции:

  • LOW вызывает прерывание, когда на порту LOW

  • CHANGE прерывание вызывается при смене значения на порту, с LOW на HIGH и наоборот

  • RISING прерывание вызывается только при смене значения на порту с LOW на HIGH

  • FALLING прерывание вызывается только при смене значения на порту с HIGH на LOW

Функция работы с прерываниями attachInterrupt(). Она задает функцию обработки внешнего прерывания (функцию, которая будет вызвана по внешнему прерыванию). Если до этого уже была задана другая функция, то результатом работы данной функции будет назначена новая.

attachInterrupt(interrupt,function,mode)

interrupt - номер прерывания (int)

function - функция, вызываемая прерыванием. Функция должна быть без параметров и не возвращать значений.

mode задает режим обработки прерывания (LOW, FAILLING и тд.)

Функция detachInterrupt() выключает обработку внешнего прерывания.

detachInterrupt(interrupt). interrupt - номер прерывания (0 или 1).

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.17.1). ДПТ подключаются аналогично предыдущим работам, энкодеры подключаются к пинам 2 и 3 микроконтроллера.

Изображение 105

Рисунок 3.17.1. Внешний вид схемы сборки лабораторной работы 17

Рабочая программа на графическом языке программирования:

Изображение 106

Изображение 107

Изображение 108

Рисунок 3.17.2. Программа лабораторной работы 17 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Объявляется целочисленная переменная HOLES, равная 20, которая хранит количество отверстий в диске энкодера. Это значение используется при вычислении скорости вращения колеса;

  2. Инициализируется последовательный порт Serial со скоростью передачи данных 9600 бод для вывода результатов измерений на компьютер;

  3. Объявляется переменная leftCount с атрибутом volatile для подсчета импульсов от левого энкодера. Атрибут volatile используется, так как значение переменной изменяется внутри обработчика прерывания;

  4. Объявляется переменная rightCount с атрибутом volatile для подсчета импульсов от правого энкодера;

  5. Пин 2, к которому подключен энкодер левого колеса, настраивается как вход (INPUT);

  6. Пин 3, к которому подключен энкодер правого колеса, настраивается как вход (INPUT);

  7. Для пина 2 настраивается аппаратное прерывание: при появлении сигнала как FALLING вызывается функция leftPulse, которая увеличивает счетчик импульсов левого колеса;

  8. Для пина 3 настраивается аппаратное прерывание: при появлении сигнала как FALLING вызывается функция rightPulse, которая увеличивает счетчик импульсов правого колеса;

  9. В последовательный порт выводится сообщение «Начало измерения скорости», сигнализирующее о запуске программы.

Блок "Метод leftPulse" осуществляет подсчет количества импульсов, соответвующих вращению левого колеса. Значение переменной leftCount увеличивается на 1при каждом поступлении импульса от левого колеса.

Блок "Метод rightPulse" осуществляет подсчет количества импульсов, соответвующих вращению правого колеса. Значение переменной rightCount увеличивается на 1при каждом поступлении импульса от правого колеса.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Выполняется задержка в 1000 мс (1 с), в течение которой продолжается подсчет импульсов от энкодеров;

  2. Объявляется переменная типа double LeftRPM и вычисляется скорость вращения левого колеса по формуле (*);

  3. Аналогично вычисляется скорость вращения правого колеса RightRPM;

  4. Значения счетчиков leftCount и rightCount обновляются для начала нового измерительного интервала;

  5. В последовательный порт выводится сообщение с результатами измерений: скорость вращения левого и правого колес в оборотах в минуту;

  6. Снова активируются прерывания для пинов 2 и 3, чтобы продолжить подсчет импульсов от энкодеров в следующем цикле измерения.

Рабочая программа для Arduino IDE:

cpp
// пины для датчиков скорости
#define LEFT_ENCODER 2 //Пин 2 для левого энкодера
#define RIGHT_ENCODER 3 //Пин 3 для правого энкодера
const int HOLES = 20; // Количество отверстий в диске энкодера
// счетчики импульсов (накапливают количество импульсов от энкодера)
volatile long leftCount = 0; //считаем импульсы с левого колеса
volatile long rightCount = 0; //считаем импульсы с правого колеса
void setup() {
Serial.begin(9600); //инициализация последовательного порта
pinMode(LEFT_ENCODER, INPUT); //пины датчиков работают на вход
pinMode(RIGHT_ENCODER, INPUT);
attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER), leftPulse, FALLING); //прерывание для левого энкодера
attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER), rightPulse, FALLING); //прерывание для правого энкодера
Serial.println("Начало измерения скорости");
}
void loop() {
delay(1000);

detachInterrupt(0); //отключение прерывания

cpp
// рассчитываем скорость (обороты в минуту)
float leftRPM = (leftCount * 60.0) / HOLES; //(импульсы/сек*60)/отверстия
float rightRPM = (rightCount * 60.0) / HOLES;
// сбрасываем счетчики
leftCount = 0;
rightCount = 0;
// выводим результаты
Serial.print("Левое колесо: ");
Serial.print( leftRPM);
Serial.print(" об/мин | Правое колесо: ");
Serial.print(rightRPM);
Serial.println(" об/мин");
attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER), leftPulse, FALLING); //прерывание для левого энкодера
attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER), rightPulse, FALLING); //прерывание для правого энкодера
}
// функции обработки прерываний
void leftPulse() {
leftCount++;
}
void rightPulse() {
rightCount++;
}

3.18 Лабораторная работа 18. Измерение скорости вращения двигателей с использованием энкодеров

Цель работы:

Объединить навыки управления двигателями и измерения скорости вращения с помощью энкодеров в одной программе.

Оборудование: микроконтроллер, машинка в собранном виде, ДПТ (2 шт.), энкодер (2 шт.).

Практическая часть:

В данной лабораторной работе необходимо разработать программу управления движением машинки с использованием двигателей постоянного тока и энкодеров для обратной связи. Программа позволяет измерять скорость вращения колес в реальном времени с помощью датчиков скорости и управлять направлением и скоростью движения через последовательный порт.

Изучим новый тип данных unsignet (беззнаковые).

unsigned int хранит только положительные числа (от 0 до 65535) и позволяет считать в 2 раза больше значений по сравнению с обычным int. В нашей программе важно использовать данный тип, так как счетчики импульсов не могут быть отрицательными.

Функция millis() возвращает количество миллисекунд с момента запуска программы.

В монитор порта будем выводить измеренную энкодерами скорость (об/мин) RPM (REVOLUTIONS Per Minute).

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.18.1).

Изображение 105

Рисунок 3.18.1. Внешний вид схемы сборки лабораторной работы 18

Рабочая программа на графическом языке программирования:

Изображение 109

Изображение 110

Рисунок 3.18.2. Программа лабораторной работы 18 на графическом языке программирования

Блок "Метод firstCounter" позволяет подсчитывать количество импульсов, соответствующих вращению первого колеса. Значение переменной firstPulses увеличивается на 1 при каждом поступлении импульсов от первого энкодера.

Блок "Метод secondCounter" осуществляет подсчет импульсов второго колеса. Значение переменной secondPulses увеличивается на 1 при каждом поступлении импульсов от второго энкодера.

Блок "Выполнить разово» содержит последовательность действий:

  1. Объявляется целочисленная переменная LEFT_ENCODER, равная 2, которая хранит номер пина, к которому подключен первый энкодер (датчик скорости левого колеса);

  2. Объявляется целочисленная переменная RIGHT_ENCODER, равная 3, которая хранит номер пина, к которому подключен второй энкодер (датчик скорости правого колеса);

  3. Объявляется целочисленная переменная HOLES, равная 20, которая хранит количество отверстий в диске энкодера и используется при вычислении скорости вращения колес;

  4. Объявляется целочисленная переменная MOT1_DIRECTION, равная 7, для хранения номера пина управления направлением вращения первого ДПТ;

  5. Объявляется целочисленная переменная MOT1_SPEED, равная 9, для хранения номера ШИМ-пина управления скоростью первого двигателя;

  6. Объявляется целочисленная переменная MOT2_DIRECTION, равная 8, для хранения номера пина управления направлением вращения второго ДПТ;

  7. Объявляется целочисленная переменная MOT2_SPEED, равная 10, для хранения номера ШИМ-пина управления скоростью второго двигателя;

  8. Объявляется целочисленная переменная firstRPM и инициализируется значением 0 для хранения вычисленной скорости вращения первого двигателя;

  9. Объявляется целочисленная переменная secondRPM и инициализируется значением 0 для хранения вычисленной скорости вращения второго двигателя;

  10. Объявляется переменная firstPulses с атрибутом volatile для подсчета импульсов от первого энкодера;

  11. Объявляется переменная secondPulses с атрибутом volatile для подсчета импульсов от второго энкодера;

  12. Объявляется переменная типа lond timeOld, которая хранит момент времени последнего измерения скорости;

  13. Инициализируется последовательный порт Serial со скоростью 9600 бод;

  14. Пин LEFT_ENCODER настраивается как вход (INPUT) для подключения первого энкодера;

  15. Для пина LEFT_ENCODER настраивается аппаратное прерывание: при появлении сигнала как FALLING вызывается функция firstCounter, увеличивающая счетчик импульсов;

  16. Пин RIGHT_ENCODER настраивается как вход (INPUT) для подключения второго энкодера;

  17. Для пина RIGHT _ENCODER настраивается аппаратное прерывание: при появлении сигнала как FALLING вызывается функция secondCounter, увеличивающая счетчик импульсов;

  18. Пины MOT1_DIRECTION, MOT1_SPEED, MOT2_DIRECTION и MOT2_SPEED настраиваются как выходы (OUTPUT) для управления двигателями;

  19. Переменной timeOld присваивается текущее значение времени, полученное функцией millis(), для начала отсчета интервала измерения скорости.

Блок "Метод calculateRPM" содержит последовательность действий:

  1. Объявляется переменная типа long currentTime, определяющая текущее время работы программы с помощью функции millis();

  2. Объявляется переменная типа long delta. Вычисляется интервал времени delta между текущим измерением currentTime и предыдущим моментом timeOld;

  3. Если интервал времени delta больше или равен 1000 мс (1 с), выполняется расчет скорости вращения колес по формуле (*) в мс. Результаты расчетов записываются в целочисленные переменные firstRPM, secondRPM;

  4. Переменной timeOld присваивается текущее время измерения;

  5. Счетчики импульсов firstPulses и secondPulses обнуляются для начала нового измерения;

  6. Результаты вычисления скорости вращения обоих колес выводятся в последовательный порт.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Вызывается метод calculateRPM, который выполняет расчет текущей скорости вращения колес;

  2. Проверяется наличие данных, поступивших из последовательного порта;

  3. Если данные поступили, считывается символ из последовательного порта;

  4. Если получена команда «s», программа переходит к установке скорости двигателей;

  5. Из Serial считывается числовое значение переменной speed, задающее скорость вращения ДПТ;

  6. Проверяется, находится ли значение скорости от -255 до 255;

  7. Если значение speed положительное: на пин MOT1_DIRECTION подается сигнал LOW, на пин MOT2_DIRECTION подается сигнал HIGH. Это задает направление вращения ДПТ для движения машинки вперед;

  8. Если значение speed отрицательное: на пин MOT1_DIRECTION подается сигнал HIGH, на пин MOT2_DIRECTION подается сигнал LOW. Это задает обратное направление вращения ДПТ для движения машинки назад. При этом значение скорости берется абсолютное (speed= -speed);

  9. Если значение speed равно 0, то оба двигателя останавливаются% на пины MOT1_DIRECTION и MOT2_DIRECTION подаются сигналы LOW;

  10. После определения направления вращения на пины MOT1_SPEED, MOT2_SPEED подается ШИМ-сигнал со значением speed, который задает скорость вращения двигателей;

  11. В последовательный порт выводится сообщение «Установлена скорость» с заданным значением скорости speed;

  12. После обработки команды пункта 2 выполняется очистка буфера последовательного порта, чтобы удалить оставшиеся данные.

Рабочая программа для Arduino IDE:

cpp
// пины датчиков скорости
#define LEFT_ENCODER 2 // пин 2 для первого энкодера
#define RIGHT_ENCODER 3 // пин 3 для второго энкодера
#define HOLES 20.0 // количество отверстий на диске энкодера
// пины моторов
#define MOT1_DIRECTION 7 //устанавливаем 7 пин вращения 1 ДПТ
#define MOT1_SPEED 9 //устанавливаем 9 ШИМ-ПИН скорости 1 ДПТ
#define MOT2_DIRECTION 8 //устанавливаем 8 пин вращения 2 ДПТ
#define MOT2_SPEED 10 //устанавливаем 10 ШИМ-ПИН скорости 2 ДПТ
// счетчики оборотов
unsigned int firstRpm = 0; //RPM для первого мотора
unsigned int secondRpm = 0; //RPM для второго мотора
volatile unsigned int firstPulses = 0; //счетчик импульсов 1 мотора
volatile unsigned int secondPulses = 0; //счетчик импульсов 2 мотора
unsigned long timeOld = 0; //переменная времени последнего измерения
// обработчики прерываний
void firstCounter() {
firstPulses++; //увеличиваем первый счетчик при каждом импульсе
}
void secondCounter() {
secondPulses++; //увеличиваем второй счетчик при каждом импульсе
}
// функция расчета оборотов в минуту
void calculateRPM() {
unsigned long currentTime = millis(); //текущее время
unsigned long delta = currentTime - timeOld; //вычисляем время с прошлого измерения
if (delta >= 1000) { //если прошла 1 секунда
firstRpm = (firstPulses * 60000.0) / (HOLES * delta); //формула скорости вращения: (импульсы*6000(60*1000 так как измеряем в мс))/(количество_отверстий*время_измерения)
secondRpm = (secondPulses * 60000.0) / (HOLES * delta);
timeOld = currentTime; //обновляем время
firstPulses = 0; //сбрасываем счетчики
secondPulses = 0;
//вывод результатов в порт монитора
Serial.print("RPM >> Левый: ");
Serial.print(firstRpm);
Serial.print(" | Правый: ");
Serial.println(secondRpm);
}
}
void setup() {
Serial.begin(9600);
// настройка энкодеров
pinMode(LEFT_ENCODER, INPUT);
attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER), firstCounter, FALLING);
pinMode(RIGHT_ENCODER, INPUT);
attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER), secondCounter, FALLING);
// настройка пинов моторов
pinMode(MOT1_DIRECTION, OUTPUT);
pinMode(MOT1_SPEED, OUTPUT);
pinMode(MOT2_DIRECTION, OUTPUT);
pinMode(MOT2_SPEED, OUTPUT);
timeOld = millis(); // Инициализация времени
}
void loop() {

calculateRPM(); // вызываем расчет скорости

cpp
// обработка команд с компьютера
if (Serial.available() > 0) {
if (Serial.read() == 's') { //выполнение если получена команда "s"
int speed = Serial.parseInt(); //читаем число
// проверка диапазона
if (speed >= -255 && speed <= 255) {
// установка направления
if (speed > 0) {
digitalWrite(MOT1_DIRECTION, LOW);
digitalWrite(MOT2_DIRECTION, HIGH);
} else if (speed < 0) {
digitalWrite(MOT1_DIRECTION, HIGH);
digitalWrite(MOT2_DIRECTION, LOW);
speed = -speed; // Берем абсолютное значение
} else {
// остановка
digitalWrite(MOT1_DIRECTION, LOW);
digitalWrite(MOT2_DIRECTION, LOW);
}
// установка скорости
analogWrite(MOT1_SPEED, speed);
analogWrite(MOT2_SPEED, speed);
Serial.print("Установлена скорость: ");
Serial.println(speed);
}
}
// очистка буфера
while (Serial.available() > 0) Serial.read();
}
}

Внимание! Показания энкодеров при одной и той же скорости не будут совпадать, но будут примерно равны. Это обусловлено особенностями реальных электромеханических систем.

3.19 Лабораторная работа 19. ПИД регулятор

Цель работы:

Изучить принцип действия ПИД регулятора и реализовать систему управления скоростью двигателей с обратной связью по энкодерам.

Оборудование: микроконтроллер, машинка в собранном виде, ДПТ (2 шт.), энкодер (2 шт.).

Теоретическая часть:

ПИД регулятор - один из самых распространенных универсальных регуляторов автоматического управления.

Прежде чем переходить непосредственно к изучению ПИД регулятора, важно изучить несколько базовых понятий, из которых состоит автоматическая система.

Объект управления — это устройство, которым мы управляем, например мотор. Для этого у нас есть управляющее устройство (например драйвер мотора). Управляющее устройство получает от регулятора управляющий сигнал, то есть конкретное число. В объекте управления стоит датчик, с которого регулятор получает величину, которой он управляет.

В системе есть обратная связь, если регулятор использует информацию о состоянии объекта для выработки управления. Системы с обратной связью называют замкнутыми, поскольку информация передается по замкнутому контуру. Регулятор получает уставку, то есть число, к которому он должен привести текущее значение с датчика. Уставка может быть фиксированной или задаваться каким угодно образом: ползунком, энкодером, кнопками - для регулятора это неважно, для него это будет просто число.

Задача регулятора состоит в том, чтобы сравнивать текущее значение с заданным и выдавать управляющий сигнал через равные промежутки времени, то есть с равным периодом или частотой. Ошибка регулирования - разность заданного значения и обратной связи, на которую ориентируется регулятор.

Изображение 111

Рисунок 3.19.1. Схема системы автоматического управления с обратной связью

ПИД регулятор состоит из трёх составляющих: пропорциональной P (proportional), интегральной I (integral) и дифференцирующей D(derivative) и формируется как сумма трёх значений, умноженная каждая на свой коэффициент. Эта сумма после вычислений становится управляющим сигналом, который подается на управляющее устройство.

Синтаксис:

output = P*Kp + I*Ki + D*Kd; (**)

Где Kp, Ki и Kd - коэффициенты, которые нужно настроить для работы ПИД регулятора. Значения их могут быть самые разные, от 0.001 до десятков, это зависит от конкретной системы и её диапазона значений входа и выхода.

Любой коэффициент может быть равен нулю, в таком случае обнуляется все его компонента. То есть регулятор можно превратить в П, ПИ, ПД и прочие сочетания. Разные системы требуют разного подхода, поэтому ПИД регулятор является универсальным.

Пропорциональная составляющая (Р) представляет собой текущее значение ошибки.

Чем больше ошибка, тем больше будет управляющий сигнал и тем быстрее система будет приводить управляемую величину к заданному значению. Коэффициент Kp играет роль усиления ошибки. P составляющая исправляет ошибку в текущий момент времени.

Интегральная составляющая (I) суммирует в себя ошибку, умноженную на время, прошедшее с предыдущего расчета. В интегральной составляющей копится ошибка, что позволяет с течением времени полностью её устранить, то есть привести систему ровно к заданному значению с минимальной ошибкой. I составляющая исправляет прошлые, накопившиеся ошибки.

Дифференциальная составляющая (D) представляет собой разность текущей и предыдущей ошибок, поделенную на производную от ошибки по времени. D составляющая реагирует на изменение сигнала с датчика и чем сильнее происходит это изменение, тем больше значение прибавляется к общей сумме. Дифференциальная составляющая исправляет возможные будущие ошибки, анализируя скорость изменения входного сигнала.

ПИД регулятор можно настраивать в программе вручную, но также существуют готовые библиотеки, которые можно найти на Github.

Для настройки регулятора необходимо варьировать коэффициенты:

  • При увеличении Kp увеличивается скорость выхода на установленное значение, увеличивается управляющий сигнал. Однако при дальнейшем увеличении коэффициента от установки П составляющей реальная система теряет устойчивость.

  • При увеличении Ki растёт скорость компенсации накопившейся ошибки, что позволяет вывести систему точно к заданному значению с течением времени. Если система медленная, а Ki слишком большой - интегральная сумма вырастет и произойдет перерегулирование, которое может иметь характер незатухающих коллебаний.

  • При увеличении Kd растёт стабильность системы, данный коэффициент не дает системе меняться слишком быстро.

Практическая часть:

В разработанной системе управления реализованы два отдельных независимых ПИД-регулятора для левого и правого двигателей. Эта архитектура обусловлена фундаментальными особенностями реальных электромеханических систем. Даже при использовании двигателей одной модели и производителя неизбежно возникают различия в их характеристиках, обусловленные:

  • технологическими допусками при производстве (разброс параметров обмоток, магнитов, подшипников)

  • различиями в механической нагрузке на оси (неравномерное распределение массы)

  • особенности передачи момента (люфты в редукторах, разное сцепление колес с поверхностью)

  • электрические параметры цепи (сопротивление проводов, контактных групп)

Применение двух независимых регуляторов позволяет обеспечить синхронное вращение колес при прямолинейном движении, так как одиночный регулятор не сможет учесть асимметрию системы.

Каждая система уникальна и требуется самостоятельный подбор коэффициентов для устройства.

КоэффициентРоль в системеДиапазон комплектных моторовМетод настройки
Kpбыстрота реакции0,05-1,0увеличивать до появления колебаний, затем уменьшить на 30%
Kiустранение статической ошибки0,01-0,5начинать с 0, добавлять после настройки Kp
Kdподавление колебаний0,001-0,1Добавляется последним для плавности работы

Пример этапов подбора коэффициентов:

  1. Установить все коэффициенты = 0

  2. Увеличивать Kp до достижения целевой скорости

  3. Добавить Ki для устранения остаточной ошибки скорости

  4. Ввести Kd для уменьшения колебаний

Коэффициенты для левого и правого двигателей могут отличаться на 15-50%.

Также в программе используется преобразование целевой скорости: targetRPM = map(abs(speed), 0, 255, 0, 300);

Функцию map мы уже изучали ранее. map(abs(speed), 0, 255, 0, 300) преобразует абсолютное значение скорости, полученной от пользователя в диапазоне от 0 до 255 в значение RPM в диапазоне от 0 до 300 об/мин. Это необходимо, так как ПИД-регулятор работает с реальной физической величиной - обороты в минуту RPM, а команда от пользователя приходит как ШИМ сигнал (RWM). Число 300 — это максимальная ожидаемая скорость вращения мотора при RWM = 255.

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.19.2).

Изображение 105

Рисунок 3.19.2. Внешний вид схемы сборки лабораторной работы 19

Рабочая программа на графическом языке программирования:

Изображение 112

Изображение 113

Изображение 114

Изображение 115

Изображение 116

Изображение 117

Рисунок 3.19.3. Программа лабораторной работы 19 на графическом языке программирования

Блок "Метод firstCounter" позволяет подсчитывать количество импульсов, соответствующих вращению первого колеса. Значение переменной firstPulses увеличивается на 1 при каждом поступлении импульсов от первого энкодера.

Блок "Метод secondCounter" осуществляет подсчет импульсов второго колеса. Значение переменной secondPulses увеличивается на 1 при каждом поступлении импульсов от второго энкодера.

Блок "Метод calculateRPM" содержит последовательность действий:

  1. Объявляется переменная типа long currentTime, определяющая текущее время работы программы с помощью функции millis();

  2. Объявляется переменная типа long delta. Вычисляется интервал времени delta между текущим измерением currentTime и предыдущим моментом timeOld;

  3. Если интервал времени delta больше или равен 100 мс, выполняется расчет скорости вращения колес по формуле (*) в мс. Результаты расчетов записываются в целочисленные переменные firstRPM, secondRPM;

  4. Переменной timeOld присваивается текущее время измерения;

  5. Счетчики импульсов firstPulses и secondPulses обнуляются для начала нового измерения;

  6. Результаты вычисления скорости вращения обоих колес выводятся в последовательный порт.

Блок "Выполнить разово" содержит последовательность действий:

  1. Объявляется целочисленная переменная LEFT_ENCODER, равная 2, которая хранит номер пина, к которому подключен первый энкодер (датчик скорости левого колеса);

  2. Объявляется целочисленная переменная RIGHT_ENCODER, равная 3, которая хранит номер пина, к которому подключен второй энкодер (датчик скорости правого колеса);

  3. Объявляется целочисленная переменная HOLES, равная 20, которая хранит количество отверстий в диске энкодера и используется при вычислении скорости вращения колес;

  4. Объявляется целочисленная переменная MOT1_DIRECTION, равная 7, для хранения номера пина управления направлением вращения первого ДПТ;

  5. Объявляется целочисленная переменная MOT1_SPEED, равная 9, для хранения номера ШИМ-пина управления скоростью первого двигателя;

  6. Объявляется целочисленная переменная MOT2_DIRECTION, равная 8, для хранения номера пина управления направлением вращения второго ДПТ;

  7. Объявляется целочисленная переменная MOT2_SPEED, равная 10, для хранения номера ШИМ-пина управления скоростью второго двигателя;

  8. Объявляется переменная firstPulses с атрибутом volatile для подсчета импульсов от первого мотора;

  9. Объявляется переменная secondPulses с атрибутом volatile для подсчета импульсов от второго энкодера;

  10. Объявляется целочисленная переменная firstRPM и инициализируется значением 0 для хранения вычисленной скорости вращения первого двигателя;

  11. Объявляется целочисленная переменная secondRPM и инициализируется значением 0 для хранения вычисленной скорости вращения второго двигателя;

  12. Объявляется переменная типа lond timeOld, которая хранит момент времени последнего измерения скорости;

  13. Объявляется переменная targetRPM и инициализируется значением 0 для хранения целевой скорости двигателей;

  14. Объявляются переменные Kp1=0.5, Ki1=0.11, Kd1=0.03 для хранения значений коэффициентов ПИД-регулятора первого мотора;

  15. Объявляются переменные error1, integral1, prevError1 и инициализируются значением 0 для хранения рассчитанных значений составляющих ПИД-регулятора первого ДПТ;

  16. Объявляются переменные Kp2=0.2, Ki2=0.1, Kd2=0.02 для хранения значений коэффициентов ПИД-регулятора второго мотора;

  17. Объявляются переменные error2, integral2, prevError2 и инициализируются значением 0 для хранения рассчитанных значений составляющих ПИД-регулятора второго ДПТ;

  18. Объявляются целочисленные переменные pwmLeft, pwmRight и инициализируются значением 0 для хранения ШИМ значения моторов;

  19. Объявляется целочисленная переменная direction, равная 1, определяющая направление движения моторов (1-впред, -1-назад, 0-стоп);

  20. Инициализируется последовательный порт Serial со скоростью 9600 бод;

  21. Пин LEFT_ENCODER настраивается как вход (INPUT) для подключения первого энкодера;

  22. Для пина LEFT_ENCODER настраивается аппаратное прерывание: при появлении сигнала как FALLING вызывается функция firstCounter, увеличивающая счетчик импульсов;

  23. Пин RIGHT_ENCODER настраивается как вход (INPUT) для подключения второго энкодера;

  24. Для пина RIGHT _ENCODER настраивается аппаратное прерывание: при появлении сигнала как FALLING вызывается функция secondCounter, увеличивающая счетчик импульсов;

  25. Пины MOT1_DIRECTION, MOT1_SPEED, MOT2_DIRECTION и MOT2_SPEED настраиваются как выходы (OUTPUT) для управления двигателями;

  26. Переменной timeOld присваивается текущее значение времени, полученное функцией millis(), для начала отсчета интервала измерения скорости.

Блок "Метод updatePID" содержит последовательность действий:

  1. Для регулирования скорости левого мотора вычисляются: ошибка регулирования error1, интегральная составляющая integral1, дифференциальная составляющая derivative1;

  2. Рассчитывается выход ПИД-регулятора по формуле (**);

  3. Значение предыдущей ошибки обновляется;

  4. Выполняется ограничение управляющего сигнала в диапазоне 0-255 и полученное значение записывается в переменную pwmLeft;

  5. Тоже самое (пункты 1-4) выполняется для правого мотора;

  6. На ШИМ-пин MOT1_SPEED подается значение pwmLeft;

  7. На ШИМ-пин MOT2_SPEED подается значение pwmRight.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Вызывается метод calculateRPM, который рассчитывает текущие обороты моторов;

  2. Проверяется наличие входящих данных в последовательном порту Serial;

  3. Если данные поступили, выполняется проверка на получение символа команды ‘s’;

  4. Если команда верно получена, считывается числовое значение скорости из последовательного порта и записывается в переменную speed;

  5. Проверяется диапазон значения скорости от -255 до 255;

  6. Если значение speed>0, устанавливается направление движения вперед (переменной direction присваивается значение 1). Если значение speed<0, устанавливается направление движения назад (переменной direction присваивается значение -1). Если значение speed=0, устанавливается режим остановки моторов (переменной direction присваивается значение 0).

  7. Вычисляется целевая скорость вращения моторов targetRPM: абсолютное значение скорости, полученной от пользователя в диапазоне от 0 до 255 в значение RPM в диапазоне от 0 до 300 об/мин;

  8. Если движение вперед, то на пин MOT1_DIRECTION подается сигнал LOW, а на пин MOT2_DIRECTION подается сигнал HIGH;

    1. Если движение назад, то на пин MOT1_DIRECTION подается сигнал HIGH, а на пин MOT2_DIRECTION подается сигнал LOW;
  9. Если движение отсутствует, то на пины MOT1_DIRECTION и MOT2_DIRECTION подается сигнал LOW;

  10. В монитор порта выводится сообщение «Установлена целевая скорость:» и значение переменной targetRPM;

  11. Пока последовательный порт доступен выполняется очистка буфера от оставшихся данных;

  12. Проверяется условие, что направление движения не равно нулю и целевая скорость больше нуля;

  13. Если данное условие выполняется, вызывается метод updatePID, который регулирует скорость моторов с помощью ПИД-регулятора;

  14. Если условие не выполняется, то на пины MOT1_SPEED и MOT2_SPEED подается значение 0, что приводит к остановке моторов.

Рабочая программа для Arduino IDE:

cpp
// пины энкодеров
#define LEFT_ENCODER 2
#define RIGHT_ENCODER 3
#define HOLES 20.0 //количество отверстий на диске энкодера
// пины моторов
#define MOT1_DIRECTION 7
#define MOT1_SPEED 9
#define MOT2_DIRECTION 8
#define MOT2_SPEED 10
// переменные RPM и импульсов
volatile unsigned int firstPulses = 0; //счетчик импульсов 1 мотора
volatile unsigned int secondPulses = 0; //счетчик импульсов 2 мотора
float firstRpm = 0; //рассчитанные RPM для левого мотора
float secondRpm = 0; //RPM для правого мотора
unsigned long timeOld = 0; //время последнего замера RPM
// целевая скорость в RPM
float targetRPM = 0;
// PID параметры ЛЕВОГО мотора
float Kp1 = 0.5, Ki1 = 0.11, Kd1 = 0.03; //коэффициенты P,I,D
float error1 = 0, integral1 = 0, prevError1 = 0; //составляющие регулятора
// PID параметры ПРАВОГО мотора
float Kp2 = 0.2, Ki2 = 0.1, Kd2 = 0.02; //коэффициенты P,I,D
float error2 = 0, integral2 = 0, prevError2 = 0; //составляющие регулятора
// PWM значения
int pwmLeft = 0; //ШИМ значение для левого мотора (0-255)
int pwmRight = 0; //ШИМ значение для правого мотора (0-255)
int direction = 1; // Направление1 = вперед, -1 = назад, 0 = стоп
// обработчики прерываний
void firstCounter() {
firstPulses++; //увеличиваем первый счетчик при каждом импульсе
}
void secondCounter() {
secondPulses++; //увеличиваем второй счетчик при каждом импульсе
}
// расчет RPM
void calculateRPM() {
unsigned long currentTime = millis(); //текущее время
unsigned long delta = currentTime - timeOld; //вычисляем время с прошлого измерения
if (delta >= 100) { //если прошло
firstRpm = (firstPulses * 60000.0) / (HOLES * delta);
secondRpm = (secondPulses * 60000.0) / (HOLES * delta);
timeOld = currentTime; //обновляем время
firstPulses = 0; //сбрасываем счетчики
secondPulses = 0;
//вывод результатов в порт монитора
Serial.print("RPM >> Левый: ");
Serial.print(firstRpm);
Serial.print(" | Правый: ");
Serial.print(secondRpm);
Serial.print(" | Цель: ");
Serial.println(targetRPM);
}
}
// ПИД-регулятор для каждого мотора
void updatePID() {
// левый мотор
error1 = targetRPM - firstRpm; //вычисление ошибки регулирования
integral1 += error1; //интегральная составляющая
float derivative1 = error1 - prevError1; //вычисление производной
float output1 = Kp1 * error1 + Ki1 * integral1 + Kd1 * derivative1; //вычисление ПИД регулятора по формуле
prevError1 = error1; //сохраняем ошибку
pwmLeft = constrain(output1, 0, 255); //ограничиваем ШИМ диапазон
// правый мотор (аналогично левому)
error2 = targetRPM - secondRpm;
integral2 += error2;
float derivative2 = error2 - prevError2;
float output2 = Kp2 * error2 + Ki2 * integral2 + Kd2 * derivative2;
prevError2 = error2;
pwmRight = constrain(output2, 0, 255);
// применение значений к моторам
analogWrite(MOT1_SPEED, pwmLeft);
analogWrite(MOT2_SPEED, pwmRight);
}
void setup() {
Serial.begin(9600);
//настройка энкодеров
pinMode(LEFT_ENCODER, INPUT);
attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER), firstCounter, FALLING);
pinMode(RIGHT_ENCODER, INPUT);
attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER), secondCounter, FALLING);
//настройка пинов моторов
pinMode(MOT1_DIRECTION, OUTPUT);
pinMode(MOT1_SPEED, OUTPUT);
pinMode(MOT2_DIRECTION, OUTPUT);
pinMode(MOT2_SPEED, OUTPUT);
timeOld = millis(); //инициализация времени
}
void loop() {

calculateRPM(); //вызываем расчет скорости

cpp
// команды по Serial
if (Serial.available() > 0) {
if (Serial.read() == 's') { //выполнение если получена команда "s"
int speed = Serial.parseInt(); //читаем число
//проверка диапазона скоростей
if (speed >= -255 && speed <= 255) {
direction = (speed > 0) ? 1 : (speed < 0) ? -1 : 0; //определяем направление движения
targetRPM = map(abs(speed), 0, 255, 0, 300);
// установка направления
if (direction > 0) { //вперед
digitalWrite(MOT1_DIRECTION, LOW);
digitalWrite(MOT2_DIRECTION, HIGH);
} else if (direction < 0) { //назад
digitalWrite(MOT1_DIRECTION, HIGH);
digitalWrite(MOT2_DIRECTION, LOW);
} else { //нет движения
digitalWrite(MOT1_DIRECTION, LOW);
digitalWrite(MOT2_DIRECTION, LOW);
}
Serial.print("Установлена целевая скорость (RPM): ");
Serial.println(targetRPM);
}
}
while (Serial.available() > 0) Serial.read(); // Очистка буфера
}
//управление моторами
if (direction != 0 && targetRPM > 0) { //проверка условия что направление движения не равно 0 и что целевая скорость вращения>0

updatePID(); //вызов функции ПИД регулятоа

cpp
} else {
analogWrite(MOT1_SPEED, 0); //остановка моторов
analogWrite(MOT2_SPEED, 0);
}
}

3.20 Лабораторная работа 20. Управление сервоприводами

Цель работы:

Изучить принцип работы сервопривода, как исполнительного механизма с обратной связью, разработать программу для управления захватного устройства.

Оборудование: микроконтроллер, машинка в собранном виде, сервопривод (2 шт.).

Практическая часть:

Программа реализует поднятие и сжатие-разжатие схвата.

Библиотека Servo.h - стандартная библиотека Arduino для управления сервоприводами. Она позволяет:

  • Управлять до 12 сервоприводами на большинстве плат Arduino

  • Генерировать точностные импульсы ШИМ (широтно-импульсной модуляции)

  • Работать с углами поворота от 0 до 180 градусов

Ключевые функции библиотеки:

Servo.attach(pin); - привязка сервопривода к цифровому пину

Servo.write(angle); - установка угла поворота

Servo.read(); - чтение текущего угла

Servo.writeMicroseconds(ms); - установка длительности импульса в мс

Servo.detach(); - отключение сервопривода от пина

Библиотека Servo генерирует импульсы длительностью 1000-2000 мкс:

  • 1000 мкс ≈ 0 градусов

  • 1500 мкс ≈ 90 градусов

  • 2000 мкс ≈ 180 градусов

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.20.1).

Изображение 118

Рисунок 3.20.1. Внешний вид схемы сборки лабораторной работы 20

Рабочая программа на графическом языке программирования:

Изображение 119

Рисунок 3.20.2. Программа лабораторной работы 20 на графическом языке программирования

Блок "Выполнить разово":

Инициализируется Serial-порт на скорости 9600 и выводится там сообщение о начале работы программы.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Цикл по созданной целочисленной переменной angle выполняет блок кода с заданным счетчиком, начальным и конечным значениями от 120 до 59 с шагом -2. Сервопривод, подключенный к 5 пину, устанавливается в положение текущего значения переменной angle. После каждого поворота сервопривода на 2 градуса производится задержка в 30 мс. Происходит плавное поднятие схвата;

  2. Задержка в 500 мс;

  3. Цикл по созданной целочисленной переменной angle выполняет блок кода с заданным счетчиком, начальным и конечным значениями от 90 до 159 с шагом 2. Сервопривод, подключенный к 6 пину, устанавливается в положение текущего значения переменной angle. После каждого поворота сервопривода на 2 градуса производится задержка в 20 мс. Происходит плавное сжатие хвата;

  4. Задержка в 500 мс;

  5. Цикл по созданной целочисленной переменной angle выполняет блок кода с заданным счетчиком, начальным и конечным значениями от 150 до 89 с шагом -2. Сервопривод, подключенный к 6 пину, устанавливается в положение текущего значения переменной angle. После каждого поворота сервопривода на 2 градуса производится задержка в 20 мс. Происходит плавное расжатие хвата;

  6. Задержка в 500 мс;

  7. Задержка в 2000 мс.

Рабочая программа для Arduino IDE:

cpp
#include <Servo.h> //подключение библиотеки для работы с сервоприводами
#define SERV_MAIN 5//pin 1 сервопривода вращательного движения захватывающего устройства
#define SERV_HVAT 6//pin 2 сервопривода захватывающего устройства
Servo mainServo; //создание объекта для стрелы
Servo hvatServo; //создание объекта для схвата
void setup() {
mainServo.attach(SERV_MAIN); //привязка сервопривода стрелы к пину 5
hvatServo.attach(SERV_HVAT); //привязка сервопривода схвата к 6 пину
Serial.begin(9600); //инициализация последовательного порта
Serial.println("Программа запущена"); //сообщение о начале программы, выводится в порт
}
void loop() {
// Подъём схвата (mainServo)
for (int angle = 120; angle >= 60; angle -= 2) {
mainServo.write(angle); //плавное изменение угла сервопривода
delay(30); //задержка для плавности движения
}
delay(500); // пауза после подъёма
// сжатие хвата (hvatServo)
for (int angle = 90; angle <= 160; angle += 2) {
hvatServo.write(angle); //плавное закрытие схвата
delay(20); //задержка для плавности движения
}
delay(500); // пауза после сжатия
// разжатие хвата
for (int angle = 150; angle >= 90; angle -= 2) {
hvatServo.write(angle); //плавное открытие схвата
delay(20); //задержка для плавности работы
}
delay(500); // пауза после разжатия
delay(2000); // пауза перед следующим циклом
}

3.21 Лабораторная работа 21. Модуль Bluetooth

Цель работы:

Изучит принцип беспроводной передачи данных по протоколу Bluetooth. Создать систему дистанционного управления Arduino по Bluetooth с телефона.

Оборудование: микроконтроллер.

Теоретическая часть:

Беспроводная передача данных на устройство является самым популярным способом дистанционного управления системой. В настоящее время существует множество различных беспроводных интерфейсов и протоколов, как для применения в обычной среде, так и промышленных, в том числе защищенных.

На программируемом контроллере комплекта “КПМИС (Стартовый)” имеется модуль беспроводной передачи данных Bluetooth 2-го поколения. Данный модуль является одним из самых популярных модулей. Под него существует большое количество готовых программ на мобильные устройства, и обмен с ним реализуется очень просто. Одним из недостатком данного модуля является невозможность обмениваться с ним данными, используя платформу IOS.

Для работы с данным модулем необходимо выполнить его физическое подключение к микроконтроллеру, установив перемыкатели на джамперы 23 и 24. Таким образом, модуль будет подключен к UART интерфейсу микроконтроллера, и обращение к нему необходимо реализовывать через класс Serial 1.

Для подключения мобильного устройства к модулю беспроводной передачи данных, необходимо подать питание на плату и выполнить поиск доступных Bluetooth - устройств. Размещенный на плате модуль имеет название HC-05 и при подключении запрашивает PIN - необходимо использовать либо “0000” или “1234”.

В Google Play существует большое количество пультов управления, поддерживающих данный модуль беспроводной передачи данных, благодаря чему можно реализовать управление робототехнической платформой с мобильного телефона.

Практическая часть:

Обратите внимание, что модуль может быть первоначально настроен на другую скорость обмена данными (либо 9600, либо 115200, либо еще какую-то). Для того, чтобы выполнить настройку модуля, можно воспользоваться АТ-командами. Для перехода в этот режим необходимо установить перемыкатель на джампер 25 и выполнить перезагрузку контроллера по питанию. Далее загрузить в контроллер представленный ниже код. После этого необходимо открыть монитор порта, установить режим работы “Оба NL&CR” вместо “Не найден конец строки”, скорость 38400 и отправить команду “АТ”. Если в ответ придет результат, то модуль успешно вошел в режим настройки АТ-командами.

Однако, может возникнуть ситуация, когда все сделано правильно, но модуль не возвращает данные. В таком случае, необходимо изменить скорость обмена данными как в программном коде, так и в мониторе порта. Проверять придется все популярные скорости в режиме АТ команд. После входа в режим с помощью АТ команд, можно изменить скорость обмена данными модуля, его имя, пин код, режим работы и тд.

Примеры АТ команд:

АТ - проверить статус подключения

AT+VERSION? - узнать версию прошивки

AT+UART? - узнать установленную скорость

AT+UART=38400,0,0 - установить скорость 38400

AT+NAME? - узнать имя

AT+NAME=HC-05 BLUE - установить имя HC-05 BLUE

AT+PSWD? - установить пароль

AT+PSWD=0000 - установить пароль 0000

AT+ORGL - сброс на заводские настройки

Пример настройки модуля в качестве ведомого (slave):

Сброс предыдущих настроек: AT+ORGL

Сброс спаренных устройств: AT+RMAAD

Установка пароля: AT+PSWD=1234

Включение режима ведомого: AT+ROLE=0

Дополнительно можно узнать адрес устройства (понадобится для настройки спаренного модуля): AT+ADDR?

В ответ получим сам адрес: ADDR=xx:x:xxxxxx

Пример настройки модуля в качестве ведущего:

Сброс предыдущих настроек: AT+ORGL

Сброс спаренных устройств: AT+RMAAD

Включение режима ведущего: AT+ROLE=1

Рестарт после смены роли: AT+RESET

Если необходимо связать ведомого и ведущего прописываем такие команды:

Установка пароля ведомого: AT+PSWD=1234

Указываем парное устройство: AT+PAIR=<адрес>, <таймаут> (пример: AT+PAIR=12,6,143117,5)

Связываем с конкретным адресом: AT+BIND=<адрес> (пример: AT+BIND=12,6,143117)

Запрещаем соединяться с другими адресами: AT+CMODE=0

После настройки требуется отключить перемыкатель АТ и перезапустить модуль.

Схема подключения:

В данной лабораторной работе используется только контроллер.

Рабочая программа на графическом языке программирования:

Изображение 120

Рисунок 3.21.1 Программа лабораторной работы 21 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Объявляется целочисленная переменная BT_BAUD для хранения скорости обмена данными Bluetooth-модуля;

  2. Инициализируется последовательный порт Serial со скоростью 9600 бод для вывода сообщений в монитор порта компьютера;

  3. Инициализируется последовательный порт Serial1 со скоростью BT_BAUD для обмена данными с Bluetooth-модулем.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Проверяется наличие входящих данных в последовательном порту Serial1;

  2. Если данные поступили, выполняется чтение строки из последовательного порта Serial1 и результат сохраняется в переменную data;

  3. Из полученной строки data удаляются лишние пробелы и символы переноса строки;

  4. В монитор порта Serial выводится сообщение, содержащее полученные данные Bluetooth-модуля;

  5. Проверяется длина полученной строки переменной data;

  6. Если длина строки 0 (получена пустая команда), то в Serial выводится сообщение «Empty command» и выполняется выход из цикла (завершение текущей итерации цикла обработки данных)

Рабочая программа для Arduino IDE:

cpp
#define BT_BAUD 9600 //задаем скорость Bluetooth модуля
void setup() {
Serial.begin(9600); // настройка монитора порта
Serial1.begin(BT_BAUD); // настройка Bluetooth
}
void loop() {
if (Serial1.available()) { //проверка пришли ли новые данные по Bluetooth
String data = Serial1.readStringUntil('\n'); //чтение всей строки из Bluetooth до символа переноса

data.trim(); // удаляем пробелы и переносы

cpp
Serial.print("Received: "); //печать полученных данных в монитор порта
Serial.println(data);
if (data.length() == 0) { //проверка на пустую команду
Serial.println("Empty command");
return;
}
}
}

В программе прописываются два монитора порта (Serial и Serial1), первый из которых необходим для общения с компьютером через USB, а второй для общения с Bluetooth-модулем.

Data это переменная-контейнер, которая хранит в себе текст команды:

String data = "ON" //data содержит в себе "ON"

data.trim - команда удаления лишних пробелов и символов переноса в начале\конце строки:

“ON\n” -> “ON” // до и после команды trim

data.length() - длина строки, команда считает количество символов в команде.

readStringUntil() - функция чтения данных до указанного символа-разделителя (‘\n’)

Изображение 121

Рисунок 3.21.1. Пример вывода сообщений в монитор порта

3.22 Лабораторная работа 22. Модуль Гироскопа-акселерометра

Цель работы:

Знакомство с работой инерциального модуля, содержащего в себе гироскоп и акселерометр, разработка программы считывания данных с датчика и вывода их в монитор последовательного порта.

Оборудование: микроконтроллер.

Теоретическая часть:

Для более точного контролирования движения мобильных платформ применяются такие устройства, как гироскоп и акселерометр.

Гироскоп измеряет угловую скорость вращения вокруг оси, условно измеряемую в радиан/секунду. Если датчик лежит на столе - по всем трём осям будет значение около нуля. Для нахождения текущего угла по скорости нужно интегрировать эту скорость.

Акселерометр измеряет ускорение вдоль оси, условно измеряемое в метрах/секунду. Если датчик лежит на столе или движется с постоянной скоростью, на оси будет спроецирован вектор силы тяжести. Если датчик движется с ускорением, вдобавок к ускорению свободного падения получим составляющие вектора ускорения. Если датчик находится в свободном падении, величины ускорения по всем осям будут равны 0. Зная проекции вектора силы тяжести, можно с высокой точностью определить угол наклона датчика относительно него. Если датчик движется, не получится однозначно определить направление вектора силы тяжести, и его угол, соответственно, тоже.

Используя оба источника данных, можно достаточно точно определить угол положения датчика, а следовательно, и угол положения робота в пространстве, на котором установлен контроллер. Данный датчик аппаратно подключен к шине I2C и имеет адрес инициализации 0х68.

Акселерометр MPU6050 измеряет ускорение с помощью микроэлектромеханических структур. Внутри датчика находятся:

  • подвижные массы, подвешенные на пружинах

  • фиксированные электроды

  • система измерения емкости

При ускорении массы смещаются, изменяя емкость между электродами. Это изменение преобразуется в напряжение, которое оцифровывается АЦП.

Изображение 122

Рисунок 3.22.1. Направление осей модуля MPU6050

Практическая часть:

Необходимо разработать программу для взаимодействия с инерциальным модулем, который объединяет в себе гироскоп и акселерометр, с целью непрерывного считывания и визуализации параметров в реальном времени через последовательный порт Arduino.

Для работы с датчиком необходимо установить библиотеку. В самом Arduino вы можете скачать библиотеку Adafruit MPU6050 (“Инструменты” -> “Управление библиотеками” ).

Изображение 123

Рисунок 3.22.2. Установка библиотеки для инерциального модуля

Данная рабочая программа (см. ниже) позволяет отслеживать:

  • Линейные ускорения по осям x,y,z (в м/c^2)

  • Угловые скорости вращения (в рад/c)

Основные функции библиотеки Adafruit MPU6050:

  • Функция begin() инициализирует коммуникацию с датчиком через I2C интерфейс. Возращаемое значение true при успешной инициализации и false при ошибке подключения.

  • Функция setAccelerometrRange (range) устанавливает чувствительность акселерометра.

Range - передаваемый параметр: MPU6050_RANGE_2_G: ±2g

MPU6050_RANGE_4_G: ±4g

MPU6050_RANGE_8_G: ±8g

MPU6050_RANGE_16_G: ±16g

Каждый диапазон имеет фиксированное количество бит АЦП (16 бит=65536 значений). Чувствительность акселерометра определяет, насколько сильно изменится цифровое значение при заданном ускорении. Единица измерения чувствительности цифровых акселерометров LSB/g. Для определения ориентации подходит чувствительность ±2g, так как имеет наименьшую погрешность измерения.

  • Функция setGyroRange (range) устанавливает чувствительность гироскопа. Выбор диапазона чувствительности зависит от требуемой точности и ожидаемыми скоростями вращения.

Range - передаваемый параметр:

MPU6050_RANGE_250_DEG: ±250 ∘/c

MPU6050_RANGE_500_DEG: ±500 ∘/c

MPU6050_RANGE_1000_DEG: ±1000 ∘/c

MPU6050_RANGE_2000_DEG: ±2000 ∘/c

Для большинства простых применений в робототехнике используется ±250 ∘/c и имеет минимальную погрешность интегрирования для определения угла.

  • Функция setFilterBandwidth (bandwidth) устанавливает полосу пропускания цифрового фильтра низких частот. Фильтр подавляет высокочастотные шумы и вибрации, но вносит задержку.

Dandwidth - передаваемый параметра:

MPU6050_BAND_260_HZ используется для минимальной фильтрации

MPU6050_BAND_184_HZ

MPU6050_BAND_94_HZ

MPU6050_BAND_44_HZ

MPU6050_BAND_21_HZ оптимально для устранения шума

MPU6050_BAND_10_HZ

MPU6050_BAND_5_HZ максимально сглаживает

  • Функция getEvent (accel_event,gyro_event,temp_event) получения калиброванных данных от всех сенсоров.

  • Структура данных sensor_event_t {

int32_t version; //версия структуры

int32_t sensor_id; //идентификатор сенсора

int32_t type;

int32_t timestamp; //временная метка (мс)

cpp
float acceleration[3]; //x,y,z [м/c^2]
float gyro[3]; //x,y,z [рад/c]
float temperature;
};

Схема подключения:

В данной лабораторной работе используется только контроллер.

Рабочая программа на графическом языке программирования:

Изображение 124

Рисунок 3.22.3 Программа лабораторной работы 22 на графическом языке программирования

Блок "Выполнить разово» содержит последовательность действий:

  1. Инициализируется Serial-порт на скорости 115200;

  2. Инициализация работа модуля гироскопа-акселерометра;

  3. В последовательный порт выводится сообщения об успешной инициализации датчика;

  4. Задержка в 500 мс;

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Считываются данные с датчика;

  2. В Serial выводятся данные с датчика:

  • С текстом «Accel» выводятся ускорения по осям x,y,z м/с;

  • С текстом «Gyro» выводятся угловые скорости по осям x,y,z рад/с;

  • Задержка в 200 мс перед следующим чтением данных с модуля датчика.

Рабочая программа для Arduino IDE:

cpp
#include <Adafruit_MPU6050.h> //подключение специальной библиотеки для работы с датчиком MPU6050
#include <Adafruit_Sensor.h> //подключение общей библиотеки для датчика
Adafruit_MPU6050 mpu; //создание виртуального объекта mpu для управления датчиком
void setup() {
Serial.begin(115200); //инициализация последовательного порта
// инициализация датчика
if (!mpu.begin()) {
Serial.println("Ошибка подключения MPU6050!"); //вывод сообщения об ошибке подключения датчика
while (1) delay(10); //бесконечный цикл (он останавливает программу при ошибке)
}
// настройка параметров
mpu.setAccelerometerRange(MPU6050_RANGE_2_G); // настройка акселерометра на диапазон ±2g (1g=9,8 м/c^2)
mpu.setGyroRange(MPU6050_RANGE_250_DEG); //настройка гироскопа на диапазон ±250°/с
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ); // установка фильтра низких частот 21 Гц для сглаживания данных
Serial.println("Датчик инициализирован!"); //вывод сообщения при успешном подключении датчика
delay(500);
}
void loop() {
// чтение данных
sensors_event_t a, g, temp; //создание трёх структур данных (а-для акселерометра, g-для гироскопа, temp-для температуры (не используем))
mpu.getEvent(&a, &g, &temp); //запрос данных с датчика и заполнение структур
// вывод акселерометра (м/с²)
Serial.print("Accel: "); //вывод заголовка данных акселерометра
Serial.print("X="); Serial.print(a.acceleration.x, 1); //вывод ускорения по оси х с точностью до 0.1 м/c^2
Serial.print(" Y="); Serial.print(a.acceleration.y, 1); //вывод ускорения по оси y с точностью до 0.1 м/c^2
Serial.print(" Z="); Serial.print(a.acceleration.z, 1); //вывод ускорения по оси z с точностью до 0.1 м/c^2
Serial.print(" m/s² | ");
// вывод гироскопа (рад/с)
Serial.print("Gyro: ");
Serial.print("X="); Serial.print(g.gyro.x, 1); //вывод угловой скорости по оси х (рад/с)
Serial.print(" Y="); Serial.print(g.gyro.y, 1); //вывод угловой скорости по оси y (рад/с)
Serial.print(" Z="); Serial.print(g.gyro.z, 1); //вывод угловой скорости по оси z (рад/с)
Serial.println(" rad/s");
delay(200); // пауза между измерениями
}

3.23 Лабораторная работа 23. Управление машинкой по Bluetooth

Цель работы:

Разработать программу дистанционного управления движением машинки и работой захватывающего устройства через Bluetooth с обработкой цифровых команд в реальном времени.

Оборудование: микроконтроллер, машинка в собранном виде, ДПТ (2 шт.), сервопривод (2 шт.).

Практическая часть:

В ходе данной лабораторной работы разработана программа дистанционного ручного управления движением машинки и работой захватывающего устройства через Bluetooth с обработкой цифровых команд.

Для Bluetooth связи можно использовать, например мобильное приложение для Android “Arduino Bluetooth Control” или другое. Отправлять команды можно через терминал в приложении.

Команды для работы машинки:

  • 0 - команда движения назад

  • 1 - команда движения вперед

  • 2 - команда переключения между поднятым и опущенным захватывающим устройством

  • 3 - команда переключения между сжатым и разжатым состоянием захватывающим устройством

  • 4 - команда поворота машинки по часовой стрелке

  • 5 - команда полной остановки моторов

Схема подключения:

При выполнении данной лабораторной работы используется следующая схема подключения (Рисунок 3.23.1).

Изображение 125

Рисунок 3.23.1. Внешний вид схемы сборки лабораторной работы 23

Рабочая программа на графическом языке программирования:

Изображение 126

Изображение 127

Рисунок 3.23.2 Программа лабораторной работы 23 на графическом языке программирования

Блок "Выполнить разово" содержит последовательность действий:

  1. Объявляется целочисленная переменная MOT1_DIRECTION, равная 7, для хранения номера пина управления направлением вращения первого ДПТ;

  2. Объявляется целочисленная переменная MOT1_SPEED, равная 9, для хранения номера ШИМ-пина управления скоростью первого двигателя;

  3. Объявляется целочисленная переменная MOT2_DIRECTION, равная 8, для хранения номера пина управления направлением вращения второго ДПТ;

  4. Объявляется целочисленная переменная MOT2_SPEED, равная 10, для хранения номера ШИМ-пина управления скоростью второго двигателя;

  5. Объявляется целочисленная переменная SERV_MAIN, равная 3, для хранения номера пина сервопривода подъема/опускания захватывающего устройства;

  6. Объявляется целочисленная переменная SERV_HVAT, равная 5, для хранения номера пина сервопривода сжатия/разжатия захватывающего устройства;

  7. Объявляется целочисленная переменная BT_BAUD для хранения скорости обмена данными Bluetooth-модуля;

  8. Объявляются целочисленные переменные LIFT_UP, LIFT_DOWN, GRAB_OPEN, GRAB_CLOSE, хранящие значения углов положения для сервоприводов;

  9. Создаются переменные состояния сервоприводов isLiftUp, isGrabClosed, с начальным значением «ложь», то есть изначально захват не поднят и не сжат;

  10. Пины ДПТ инициализируются как выходы (OUTPUT);

  11. Пин сервопривода подъема/опускания захватывающего устройства инициализируется переменной, равной 0, пин сервопривода сжатия/разжатия инициализируется переменной, равной 1;

  12. Устанавливается исходное положение захвата: сервоприводам присваиваются значения положений LIFT_DOWN и GRAB_OPEN;

  13. Инициализируется последовательный порт Serial со скоростью 9600 бод для вывода сообщений в монитор порта компьютера;

  14. Инициализируется последовательный порт Serial1 со скоростью BT_BAUD для обмена данными с Bluetooth-модулем.

Блок "Метод toggleLift" содержит последовательность действий:

  1. Выполняется логическое отрицание переменной isLiftUp и результат записывается обратно в переменную isLiftUp, что позволяет изменить текущее состояние положения захвата;

  2. Проверяется условие: если переменная isLiftUp имеет значение истина, выполняется команда установки сервопривода SERV_MAIN в положение LIFT_UP, соответствующее поднятому состоянию захватывающего устройства;

  3. Если условие не выполняется (переменная isLiftUp имеет значение ложь), сервопривод SERV_MAIN устанавливается в положение LIFT_DOWN;

  4. После установки положения сервопривода выполняется задержка 500 мс, необходимая для завершения перемещения захватывающего механизма.

Блок "Метод toggleGrab " содержит последовательность действий:

  1. Выполняется логическое отрицание переменной isGrabClosed, что позволяет изменить текущее состояние положения захвата;

  2. Проверяется условие: если переменная isGrabClosed имеет значение истина, выполняется команда установки сервопривода SERV_HVAT в положение GRAB_CLOSE, соответствующее закрытому состоянию захвата;

  3. Если условие не выполняется (переменная isGrabClosed имеет значение ложь), сервопривод SERV_HVAT устанавливается в положение GRAB_OPEN;

  4. После установки положения сервопривода выполняется задержка 500 мс, необходимая для завершения перемещения захватывающего механизма.

Блок "Метод moveForward" содержит последовательность действий:

  1. На пин MOT1_DIRECTION подается логический сигнал LOW, определяющий направление вращения первого двигателя;

  2. На пин MOT1_PWM подается ШИМ-сигнал со значением 40, задающий скорость вращения первого двигателя;

  3. На пин MOT2_DIRECTION подается логический сигнал HIGH, определяющий направление вращения второго двигателя;

  4. На пин MOT2_PWM подается ШИМ-сигнал со значением 40, задающий скорость вращения второго двигателя.

Блок "Метод moveBackward" содержит последовательность действий:

  1. На пин MOT1_DIRECTION подается логический сигнал HIGH, определяющий направление вращения первого двигателя;

  2. На пин MOT1_PWM подается ШИМ-сигнал со значением 30, задающий скорость вращения первого двигателя;

  3. На пин MOT2_DIRECTION подается логический сигнал LOW, определяющий направление вращения второго двигателя;

  4. На пин MOT2_PWM подается ШИМ-сигнал со значением 30, задающий скорость вращения второго двигателя.

Блок "Метод turnRight" содержит последовательность действий:

  1. На пин MOT1_DIRECTION подается логический сигнал LOW, определяющий направление вращения первого двигателя;

  2. На пин MOT1_PWM подается ШИМ-сигнал со значением 50, задающий скорость вращения первого двигателя;

  3. На пин MOT2_DIRECTION подается логический сигнал LOW, определяющий направление вращения второго двигателя;

  4. На пин MOT2_PWM подается ШИМ-сигнал со значением 50, задающий скорость вращения второго двигателя.

Блок "Метод stopMotors" содержит последовательность действий:

  1. На пин MOT1_PWM подается ШИМ-сигнал со значением 0, что приводит к остановке первого двигателя;

  2. На пин MOT2_PWM подается ШИМ-сигнал со значением 0, что приводит к остановке второго двигателя.

Блок "Выполнять в цикле" содержит последовательность действий:

  1. Проверяется наличие входящих данных от Bluetooth-модуля в последовательном порту Serial1;

  2. Создается символьная переменная command, которой присваивается полученный символ-команда с Serial1;

  3. Для контроля полученная команда выводится в монитор порта компьютера Serial;

  4. Полученная команда обрабатывается через switch(command). После ключевого слова switch в скобках идёт сравниваемое выражение. Значение этого выражения последовательно сравнивается со значениями после оператора case, и если совпадение найдено, выполняется определённый блок.

  5. Если с Serial1 получена команда 0, то выполняется движение назад: вызывается метод moveBackward;

  6. Если с Serial1 получена команда 1, то выполняется движение вперед: вызывается метод moveForward;

  7. Если с Serial1 получена команда 2, то выполняется команда поднять/опустить схват: вызывается метод toggleLift;

  8. Если с Serial1 получена команда 3, то выполняется команда открыть/закрыть схват: вызывается метод toggleGrab;

  9. Если с Serial1 получена команда 4, то выполняется поворот по часовой стрелке: вызывается метод turnRight и stopMottors;

  10. Если с Serial1 получена команда 5, то выполняется остановка машинки: вызывается метод stopMottors.

Рабочая программа для Arduino IDE:

cpp
#include <Servo.h>
// пины для двигателей
#define MOT1_DIRECTION 7
#define MOT1_PWM 9
#define MOT2_DIRECTION 8
#define MOT2_PWM 10
// пины для сервоприводов
#define SERV_MAIN 3
#define SERV_HVAT 5
// константа для Bluetooth
#define BT_BAUD 9600 //скорость общения с Bluetooth модулем
// состояния сервоприводов
#define LIFT_UP 40 //угол поднятого захватывающего устройства
#define LIFT_DOWN 70 //угол опущенного захватывающего устройства
#define GRAB_OPEN 60 //угол открытого захватывающего устройства
#define GRAB_CLOSE 160 //угол закрытого захватывающего устройства
// создаем Объекты сервоприводов
Servo mainServo;
Servo hvatServo;
// текущие состояния для положения захватывающего устройства
bool isLiftUp = false;
bool isGrabClosed = false;
void setup() {
// настройка пинов двигателей
pinMode(MOT1_DIRECTION, OUTPUT);
pinMode(MOT1_PWM, OUTPUT);
pinMode(MOT2_DIRECTION, OUTPUT);
pinMode(MOT2_PWM, OUTPUT);
// инициализация сервоприводов
mainServo.attach(SERV_MAIN);
hvatServo.attach(SERV_HVAT);
// исходное положение схвата (опущен и открыт)
mainServo.write(LIFT_DOWN);
hvatServo.write(GRAB_OPEN);
// настройка Bluetooth
Serial.begin(9600); // Монитор порта для отладки
Serial1.begin(BT_BAUD); // Bluetooth на Serial1
}
void loop() {
if (Serial1.available()) {//проверка присланы ли команды по Bluetooth
char command = Serial1.read(); //чтение команды Bluetooth
Serial.print("Received: ");
Serial.println(command); //выводим полученную команду в монитор порта компьютера
// обработка команд
switch(command) {
case '0': // движение назад
moveBackward();
break;
case '1': // движение вперед
moveForward();
break;
case '2': // поднятие/опускание схвата
toggleLift();
break;
case '3': // сжатие/разжатие схвата
toggleGrab();
break;
case '4': // поворот по часовой стрелке
turnRight();
stopMotors();
break;
case '5': // остановка
stopMotors();
break;
}
}
}
// переключение поднятия/опускания схвата
void toggleLift() {
isLiftUp = !isLiftUp;
mainServo.write(isLiftUp ? LIFT_UP : LIFT_DOWN); //проверка на текущее состояние схвата (если уже поднят, то опустить и наоборот)
delay(500);
}
// переключение сжатия/разжатия схвата
void toggleGrab() {
isGrabClosed = !isGrabClosed;
hvatServo.write(isGrabClosed ? GRAB_CLOSE : GRAB_OPEN); //проверка на текущее состояние схвата (если сжат, то разжать и наоборот)
delay(500);
}
// функции управления моторами
void moveForward() { //движение вперед
digitalWrite(MOT1_DIRECTION, LOW);
analogWrite(MOT1_PWM, 40);
digitalWrite(MOT2_DIRECTION, HIGH);
analogWrite(MOT2_PWM, 40);
}
void moveBackward() { //движение назад
digitalWrite(MOT1_DIRECTION, HIGH);
analogWrite(MOT1_PWM, 30);
digitalWrite(MOT2_DIRECTION, LOW);
analogWrite(MOT2_PWM, 30);
}
void turnRight() { // поворот по часовой стрелке
digitalWrite(MOT1_DIRECTION, LOW);
analogWrite(MOT1_PWM, 50);
digitalWrite(MOT2_DIRECTION, LOW);
analogWrite(MOT2_PWM, 50);
delay(200); // Время поворота
}
void stopMotors() { //остановка моторов
analogWrite(MOT1_PWM, 0);
analogWrite(MOT2_PWM, 0);
}