Опубликовано

Генерирование несколько тонов одновременно с помощью Arduino

По материалам главы 9 («Работа со звуком») книги “Arduino. Большая книга рецептов, 3-е изд.” (авторы Джепсон Брайан, Марголис Майкл, Уэлдин Николас Роберт).

Arduino. Большая книга рецептов, 3-издание

ЗАДАЧА

Требуется генерировать одновременно два звука. Библиотека Tone среды Arduino IDE поддерживает воспроизведение только одного тона на стандартных платах, но мы хотим создавать одновременно два тона. При этом следует отметить, что плата Mega оснащена большим количеством таймеров и способна воспроизводить до шести тонов одновременно.

РЕШЕНИЕ

Возможности библиотеки Tone среды Arduino IDE ограничены одновременным воспроизведением лишь одного тона. Это объясняется тем, что для каждого тона требуется использовать отдельный таймер, и хотя стандартная плата Arduino оснащена тремя таймерами, один из них занят функцией millis(), а еще один требуется для работы с сервомашинками. Чтобы решить эту проблему, здесь применена библиотека Tone с расширенными возможностями разработки Бретта Хагмана (Brett Hagman), который также является и разработчиком функции tone(). Его библиотека позволяет генерировать одновременно несколько тонов. Библиотеку можно загрузить с ее веб-страницы Github (https://github.com/bhagman/Tone) или же просто установить с помощью Менеджера библиотек.

В листинге 9.4 приводится код скетча, который проигрывает часть мелодии «Twinkle, Twinkle Little Star» с теми же самыми нотами, но в двух октавах.

Листинг 9.4. Скетч для воспроизведения нескольких тонов одновременно на стандартной плате Arduino

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* Скетч Dual Tones
* Проигрывает фрагмент мелодии "Twinkle, Twinkle Little Star" в двух октавах
*/
 
#include <Tone.h>
int notes1[] = {NOTE_C3, NOTE_C3, NOTE_G3, NOTE_G3, NOTE_A4, NOTE_A4,
NOTE_G3, NOTE_F3, NOTE_F3, NOTE_E3, NOTE_E3, NOTE_D3,
NOTE_D3, NOTE_C3 };
int notes2[] = {NOTE_C3, NOTE_C3, NOTE_G3, NOTE_G3, NOTE_A4, NOTE_A4,
NOTE_G3, NOTE_F3, NOTE_F3, NOTE_E3, NOTE_E3, NOTE_D3,
NOTE_D3, NOTE_C3 };
const byte scoreLen = sizeof(notes1)/sizeof(notes1[0]); // Количество нот
// Тоны можно объявить в виде массива
Tone notePlayer[2];
 
void setup(void)
{
notePlayer[0].begin(11);
notePlayer[1].begin(12);
}
 
void loop(void)
{
for (int i = 0; i < scoreLen; i++)
{
notePlayer[0].play(notes1[i]);
delay(100); // Небольшая пауза перед проигрыванием следующей ноты
notePlayer[1].play(notes2[i]);
delay(400);
notePlayer[0].stop();
notePlayer[1].stop();
delay(30);
}
delay(1000);
}

Обсуждение работы решения и возможных проблем

Чтобы смешать два выходных сигнала разной частоты на одном динамике, сигналы с выходных контактов платы Arduino подаются на один из контактов динамика через резисторы номиналом 500 Ом. Второй контакт динамика подключается на «землю» (контакт GND платы Arduino), как показано на схемах для предыдущих решений (например, на рис. 9.3).

Подключение динамика для воспроизведения тонов разной частоты и длительности

Рис. 9.3. Подключение динамика для воспроизведения тонов разной частоты и длительности

На стандартных платах Arduino для генерирования первого тона задействуется таймер 2 (в результате чего подача ШИМ-сигнала на контакты 9 и 10 платы будет невозможна), а для генерирования второго сигнала — таймер 1 (что делает невозможным использование библиотеки Servo, а также подачу ШИМ-сигнала на контакты 11 и 12). На платах Mega для каждого тона используется отдельный таймер
в следующем порядке: 2, 3, 4, 5, 1, 0. На момент подготовки книги эта библиотека поддерживает возможности, реализуемые архитектурой AVR, и не поддерживается платами с микроконтроллерами ARM и платами MegaAVR — такими как платы Arduino Uno WiFi R2 и Nano Every.

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

Хотя на плате Arduino возможно одновременно воспроизводить три тона, а на плате Mega — более чем шесть, это создает проблемы для нормальной работы функций delay() и millis(). Поэтому для надежности рекомендуется одновременно воспроизводить только два тона на плате Arduino или пять на плате Mega.
Опубликовано

Имитация ИК-сигналов управления пульта ДУ

Раздел из главы 10 “Дистанционное управление” из книги “Arduino. Большая книга рецептов, 3-е изд.” (авторы Джепсон Брайан, Марголис Майкл, Уэлдин Николас Роберт)

Arduino. Большая книга рецептов, 3-издание

ЗАДАЧА

Требуется управлять с помощью платы Arduino телевизором или каким-либо другим устройством, эмулируя коды ИК-сигналов пульта ДУ. Эта задача противоположна рассмотренной в разд. 10.2 — здесь нам нужно не принимать команды, а отправлять их.

Для приема платой Arduino сигналов пульта ДУ используется модуль приемника ИК-сигналов. На рынке широко представлены приемники ИК-сигнала TSOP38238, TSOP4838, PNA4602 и TSOP2438. Первые три эти приемника имеют одинаковую схему расположения контактов, а у приемника TSOP2438 выводы питания (+5 В и GND) поменяны местами. При использовании какого-либо иного приемника ИК-сигналов уточните схему расположения его контактов в справочном листке (datasheet) на него и подключите его к плате Arduino, как там указано. Подключение приемников TSOP38238, TSOP4838 и PNA4602 к плате Arduino показано в схеме на рис. 10.1.

C:\Users\acer\Documents\#Electronics\Магазин электроники\фрагменты из книг\2788. Arduino Cookbook. Recipes to Begin, Expand, and Enhance\verstka\pic\10\10.1.png

Рис. 10.1. Подключение приемника ИК-сигнала к плате Arduino

РЕШЕНИЕ

Для решения этой задачи к плате Arduino нужно подключить пять кнопок и инфракрасный светодиод, как показано на рис. 10.2.

Скетч для управления этой схемой приводится в листинге 10.4. Для управления устройством в нем используются коды кнопок из скетча, приведенного в решении из разд. 10.2 (см. листинг 10.2). Коды для управления вашим устройством, скорее всего, будут другими, поэтому узнайте их, исполнив скетч из разд. 10.2, а затем замените ими коды в скетче из листинга 10.4. Нажатие одной из пяти кнопок выбирает и отправляет требуемый код.

Листинг 10.4. Скетч для эмулирования ИК-пульта ДУ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
* Скетч irSend
* Требует подключения ИК-светодиода к контакту 3
* и 5 кнопок к контактам 6-10
*/
#include <IRremote.h> // Подключаем библиотеку IRremote
const int numberOfKeys = 5;
const int firstKey = 6; // Номер первого из пяти последовательных
// контактов, к которым подключаются кнопки
bool buttonState[numberOfKeys];
bool lastButtonState[numberOfKeys];
long irKeyCodes[numberOfKeys] =
{
0x18E758A7, //0 key
0x18E708F7, //1 key
0x18E78877, //2 key
0x18E748B7, //3 key
0x18E7C837, //4 key
};
 
IRsend irsend;
void setup()
{
for (int i = 0; i < numberOfKeys; i++)
{
buttonState[i] = true;
lastButtonState[i] = true;
int physicalPin=i + firstKey;
pinMode(physicalPin, INPUT_PULLUP); // Включаем внутренние повышающие резисторы
}
Serial.begin(9600);
}
 
void loop()
{
for (int keyNumber=0; keyNumber<numberOfKeys; keyNumber++)
{
int physicalPinToRead = keyNumber + firstKey;
buttonState[keyNumber] = digitalRead(physicalPinToRead);
if (buttonState[keyNumber] != lastButtonState[keyNumber])
{
if (buttonState[keyNumber] == LOW)
{
irsend.sendSony(irKeyCodes[keyNumber], 32);
Serial.println("Sending"); // Посылаем команду
}
lastButtonState[keyNumber] = buttonState[keyNumber];
}
}
}

C:\Users\acer\Documents\#Electronics\Магазин электроники\фрагменты из книг\2788. Arduino Cookbook. Recipes to Begin, Expand, and Enhance\verstka\pic\10\10.2.png

Рис. 10.2. Подключение кнопок и инфракрасного светодиода к плате Arduino

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

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

Обсуждение работы решения и возможных проблем

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

Преобразование числовых кодов в мигания ИК-светодиода реализует библиотека IRremote. Для этого в скетче требуется создать экземпляр объекта этой библиотеки. Такой экземпляр для управления светодиодом, подключенным к контакту 3 (выбирать контакт для подключения светодиода не представляется возможным, поскольку этот параметр жестко закодирован в библиотеке), создается следующей строкой кода:

IRsend irsend;
В зависимости от используемой платы Arduino, ИК-светодиод может потребоваться подключить к иному контакту, чем 3. Например, для плат Teensy 3.x ИК-светодиод надо подключать к контакту 5. Более подробная информация на этот счет содержится в файле README библиотеки IRremote (https://oreil.ly/XOfdd). Если для вашей платы ИК-светодиод нужно подключать к одному из контактов, который в скетче решения используется для подключения кнопок (контакты 6–10), следует взять другой диапазон номеров контактов для подключения кнопок.

Для хранения диапазона значений кодов управления в скетче используется массив irKeyCodes (см. также решение разд. 2.4). Скетч отслеживает состояние пяти кнопок и при обнаружении нажатия одной из них посылает соответствующий код кнопки следующим оператором:

irsend.sendSony(irKeyCodes[keyNumber], 32);

Объект irSend поддерживает разные функции для работы с различными популярными форматами ИК-кодов. В случае использования системы дистанционного управления с другими форматами кодов, посмотрите эти форматы в документации на библиотеку. Коды также можно узнать с помощью скетча из разд. 10.2, который выводит их в окно монитора порта.

При передаче функции кода кнопки из массива в конце кода также передается число, сообщающее функции количество битов в коде. Префикс 0x перед определениями кодов в массиве irKeyCodes в начале скетча означает шестнадцатеричный формат этих чисел (подробная информация о шестнадцатеричных числах приводится в главе 2). Каждый символ шестнадцатеричного числа представляет 4-битное значение. Восемь шестнадцатеричных цифр кода означает 32 бита.

Светодиод к плате Arduino подключается через токоограничивающий резистор (подробная информация о применении токоограничивающих резисторов со светодиодами приводится во введении главы 7 (разд. 7.0)).

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

Дополнительная информация

Дополнительная информация по управлению светодиодами приведена в главе 7.

Проект необычного ИК-пульта дистанционного управления для телевизоров представлен на веб-странице https://oreil.ly/7BzKh.

Опубликовано

Управление несколькими цветными светодиодами одним контактом платы Arduino

Раздел из главы 7 “Управление светодиодными устройствами вывода данных” из книги “Arduino. Большая книга рецептов, 3-е изд.” (авторы Джепсон Брайан, Марголис Майкл, Уэлдин Николас Роберт)

Arduino. Большая книга рецептов, 3-издание

ЗАДАЧА

Требуется управлять цветом нескольких RGB-светодиодов, используя один контакт платы Arduino.

РЕШЕНИЕ

Для решения этой задачи требуется использовать специальный модуль RGB-светодиодов со встроенным микроконтроллером — кольцо NeoPixel. Подключение модуля NeoPixel и потенциометра для управления его цветом к плате Arduino показано на рис. 7.7.

C:\Users\acer\Documents\#Electronics\Магазин электроники\фрагменты из книг\2788. Arduino Cookbook. Recipes to Begin, Expand, and Enhance\pic\07\7.7.png

Рис. 7.7. Подключение модуля NeoPixel и потенциометра к плате Arduino

При использовании платы Arduino с питанием 3,3 В как потенциометр, так и положительный вывод модуля NeoPixel следует подключить к контакту 3V3 платы Arduino, а не к контакту 5V.

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

Листинг 7.4. Работа с модулем RGB-светодиодов NeoPixels

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
* Скетч SimplePixel
* Меняет свет модуля RGB-светодиодов в зависимости от значения
сигнала датчика
*/
#include <Adafruit_NeoPixel.h>
const int sensorPin = A0; // Номер контакта для подключения датчика
const int ledPin = 6; // Номер контакта для подключения модуля RGB-светодиодов
const int count = 8; // Количество светодиодов в модуле
// Объявляем экземпляр объекта модуля RGB-светодиодов
Adafruit_NeoPixel leds = Adafruit_NeoPixel(count, ledPin, NEO_GRB + NEO_KHZ800);
 
void setup()
{
leds.begin(); // Инициализируем экземпляр модуля светодиодов
for (int i = 0; i < count; i++)
{
leds.setPixelColor(i, leds.Color(0,0,0)); // Выключаем все светодиоды модуля
}
leds.show(); // Обновляем состояние модуля новыми значениями
// отдельных RGB-светодиодов (все выключены)
}
 
void loop()
{
static unsigned int last_reading = -1;
int reading = analogRead(sensorPin);
if (reading != last_reading)
{
// Если значение входного сигнала изменилось,
// сопоставляем новое значение диапазону цветов модуля NeoPixel
unsigned int mappedSensorReading = map(reading, 0, 1023, 0, 65535);
// Обновляем состояние отдельных RGB-светодиодов модуля
// с небольшой задержкой между ними, чтобы создать эффект скольжения
for (int i = 0; i < count; i++)
{
leds.setPixelColor(i, leds.gamma32(
leds.ColorHSV(mappedSensorReading, 255, 128)));
leds.show();
delay(25);
}
last_reading = reading;
}
}

Обсуждение работы решения и возможных проблем

Скетч решения управляет модулем NeoPixel компании Adafruit, содержащим восемь RGB-светодиодов, расположенных в виде кольца. Скетч можно использовать с другими версиями модуля NeoPixel, содержащими большее количество RGB-светодиодов (пикселов), присвоив переменной numOfLeds соответствующее значение. Но в таком случае следует иметь в виду, что каждый светодиод может потреблять до 60 мА тока (если задать для него белый цвет на полную яркость). Порт USB может запитать до восьми светодиодов, но для большего количества питание на модуль NeoPixel необходимо подавать с отдельного мощного источника питания напряжением 5 В. При этом шину «земли» этого источника питания необходимо подключить к шине «земли» платы Arduino. Кроме того, для плат Arduino с напряжением питания 3,3 В, напряжение питания модуля NeoPixel не должно превышать 3,7 В, поскольку напряжение сигнала данных модуля NeoPixel должно быть примерно равно напряжению питания модуля. При использовании для модуля NeoPixel отдельного источника питания, необходимо установить конденсатор емкостью 1000 мкФ между положительной и отрицательной («землей») шинами источника питания для защиты светодиодов модуля. При этом следует обеспечить правильную полярность подключения этого конденсатора.

В скетче объявляется переменная leds объекта модуля NeoPixel с помощью следующей строки кода:

1
Adafruit_NeoPixel leds = Adafruit_NeoPixel(count, ledPin, NEO_GRB + NEO_KHZ800);

В результате исполнения этой строки в памяти платы создается структура для хранения цвета каждого светодиода и взаимодействия с модулем. В параметрах объявления указывается количество светодиодов в модуле (count), номер контакта платы Arduino (ledPin), к которому подключается линия управления модуля, а также тип используемого модуля (NEO_GRB + NEO_KHZ800). При использовании другого светодиодного модуля нужно проверить информацию в документации библиотеки и используемого модуля, чтобы узнать, не требуется ли применить иные параметры.

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

Цвет отдельных светодиодов устанавливается с помощью метода (функции) led.setPixelColor(). В параметрах функции передается номер требуемого светодиода (нумерация которых начинается со значения 0) и требуемый цвет. Данные на светодиод передаются посредством функции led.show(). Прежде чем вызывать функцию led.show(), можно изменить значения цвета нескольких светодиодов, которые потом будут применены одновременно с этой функцией. Значения, которые не были изменены, останутся прежними. При создании экземпляра объекта Adafruit_NeoPixel все значения цветов инициализируются значением 0.

Библиотека Adafruit NeoPixel содержит собственную функцию ColorHSV() для преобразования цвета спектра в значения RGB. Функции передаются три параметра: цвет спектра, насыщенность цвета и яркость, в указанном порядке. Результат, возвращенный функцией ColorHSV(), обрабатывается функцией gamma32(), которая преобразовывает его, чтобы компенсировать разницу между представлением цвета компьютерами и его восприятием людьми.

Каждый светодиод модуля (так называемый пиксел) оснащен контактами для ввода и вывода данных, а также плюса и минуса («земли») питания. Управляющий сигнал с Arduino подается на контакт ввода данных первого светодиода, контакт вывода данных которого подключен к контакту ввода следующего светодиода, и т. д. со всеми светодиодами цепочки. На рынке предлагаются как отдельные пикселы, так и модули из нескольких уже соединенных пикселов, организованных в кольцо или полосу.

Если используемый модуль не поддерживается библиотекой Adafruit

В ранних светодиодных модулях использовалась микросхема WS2811, на смену которой пришли разные другие версии — например, WS2812, WS2812B и APA102. Если ваш светодиодный модуль не поддерживается библиотекой Adafruit, попробуйте использовать библиотеку Fast LED (http://fastled.io).

Совместимая с Arduino плата Teensy 3.x и более поздние ее версии (https://www.pjrc.com/teensy) может управлять восемью модулями и благодаря своему эффективному аппаратному и программному обеспечению способна реализовывать высококачественную анимацию.

Как уже упоминалось ранее, пикселы доступны на рынке как в виде отдельных светодиодов, так и установленные на гибких лентах на разных расстояниях друг от друга (указывается в количестве светодиодов на метр или фут). Компания Adafruit выпускает под торговым именем NeoPixel широкий диапазон светодиодных модулей на печатных платах разных форм-факторов: круги, короткие полоски или панели.

Дополнительная информация

Более подробная информация по модулям NeoPixel приводится в руководстве Uberguide компании Adafruit (https://oreil.ly/zgAVa).

Веб-страница библиотеки OctoWS2811 (https://oreil.ly/yxGBM) поддерживает работу со светодиодными модулями платы Teensy и содержит удачные схемы подключения источников питания для большого количества светодиодов, а также программу movie2serial на языке Processing, которая извлекает данные из файла видео, указанного в программе, и отображает это видео на панели из светодиодных полос.

Опубликовано

Взаимодействие Arduino с компьютерной программой Processing

По материалам книги Джереми Блума “Изучаем Arduino: инструменты и методы технического волшебства. 2-е изд.: пер. с англ.“(Глава 7. Последовательный интерфейс USB)

Изучаем Arduino: инструменты и методы технического волшебства. 2-е изд.

Прежде чем рассмотреть взаимодействие Arduino с компьютерной программой, рассмотрим как такое взаимодействие организовано посредством программы Монитора порта.

Отправка одиночных символов для управления светодиодом

Прежде чем приступать к обработке чисел, состоящих из нескольких цифр, разработаем скетч для управления светодиодом с помощью одного символа. В частности, при получении символа “1” скетч будет включать светодиод, а символа “0” — выключать его. Для работы этого скетча нужно подключить светодиод к контакту 9 платы Arduino, как показано на рис. 7.10.

C:\Users\Anansi\AppData\Local\Temp\FineReader10\media\image10.jpeg

Рис. 7.10. Подключение светодиода к контакту 9 платы Arduino (Рисунок создан в программе Fritzing)

Как мы разобрались в предыдущем разделе, при отправке одиночного символа проще всего сравнить его с целевым символом в операторе условия if. Каждый помещаемый в буфер символ сравнивается с символом ‘0’ или ‘1’, и в зависимости от того, с каким символом он совпадает, предпринимается соответствующее действие. Создайте новый скетч, скопируйте в него код из листинга 7.4, загрузите его в свою плату Arduino и экспериментируйте, отправляя одиночные символы 0 и 1 из монитора порта.

Листинг 7.4. Программа single_char_control.ino для управления светодиодом с помощью одиночных символов

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Управление светодиодом с помощью одиночных символов
const int LED=9;
char data; // Переменная для хранения получаемого символа
void setup()
{
Serial.begin(9600); // Запускаем последовательный порт
// со скоростью в бодах = 9600
pinMode(LED, OUTPUT);
}
 
void loop()
{
// Предпринимаем действие только при наличии данных в буфере
if (Serial.available() > 0)
{
data = Serial.read(); // Считываем входящий байт данных
// Включаем светодиод
if (data == '1')
{
digitalWrite(LED, HIGH);
Serial.println("LED ON");
}
 
// Выключаем светодиод
else if (data == '0')
{
digitalWrite(LED, LOW);
Serial.println("LED OFF");
}
}
}

Обратите внимание на то, что вместо простого оператора условия if…else в коде присутствует его вариант if…else…if. Поскольку в каждой передаче вместе с символами управления светодиодом монитор порта также отправляет и символ новой строки, важно удалить этот символ из буфера. Считываемый функцией Serial.read() символ новой строки не проходит проверку на превышение символа ‘0’, и в скетче не предпринимается никакого действия по его обработке. Обрабатываются только получаемые символы ‘0’ и ‘1’. Если же в коде использовался бы вариант if…else, то светодиод выключался бы при получении как символа ‘0’, так и символа новой строки ‘\n’. Более того, даже при получении символа ‘1’ скетч сразу же после включения светодиода выключал бы его при получении символа новой строки ‘\n’, следующего за символом ‘1’.

Управление разноцветным светодиодом с помощью списка значений

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

C:\Users\Anansi\AppData\Local\Temp\FineReader10\media\image11.jpeg

Рис. 7.11. Подключение разноцветного светодиода к плате Arduino (Рисунок создан в программе Fritzing)

Для управления разноцветным светодиодом нам нужно посылать плате Arduino три значения в диапазоне 0–100, задающие яркость каждого из составляющих его трех светодиодов. Например, чтобы задать полную яркость для всех трех составляющих светодиодов, нам нужно отправить значения 100,100,100. Решение этой задачи сопряжено с несколькими проблемами:

  • Нам нужно как-то отличить числа от запятых.
  • Последовательность символов нужно преобразовать в целые числа и сопоставить эти числа значениям в диапазоне 0–255 для управления светодиодами, используя функции analogWrite().
  • Нам также нужно учитывать, что мы работаем со светодиодом с общим анодом, и сигналы управления подаются на катоды составляющих его светодиодов. Иными словами, значение 255 выключит светодиод, а значение 0 включит его на полную яркость.
  • Необходимо учитывать возможность, что значения яркости могут состоять из одной, двух или трех цифр.
  • Наша программа должна быть достаточно надежной, способной обрабатывать полученные данные, форматирование которых не соответствует норме, таким образом, чтобы они не искажали данные последующих передач (в разумных пределах).

К счастью среда Arduino IDE содержит очень удобную функцию Serial.parseInt() для определения и извлечения целых чисел. Функция ожидает, пока во входной буфер последовательного порта не поступит нецифровое значение, после чего она преобразовывает все предыдущие цифры в целое число. В нашем случае, первые два значения считываются по обнаружению следующих за ними запятых, а последнее — после обнаружения символа перевода строки.

Чтобы увидеть работу этой функции, загрузите код из листинга 7.5 в свою плату Arduino.

Листинг 7.5. Программа list_control.ino для управления разноцветным светодиодом

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Отправляем одновременно несколько значений
// Данные отправляются в следующем формате: <0-100>,<0-100>,<0,100)>\n
// Здесь числа представляют яркость в процентах красного,
// зеленого и синего светодиодов
// Контакты платы Arduino, к которым подключаются катоды светодиодов
const int RED = 11;
const int GREEN = 10;
const int BLUE = 9;
void setup()
{
Serial.begin(9600); // Запускаем последовательный порт
// со скоростью в бодах = 9600
Serial.setTimeout(10); // Таймаут, в течение которого
// ожидать целое число
// Задаем выходной режим работы для контактов управления светодиодами
pinMode(RED, OUTPUT);
pinMode(GREEN, OUTPUT);
pinMode(BLUE, OUTPUT);
// Выключаем светодиоды
// Поскольку светодиод имеет общий анод, для выключения светодиода
// на катод подается сигнал высокого уровня
digitalWrite(RED, HIGH);
digitalWrite(GREEN, HIGH);
digitalWrite(BLUE, HIGH);
}
void loop()
{
// Считываем данные, когда они присутствуют в буфере
if (Serial.available() > 0)
{
// Ожидаем получить три целых числа по последовательному каналу связи
// Функция parseInt() заблокирует исполнение следующего кода
// до тех пора, пока не будет получено действительное целое число
// Целое число определяется по завершающей запятой
// или символу новой строки
// Недействительные символы удаляются перед найденным целым числом,
// а не после него
int val1 = Serial.parseInt();
int val2 = Serial.parseInt();
int val3 = Serial.parseInt();
// После считывания всех целых чисел, очищаем буфер
while (Serial.available())
{
Serial.read();
}
// Ограничиваем полученные значения диапазоном 0–100%
int val1c = constrain(val1,0,100);
int val2c = constrain(val2,0,100);
int val3c = constrain(val3,0,100);
// Сопоставляем аналоговые значения процентным значениям
int rval = map(val1c,0,100,255,0); // Первое действительное
// целое число
int gval = map(val2c,0,100,255,0); // Второе действительное
// целое число
int bval = map(val3c,0,100,255,0); // Третье действительное
// целое число
// Устанавливаем яркость светодиода
analogWrite(RED, rval);
analogWrite(GREEN, gval);
analogWrite(BLUE, bval);
// Выводим на экран использованные значения яркости
Serial.println("Red: " + String(val1c) + "%");
Serial.println("Green: " + String(val2c) + "%");
Serial.println("Blue: " + String(val3c) + "%\n");
}
}

В функции setup() запускаем последовательный интерфейс, а затем с помощью функции setTimeout() задаем величину таймаута, равную 10 мс для функции parseInt(). Если в течение 10 мс по последовательному каналу не будет получен следующий символ, эта функция считает завершенным извлечение текущего целого числа. Это просто мера предосторожности, чтобы не допустить зависания программы в случае отправки неполного списка значений.

Вспомним, что поскольку наш разноцветный светодиод с общим анодом, мы управляем составляющими светодиодами, подключая их катоды на землю. Таким образом, подача высокого уровня на катод светодиода препятствует протеканию тока через него, т. е. выключает светодиод. Подобным образом необходимо инвертировать значения аргументов функции analogWrite(), т. е. значение 255 выключает светодиод, а значение 0 включает его на полную яркость. Код главного цикла loop() ожидает наличия данных в буфере последовательного порта и извлекает из него первые три целых числа. Если буфер содержит какие-либо дополнительные данные, все они удаляются функцией Serial.read(), оставляя буфер пустым. Затем вызывается функция constrain(), ограничивающая все значения диапазоном 0–100. Далее функция map() сопоставляет 0 процентов значению 255, а 100 процентов значению 0, чтобы обеспечить использование функции analogWrite(). Наконец, устанавливаются уровни яркости составляющих цветных светодиодов, значения которых также дополнительно выводятся в окно монитора порта. Затем цикл ожидает поступления следующего набора команд.

Загрузите эту программу в свою плату Arduino и откройте окно монитора последовательного порта. Наберите в строке ввода монитора порта три числа со значениями в диапазоне 0–100, например, 80,10,80, и нажмите кнопку Отправить. Попробуйте немного поэкспериментировать, устанавливая разные цвета для светодиода.

Взаимодействие с компьютерной программой

Со временем вам надоест взаимодействовать с платой Arduino посредством программы монитора порта. К счастью, это не единственная программа, с помощью которой можно обмениваться информацией между компьютером и платой Arduino: практически любой язык программирования содержит библиотеки, позволяющие работать с последовательным портом компьютера. Из наиболее популярных языков программирования, обладающих обширными, хорошо задокументированными библиотеками последовательной связи, можно назвать, например, Python, Java, C, Node.js и другие. Таким образом, если вы знаете какой-либо из этих языков программирования, то сможете разрабатывать на нем программы для двустороннего обмена данными через последовательный интерфейс между компьютером и платой Arduino.

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

Интерфейс программирования языка Processing достаточно простой, похожий на интерфейс среды Arduino IDE. В этом разделе мы установим язык Processing на компьютер, а затем разработаем на нем простой графический интерфейс для визуального представления данных, получаемых с платы Arduino по последовательному каналу. Разобравшись с работой этой программы, мы создадим другую: графический интерфейс для передачи данных с компьютера на плату Arduino.

Установка Processing

Чтобы разрабатывать программы на языке Processing, нам нужно установить этот язык на свой компьютер. Загрузите установочный архив для своей операционной системы со страницы загрузки Processing (https://processing.org/download/) на свой компьютер. Для установки просто распакуйте загруженный архив в какую-либо папку. Запустите приложение Processing, выполнив двойной щелчок мышью по значку приложения. Должно открыться главное окно среды IDE, которое выглядит, как показано на рис. 7.12.

C:\Users\Anansi\AppData\Local\Temp\FineReader10\media\image12.jpeg

Рис. 7.12. Главное окно среды Processing IDE

Управление приложением Processing посредством платы Arduino

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

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

Листинг 7.6. Программа Arduino arduino_read_pot.ino для передачи данных на компьютер

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Передача значения уровня сигнала с потенциометра на компьютер
const int POT=0; // Сигнал с потенциометра подается
// на аналоговый контакт A0
int val; // Переменная для хранения сопоставленного
// значения сигнала с потенциометра
void setup()
 
{
Serial.begin(9600); // Запускаем последовательную передачу данных
}
 
void loop()
{
val = map(analogRead(POT), 0, 1023, 0, 255); // Считываем сигнал
// с потенциометра и сопоставляем его
// значению в диапазоне 0–255
Serial.println(val); // Отправляем значение на компьютер
delay(50); // Выдерживаем паузу, чтобы
// не переполнить входной буфер
}

А теперь следует интересная часть задачи: создание скетча Processing для приема и обработки данных, передаваемых платой Arduino. Программа, приведенная в листинге 7.7, считывает данные из входного буфера последовательного порта компьютера, и изменяет яркость одного из основных цветов созданного им окна на основе значений, получаемых от Arduino. Создайте новый скетч Processing и скопируйте в него код из листинга 7.7. Затем вам нужно будет внести в скопированный код одну важную корректировку. В частности, скетчу Processing необходимо знать, на каком последовательном порту ожидать поступающие от Arduino данные. Это будет тот же самый порт, к которому подключена ваша плата Arduino. Поэтому в листинге 7.7 замените COM3 номером этого последовательного порта. (В системах под Linux и Mac OS обозначение этого порта будет выглядеть наподобие /dev/ttyUSB0). Чтобы избежать ошибок при ручном вводе, номер последовательного порта можно скопировать из среды Arduino IDE.

Листинг 7.7. Скетч processing_display_color.pde для приема данных по последовательному каналу и изменения цвета окна

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Скетч Processing для приема данных по последовательному каналу
// и изменения цвета окна
// Импортируем и инициализируем библиотеку последовательного порта
import processing.serial.*;
Serial port;
float brightness = 0; // Переменная для хранения значения сигнала
// с потенциометра
 
void setup()
{
size(500,500); // Размер окна
port = new Serial(this, "COM3", 9600); // Инициализируем
// последовательный порт
port.bufferUntil('\n'); // Конфигурируем порт для чтения
// данных до символа новой строки
}
 
void draw()
{
background(0,0,brightness); // Обновляем окно
}
void serialEvent (Serial port)
{
brightness = float(port.readStringUntil('\n')); // Получаем значение
// от Arduino
}

Скопировав код в свой скетч и указав в нем правильный номер последовательного порта, проверьте, что программа монитора порта среды Arduino IDE не открыта. Последовательный порт компьютера может одновременно использоваться только одной программой. Нажмите кнопку Run окна Processing IDE (левая кнопка со значком треугольника в ней, расположенная в верхнем левом углу окна). Откроется небольшое окно (рис. 7.13). Вращайте подключенный к плате Arduino потенциометр; цвет окна должен плавно изменяться с черного на синий.

C:\Users\Anansi\AppData\Local\Temp\FineReader10\media\image13.jpeg

Increasing analog values

Увеличивающиеся аналоговые значения

Рис. 7.13. Пример исполнения скетча Processing для изменения цвета окна

Убедившись, что наш скетч работает должным образом, рассмотрим пошагово его код, чтобы разобраться, как он работает. В отличие от скетчей Arduino, библиотека для работы с последовательным портом не импортируется автоматически в программы на Processing. Эту задачу выполняет оператор import processing.serial.*, а оператор Serial port создает объект последовательной связи, называющийся port.

Подобно скетчам Arduino, программы на Processing содержат функцию setup(), которая исполняется один раз в начале кода. В данном коде Processing эта функция выполняет настройки последовательного порта и посредством команды size(500,500) создает окно размером 500 × 500 пикселов. Оператор port = new Serial(this, “COM3”, 9600) предоставляет Processing всю необходимую информацию для создания экземпляра объекта последовательного порта. При исполнении в этом скетче данный экземпляр порта (называющийся port) будет поддерживать связь через порт COM3 (или через другой конкретный порт) со скоростью 9600 бод. Скорость последовательной передачи данных должна быть одинаковой, как в скетче Arduino, так и в скетче Processing; в противном случае передаваемые символы будут искажаться в процессе приема. Оператор port.bufferUntil(‘\n’) дает указание Processing помещать полученные данные во входной буфер и не предпринимать никаких действий до тех пор, пока не будет получен символ новой строки.

В Processing вместо цикла loop() предусмотрены другие специальные функции, в частности, функции draw() и serialEvent(). Функция draw() Processing похожа на функцию loop() Arduino: она исполняется в бесконечном цикле и обновляет дисплей. Функция background() устанавливает цвет окна, задавая интенсивность составляющих цветов: красного, зеленого и синего (значения которых передаются функции в аргументах). В данном случае выходной сигнал от потенциометра управляет интенсивностью синего цвета, а для красного и зеленого цветов установлены нулевые значения. Можно изменить управляемый потенциометром цвет, просто разместив аргумент brightness в соответствующей позиции списка аргументов. Значения красного, зеленого и синего цветов задаются значениями в диапазоне от 0 до 255, поэтому значения выходного сигнала с потенциометра перед их передачей сопоставляются значениям в этом диапазоне.

Функция serialEvent() вызывается всякий раз, когда выполняется условие bufferUntil(), заданное в функции setup(). Таким условием является получение символа новой строки. Поступающие по последовательному каналу данные считываются в строку оператором port.readStringUntil(‘\n’). Строку можно рассматривать, как массив текстовых символов. Содержимое строки преобразуется в число с плавающей запятой посредством функции float(). Результат этой функции присваивается переменной brightness, в итоге изменяется цвет фона окна приложения.

Чтобы остановить исполнение приложения Processing, нажмите кнопку Stop в окне среды Processing IDE (круглая кнопка со значком квадрата внутри нее, расположенная справа от кнопки Run).

Передача скетчем Processing данных на плату Arduino

Очевидным следующим шагом будет передача данных в обратном направлении: с компьютера на Arduino. Для этого эксперимента нам нужно будет подключить к плате Arduino разноцветный светодиод с общим анодом, как показано на монтажной схеме на рис. 7.11 и загрузить в плату Arduino программу из листинга 7.5. Только теперь вместо отправки плате Arduino трех числовых значений через монитор порта, мы будем отправлять цвет, выбираемый с помощью палитры цветов, встроенной в Processing.

Создайте в Processing новый скетч, скопируйте в него код, приведенный в листинге 7.8, и запустите программу на исполнение. (Не забудьте изменить номер порта в коде на номер порта, используемый вашей платой Arduino, как это нужно было сделать в предыдущем скетче.)

Листинг 7.8. Скетч processing_control_RGB.pde для управления разноцветным светодиодом на Arduino

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import processing.serial.*; // Импортируем библиотеку serial
PImage img; // Объект изображения
Serial port; // Объект последовательного порта
void setup()
{
size(640,256); // Размер изображения в цветовой модели HSV
img = loadImage("hsv.jpg"); // Загружаем фоновое изображение
port = new Serial(this, "COM3", 9600); // Открываем последовательный
// порт
}
void draw()
{
background(0); // Черный фон
image(img,0,0); // Накладываемое изображение
}
void mousePressed()
{
color c = get(mouseX, mouseY); // Получаем цвет RGB в точке,
// где находится курсор мыши
int r = int(map(red(c), 0, 255, 0, 100));
int g = int(map(green(c), 0, 255, 0, 100));
int b = int(map(blue(c), 0, 255, 0, 100));
String colors = r+","+g+","+b+"\n"; // Извлекаем значения из цвета
print(colors); // Выводим значения цветов
// на экран для целей отладки
port.write(colors); // Отправляем значения
// на плату Arduino
}

Скетчи Processing автоматически загружают сопутствующие файлы из папки data, вложенной в папку скетча. Файл hsv.jpg входит в состав кода для этой главы. Загрузите его и поместите в папку data в папке своего скетча. По умолчанию Processing сохраняет скетчи в папке Processing, находящейся в папке Мои документы текущего пользователя, автоматически создавая папку для скетча с таким же самым названием, как и скетч. Структура папок проекта выглядит подобно показанной на рис. 7.14. Изображение в папке data будет использоваться в качестве цветовой палитры.

C:\Users\Anansi\AppData\Local\Temp\FineReader10\media\image14.jpeg

Рис. 7.14. Структура папок проекта Processing

Запустите скетч Processing на исполнение; отроется окно приложения, заполненное палитрой, как на рис. 7.15.

7

Рис. 7.15. Окно программы Processing для выбора цвета

Щелкайте мышью по разным цветам; в консоли окна среды Processing IDE будут выводиться значения составных цветов выбранного цвета, которые передаются на плату Arduino, вызывая соответствующее изменение цвета подключенного к ней разноцветного светодиода.

Убедившись, что все составляющие проекта функционируют должным образом, рассмотрим код скетча Processing более подробно, чтобы разобраться, как он работает. Как и в предыдущем скетче Processing, импортируется библиотека последовательного интерфейса serial и создается объект последовательного порта с названием port. Кроме этого создается объект типа PImage с названием img, который будет содержать фоновое изображение. В функции setup() выполняется инициализация последовательного порта, устанавливается размер окна приложения, равный размеру выводимого в нем изображения, и осуществляется импорт изображения в объект изображения, с помощью оператора img = loadImage(“hsv.jpg”). Операция импорта предполагает, что файл hsv.jpg находится в папке data текущего скетча Processing.

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

При каждом щелчке мышью по изображению палитры в окне приложения вызывается функция mousePressed(). Цвет пиксела, по которому пришелся щелчок, сохраняется в виде объекта типа color с названием c. Сам цвет извлекается методом get(), который предоставляет приложению информацию, откуда получить значения цвета (в данном случае, координаты X и Y щелчка мыши). Затем вызывается функция map(), которая сопоставляет значения составных цветов процентным значениям, ожидаемых скетчем Arduino. Полученные процентные значения далее конкатенируются в строку, которая отправляется по последовательному каналу на плату Arduino. Для справки эти значения также выводятся в консоли главного окна Processing IDE.

Подключите свою плату Arduino (с подсоединенным к ней разноцветным светодиодом) к компьютеру и загрузите в нее код из листинга 7.5. Запустите на исполнение скетч Processing (предварительно исправив в нем номер последовательного порта должным образом), и щелкайте по разным цветам в палитре в открывшемся окне приложения. Выбранный таким образом цвет должен устанавливаться на разноцветном светодиоде, подключенном к плате Arduino.

Резюме

В этой главе мы узнали следующее:

  • Платы Arduino подключаются к компьютеру через преобразователь USB/RS-232.
  • Для преобразования USB/RS-232 в разных платах Arduino может быть предусмотрена специальная микросхема или реализована встроенная поддержка функциональности USB.
  • Плата Arduino может отправлять данные на компьютер через интерфейс USB.
  • Для форматирования текста, выводимого в окне монитора порта, можно использовать символы новой строки и табуляции.
  • Данные передаются по последовательному каналу в виде символов, которые можно преобразовать в целые числа несколькими способами.
  • По последовательному каналу с компьютера в скетч Arduino можно отправлять наборы целых чисел, разделенных запятыми. В скетче Arduino составляющие числа можно извлечь из набора с помощью специальных функций и использовать их в качестве команд для скетча.
  • По последовательному каналу можно отправлять данные из скетча Arduino в программу на Processing, исполняющуюся на компьютере.
  • Из программы на Processing можно отравлять данные в скетч Arduino для управления подключенными к плате Arduino периферийными устройствами.
Опубликовано

Ходовая часть мобильных роботов

По материалам книги “Мобильные роботы на базе Arduino, 3-е изд.“, 3-е изд. (автор Михаил Момот) (глава 5. Ходовая часть)

Мобильные роботы на базе Arduino, 3-е изд.

Выбор двигателей

Итак, когда структура ходовой части выбрана, следует определить, какие двигатели будет использовать наш робот. На выбор: коллекторные двигатели постоянного тока с редуктором, сервопривод MG995 3600 постоянного вращения (скорость вращения задается сигналом на входе управления), шаговые двигатели. Их параметры представлены в табл. 5.1.

Таблица 5.1. Сравнительные параметры различных типов двигателей

Сравниваемые параметры Двигатель
постоянного тока с понижающим редуктором
Сервопривод MG995 3600 Шаговый
двигатель
Количество задействованных портов микроконтроллера для управления 2–3 1 2–3
Скорость реакции на поступившую команду, сек (чем больше, тем хуже) ~1/100 ~1/50 ~1/1000
Скорость вращения (с учетом понижающего редуктора), об/мин 10–240 1–60 0,1–300
Стоимость (за единицу принята стоимость двигателя постоянного тока с редуктором), сравниваются двигатели, равные по мощности 1 4 6
Минимальное количество портов управления ходовой частью для проекта колесного робота с двумя ведущими колесами 4 2 4+1
дополнительный контакт требуется для отключения всех моторов

Что мы здесь видим?

  • Коллекторные моторы с пластиковым редуктором — самые простые и самые дешевые.
  • Сервопривод MG995 3600 — наиболее экономичный по ресурсам контроллера Arduino. При его использовании хорошо регулируется скорость движения робота, если скорость колес нужно менять постоянно и держать в заданных пределах. Скорость оборота колеса, вращаемого сервоприводом, зависит только от установленного на порту управления значения сигнала, и при увеличении сопротивления (когда, например, робот движется в гору) меняется слабо, в то время как двигатель постоянного тока без обратной связи по скорости изменяет скорость своего вращения в зависимости от нагрузки.
  • Шаговый двигатель следует применять там, где нужна точность, и если это необходимо, обратитесь к книге «Мобильные роботы на базе ESP32 в среде Arduino IDE»[1], там как раз используются шаговые моторы в качестве ходовой части. Шаговым двигателем также можно держать постоянную скорость.

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

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

Драйверы двигателей

Платы Arduino, кроме специализированных, не поддерживают возможностей управления двигателями постоянного тока напрямую, и для этого нужно применять специализированные микросхемы, называемые драйверами двигателей. Наиболее распространенные из них приведены в табл. 5.2.

Таблица 5.2. Специализированные микросхемы драйверов двигателей (на платах)

Характеристики TB6612FNG
(рис. 5.6)
MX1508
(рис. 5.7)
L298N
(рис. 5.8)
Максимальный ток на канал, А 1,2 1,2 2
Количество подключаемых коллектроных моторов 2 2 2
Максимальное напряжение питания, В 13,5 10 35
Минимальное напряжение питания, В 2,5 2,5 6
Встроенная диодная защита от паразитных токов Есть Есть Есть

Все три платы драйверов подходят для управления направлением вращения и мощностью коллекторных двигателей постоянного тока. Они также могут применяться и для управления шаговыми двигателями.

Драйверы_2

Рис. 5.6. Плата драйвера TB6612FNG

Драйверы TB6612 и L298N имеют отдельные входы управления мощностью моторов (PWMA–PWMB и ENA–ENB соответственно), но если на эти входы подать высокое логическое напряжение (5 вольт), то управление драйверами TB6612, L298N и MX1508 будет осуществляться схоже по входам AIN1–AIN2 и BIN1–BIN2 — для TB6612, а также IN1–IN2 и IN3–IN4 — для L298N и MX1508. Именно такие режимы работы мы и будем использовать, чтобы наша программа вела себя одинаково и независимо от того, какой драйвер моторов вы решили использовать в своей модели.

5

Рис. 5.7. Плата драйвера MX1508

5

Рис. 5.8. Плата драйвера L298N

 

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

Таблица 5.3. Значения сигналов на входах и соответствующая реакция двигателя

IN1 (AIN1) IN2 (AIN2) Мотор A (направление вращения условное)
0 0 Отключен
0 1 Вал вращается против часовой стрелки
1 0 Вал вращается по часовой стрелке
1 1 Отключен

Таблица 5.3 (окончание)

IN3 (BIN1) IN4 (BIN2) Мотор B (направление вращения условное)
0 0 Отключен
0 1 Вал вращается против часовой стрелки
1 0 Вал вращается по часовой стрелке
1 1 Отключен

 

Сборка макета

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

Управляем двигателем без Arduino

Возьмем драйвер L298N (или MX1508), двигатели постоянного тока, аккумуляторы с боксом, провода, керамические конденсаторы 0,1 мкФ (маркируются числом 104) — они нужны для исключения электромагнитных наводок, которые возникают при работе двигателей и могут стать причиной сбоев в работе электроники робота, и паяльник. При пайке легко испортить поверхность стола, поэтому используйте фанеру или постелите на место работы с паяльником пару листов бумаги.

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

2017-01-28 12-10-46

Рис. 5.9. Двигатель постоянного тока, керамический конденсатор и провода

Обмотаем ножки конденсатора вокруг залуженных кончиков проводов, как показано на рис. 5.10, — теперь ножки конденсатора хорошо держатся на проводе, и их легко можно припаять (рис. 5.11).

2017-01-28 12-13-24 2017-01-28 12-14-34 (2)
Рис. 5.10. Ножки конденсатора обмотаны
вокруг оголенных концов провода
Рис. 5.11. Пайка конденсатора

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

Готовый к работе двигатель показан на рис. 5.13.

2017-01-28 12-16-20

Рис. 5.12. Припаивание проводов к контактам двигателя

5

Рис. 5.13. Двигатель с припаянными проводами и конденсатором

Картинка мотор+аккумул_bb

Рис. 5.14. Схема тестирования двигателя

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

Теперь, когда двигатели готовы, приступим к сборке схемы, изображенной на рис. 5.15. Перемычки на контактах ENA и ENB драйвера не убираем!

Картинка драйвер +мотор_стенд_bb

Рис. 5.15. Схема управления двигателями без контроллера Arduino

Двигатели подключаются к драйверу через винтовые зажимы, также подключается и аккумуляторный бокс, для контактов IN1–IN4 драйвера L298N лучше использовать провода с готовыми клеммами Dupont (см. главу 2), а для MX1508 требуется пайка.

Подключая 5 вольт к различным контактам IN1–IN4, можно проследить, как изменяется вращение двигателей M1 и M2 (используйте данные табл. 5.4).

Подсказка

Контакты IN1 и IN2 отвечают за работу двигателя М1, а IN3 и IN4 — двигателя М2.

Широтно-импульсная модуляция

Проведем наглядный эксперимент: отключим контакт IN1 мотора М1, а контакт IN2 будем руками быстро подключать к напряжению 5 вольт и отключать от него, — мотор станет вращаться с заметными рывками.

Если же делать эту операцию с высокой скоростью — такой, на которую способен контроллер, то вал мотора будет вращаться без рывков, но мощность на валу окажется меньше, чем при постоянно включенном IN2. То есть передаваемая на вал мощность зависит от частоты переподключения и длительности включения, и если время включения мало, а время отключения относительно времени включения
велико, то и выдаваемая мощность будет мала, и наоборот. Этот подход к регулированию мощности вращения двигателей постоянного тока называется широтно-импульсной модуляцией.

Мы в наших проектах будем использовать шкалу мощности от 0 до 255, где число — это относительная длительность импульса включения мотора за период (рис. 5.16). А чтобы иметь возможность изменять направление вращения, задействуем и отрицательную шкалу: –255…0…255, при этом изменяя вход драйвера, на который подается сигнал (при положительном вращении — на IN1, при отрицательном — на IN2 для мотора М1 и IN3/IN4 — для мотора М2).

рис 5_14

Рис. 5.16. Принцип работы ШИМ

Подключаем контроллер Arduino

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

Добавим к собранной схеме контроллер Arduino и научим его управлять двига-
телями. Дополнительно нам потребуется несколько проводов с клеммами Dupont.

l298nMP

Рис. 5.17. Схема управления двигателями для драйверов L298N и MX1508

На рис. 5.17 показана схема подключения драйверов L298N и MX1508, на рис. 5.18 и 5.19 — схема подключения TB6612FNG (она несколько сложнее).

Дело в том, что подключение драйвера TB6612FNG потребует немного больше соединений, т. к. нужно подать логическую единицу (5 вольт от Arduino) на контакты VCC, PWMA, PWMB и STBY, в остальном же схема схожа с подключением L298N и MX1508. Чтобы исключить на рисунках пересечение соединений и неверную их трактовку, схема подключения драйвера TB6612FNG разбита на две части: подключение силовой части (рис. 5.18) и логической части (рис. 5.19).

Удобно при этом задействовать дополнительные платы, которые упрощают реализацию соединений, — например Arduino Sensor shield V5.0 (для Arduino Uno) или NANO V3.0 Shield (для Arduino Nano).

Далее приведены примеры управления вращением двигателей при помощи программы, работающей на контроллерах Arduino UNO, Arduino Nano и Arduino Pro Mini.

23_tb6612fngPower_МП

Рис. 5.18. Схема управления двигателями для драйвера TB6612FNG (силовая часть)

23_tb6612fngCom_МП

Рис. 5.19. Схема управления двигателями для драйвера TB6612FNG (логическая часть)

Подключаем библиотеку mobrob3.zip и пишем тестовую программу

Специально для этого издания книги ее автор разработал библиотеку mobrob3.zip.
В ней, в частности, содержится модуль управления мощностью пары моторов
(см. далее).


Электронный архив

Электронный архив, включающий библиотеку mobrob3.zip, можно скачать с сервера издательства «БХВ» по ссылке https://zip.bhv.ru/9785977517034.zip или со страницы книги на сайте https://bhv.ru/ (см. приложение).


Перед использованием библиотеку mobrob3.zip нужно добавить в Arduino IDE стандартным способом: Скетч | Подключить библиотеку | Добавить .ZIP библиотеку, и выбрать с диска файл mobrob3.zip. В результате все модули и примеры из этой книги (по главам) станут доступны в Arduino IDE. Для управления моторами в библиотеку включен модуль mobrob3xmotor.h, в котором учтены некоторые особенности управления моторами для генерации ШИМ, заключающиеся в том, что в качестве левых моторов будущего робота можно использовать только контакты GPIO 2 и 3 (менять их местами можно), а для правого мотора — только 4 и 5 (их также можно менять местами, чтобы добиться вращения в нужную сторону). Подобное ограничение связано с тем, что генерация ШИМ возможна только на контактах 3 и 5 GPIO.

Логика работы тестовой программы управления двигателями (листинг 5.1) следующая.

В бесконечном цикле:

  1. Отключаем оба двигателя.
  2. Ждем 1 секунду.
  3. Подключаем двигатель 1 на полную мощность по часовой стрелке.
  4. Ждем 1 секунду.
  5. Отключаем двигатель 1.
  6. Ждем 1 секунду.
  7. Подключаем двигатель 1 на полную мощность против часовой стрелки.
  8. Ждем 1 секунду.
  9. Отключаем двигатель 1.
  10. Возвращаемся на пункт 1.

Самостоятельно измените эту программу для управления двигателем 2.

Листинг 5.1. Тестовая программа управления двигателями

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Контакты управления левого мотора только 2 и 3.
# define GPIO_1F 2
# define GPIO_1B 3
// Контакты управления правого мотора только 4 и 5.
# define GPIO_2F 4
# define GPIO_2B 5
 
// Подключаем библиотеку управления моторами.
#include < mobrob3xmotor.h>
void setup()
{
// Вызываем функцию инициализации моторов.
motor_setup();
}
 
// Основная программа.
void loop()
{
motors_power(0, 0); // Отключены оба.
delay(1000);
motors_power(255, 0); // Включен мотор 1 +
delay(1000);
motors_power(0, 0); // Отключены оба.
delay(1000);
motors_power(-255, 0); // Включен мотор 1 -
delay(1000);
// Проделайте те же операции для двигателя 2 самостоятельно.
// ....
}

Добавляем регулирование на основе ШИМ

Рассмотренная и подключенная в предыдущем разделе библиотека mobrob3.zip уже содержит модуль mobrob3xmotor.h, который может регулировать мощность моторов при помощи ШИМ.

Напомню, что значения ШИМ в Arduino могут изменяться от 0 до 255. На практике вал двигателя не сразу начнет вращаться — сначала (при малых значениях) двигатель станет гудеть и лишь по достижении определенного значения ШИМ начнет медленно увеличивать обороты, что связано с недостатком мощности для компенсации сил трения.

Тестовая программа управления двигателями с регуляцией на основе ШИМ

Логика работы этой тестовой программы (листинг 5.2) следующая.

В бесконечном цикле:

  1. Отключаем оба двигателя.
  2. Ждем 1 секунду.
  3. Далее в цикле через 100 миллисекунд увеличиваем мощность от 70 до 255 для обоих моторов.
  4. Далее в цикле через 100 миллисекунд уменьшаем мощность от 255 до 70 для обоих моторов.
  5. Возвращаемся к пункту 1.

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

Самостоятельно измените программу так, чтобы моторы разгонялись в разные стороны.

Листинг 5.2. Тестовая программа управления двигателями с регуляцией на основе ШИМ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Зададим номера контактов управления моторами.
// Контакты управления левого мотора только 2 и 3.
# define GPIO_1F 2
# define GPIO_1B 3
// Контакты управления правого мотора только 4 и 5.
# define GPIO_2F 4
# define GPIO_2B 5
// Подключаем библиотеку управления моторами.
#include <mobrob3xmotor.h>
void setup()
{
// Вызываем функцию инициализации моторов.
motor_setup();
}
//=================================================================
// Основная программа.
void loop()
{
motors_power(0, 0);
delay(500);
for (int i = 70; i < 255; i++)
{
motors_power(i, i);
delay(100);
}
for (int i = 255; i > 70; i--)
{
motors_power(i, i);
delay(100);
}
}

Выводы

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

Для закрепления теоретических знаний собраны и опробованы на практике различные схемы управления двигателями постоянного тока. Следующий шаг — сборка базовой модели робота.

  1. См. https://bhv.ru/product/mobilnye-roboty-na-baze-esp32-v-srede-arduino-ide/.
Опубликовано

Воспроизведение звуковых файлов на Arduino

По материалам книги С. Монка «Мейкерство. Arduino и Raspberry Pi» (глава 15 «Звук»)

Мейкерство. Arduino и Raspberry Pi

Эксперимент: громкоговоритель без усилителя на Arduino

Для воспроизведения более или менее сложного звука понадобится какой-нибудь громкоговоритель. Громкоговорители, появившиеся почти сто лет назад, в большинстве своем работают на основе соленоида (см. разд. «Соленоиды» главы 7) — содержащаяся в них электромагнитная катушка с достаточно высокой частотой колеблет жесткий конус, создающий звуковые волны.

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

Обычно на громкоговорителях встречаются значения 4 или 8 ом. И если вы собираетесь подключить 8-омный громкоговоритель к выходу Arduino с напряжением 5 В, то с полным основанием можете ожидать возникновения тока, равного по силе:

I = V / R = 5 / 8 = 625 мА

То есть, тока намного большего, чем те 40 мА, которые рекомендуются для выходного контакта Arduino. Похоже, нам и здесь понадобится согласующий резистор!

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


Звуковые частоты

Частота звуковой волны в музыкальных терминах называется тоном и выражается в количестве звуковых волн, достигающих ваших ушей за одну секунду. Мне нравится представлять звуковые волны в виде ряби на пруду. Звук высокой частоты — скажем, 10 кГц (килогерц), создает 10 000 звуковых волн в секунду, а звук низкой частоты (скажем, 100 Гц) — всего 100 звуковых волн в секунду. Человек обычно слышит звуки в диапазоне от 20 Гц до 20 кГц, но верхняя граница с возрастом снижается. Звуки выше 20 кГц обычно называют ультразвуком.

У животных другие диапазоны слышимых звуков. Например, кошки могут слышать частоты вплоть до 55–79 кГц, а летучие мыши известны тем, что вообще пользуются ультразвуковой эхолокацией.

В музыке самая низкая нота «До» на обычном пианино соответствует частоте 32,7 Гц, а самая высокая — частоте 4186 Гц. Известное всем, имеющим хоть какое-то касательство к музыке, понятие октава означает удваивание частоты. Поэтому, если заставить на клавиатуре пианино зазвучать две ноты «До» смежных октав, то вторая нота будет звучать с удвоенной частотой первой ноты.


Комплектующие

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

Таблица 15.1. Комплектующие для работы с Arduino в эксперименте с громкоговорителем

Компонент схемы Источники
Небольшой громкоговоритель с импедансом 8 Ом Adafruit: 1891
Резистор 270 Ом 0,25 Вт Mouser: 291-270-RC
400-точечная беспаечная макетная плата Adafruit: 64
Перемычки «папа-папа» Adafruit: 758

Громкоговоритель я извлек из старого радиоприемника, и у него оказался разъем, в который можно было вставить штекерные наконечники перемычек. Вам может достаться громкоговоритель с припаянными к нему проводами, которые удастся воткнуть в разъемы Arduino, или же вам придется подпаять к этим проводам выводы, толщина которых позволит воткнуть их в гнезда монтажной платы.

Макетная схема эксперимента

Схема, собранная для проведения эксперимента на макетной плате, показана на рис. 15.1.

mkac_1501

Рис. 15.1. Схема эксперимента с громкоговорителем на Arduino в сборе

Один провод от громкоговорителя подключается к контакту GND (заземление) Arduino, а второй — к контакту D11 через резистор.

Программа для Arduino

Скетч Arduino для этого эксперимента находится в каталоге /arduino/experiments/ex_speaker (см. разд. «Код к книге» главы 2):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const int soundPin = 11;
void setup() {
pinMode(soundPin, OUTPUT);
Serial.begin(9600);
Serial.println("Введите частоту ");
}
 
void loop() {
if (Serial.available()) {
int f = Serial.parseInt();
tone(soundPin, f); //<strong> 1</strong>
delay(2000);
noTone(soundPin); //<strong> 2</strong>
}
}

Незнакомая вам часть кода может находиться внутри цикла loop():

  1. tone настраивает один из выходных контактов Arduino на воспроизведение звука с указанной частотой (в данном случае, с частотой, набранной в окне монитора порта).
  2. После двухсекундной задержки команда noTone отменяет воспроизведение звука, возвращая умиротворяющую тишину.

Загружаем и выполняем программу

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

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

В случае, если вы попытаетесь получить в этом эксперименте частоты на границах слышимости, вас, к сожалению, будет ожидать разочарование, поскольку у громкоговорителя, скорее всего, окажется свой, еще более узкий диапазон звучания, и на частотах, превышающих примерно 10 кГц, громкость звука резко пойдет на спад. Небольшие громкоговорители также обычно не способны нормально воспроизводить звуки с частотой, ниже 100 Гц.


Синусоида и меандр

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

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


Звуковые волны в виде меандра и синусоиды

Рис. 15.2. Звуковые волны в виде меандра и в виде синусоиды

Усилители

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

В конструкции, собранной для эксперимента из разд. «Эксперимент: громкоговоритель без усилителя на Arduino», мощность звука, излучаемого громкоговорителем, можно существенно усилить, используя транзистор, как это делалось для включения и выключения реле или электродвигателя, — звук останется столь же неприятным, но станет значительно громче.

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

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

Такой подход мы рассмотрим позже, когда кукла Пепе (из разд. «Проект: танцующая кукла Пепе на Raspberry Pi» главы 9) станет обретать голос (см. разд. «Проект: кукла Пепе обретает голос» далее в этой главе).

Эксперимент: воспроизведение звуковых файлов на Arduino

Звуковые файлы формата WAV можно проигрывать на Arduino с помощью оборудования из разд. «Эксперимент: громкоговоритель без усилителя на Arduino» и библиотеки Arduino под названием PCM (от англ. pulse code modulation, импульсно-кодовая модуляция). Для генерирования колебаний, способных вызвать звук, в этой библиотеке используется технология, слегка похожая на технологию широтно-импульсной модуляции (ШИМ). Флэш-памяти Arduino хватает приблизительно на 4 секунды записи, а если требуется проигрывать более длинные звуковые клипы, надо добавить к Arduino кардридер SD и следовать рекомендациям, приведенным на веб-сайте Arduino по адресу: https://www.arduino.cc/en/Tutorial/SimpleAudioPlayer.

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

Исходная статья с описанием такого подхода к воспроизведению звука опубликована на веб-сайте High-Low Tech (http://highlowtech.org/?p=1963). Наш эксперимент немного отличается от этого описания тем, что в нем для записи аудиоклипа используется свободно распространяемое приложение Audacity.

Оборудование и софт

В этом эксперименте используется то же самое оборудование, что и в разд. «Эксперимент: громкоговоритель без усилителя на Arduino». Но вам придется установить на компьютер следующие программы, позволяющие записывать и обрабатывать звуковые клипы:

  • приложение Audacity: audacityteam.org;
  • утилиту Audio Encoder — ссылку на версию под вашу операционную систему можно найти на веб-сайте highlowtech.org/?p=1963.

Создание звукового файла


Примечание

Если записывать свой собственный звуковой клип желания у вас нет, вы можете сразу перейти к разд. «Загружаем и выполняем программу», входящему в состав этого эксперимента, и запустить программу wav_arduino, содержащую в закодированном виде небольшое звуковой блок.


Чтобы создать звуковой файл, запустите программу Audacity, переключите режим записи в моно (Mono) и выберите частоту проекта (Project Rate) равной 8000 Гц. Эти варианты настройки выделены на рис. 15.3.

mkac_1503

Рис. 15.3. Программа Audacity: запись звукового клипа

Чтобы начать запись, щелкните на красной кнопке Record (Записать) и запишите свое сообщение. Учтите, что запись не должна быть длиннее 4 секунд. Как только запись будет сделана, в окне Audacity появится изображение звуковой волны. Можно выбрать любую «немую» область в начале или в конце записи и удалить ее, оставив только нужную часть звукового блока.

Записанный звук надо экспортировать в нужный формат. Для этого опять понадобится выполнить специальные настройки. Выберите в меню File (Файл) пункт Export (Экспорт аудио), затем в раскрывающемся меню выберите вариант Other uncompressed files (Прочие несжатые файлы), перейдите в область настроек Format options (Настройки формата) и укажите характеристики WAV (Microsoft) и Unsigned 8 bit PCM (рис. 15.4). Присвойте файлу имя и пропустите страницу, приглашающую ввести данные об исполнителе.

Только что сгенерированный файл представляет собой двоичные данные. Его нужно преобразовать в список чисел в текстовом виде, отделенных друг от друга запятыми, чтобы его можно было вставить в нашу программу. Запустите для этого утилиту Audio Encoder, загруженную с веб-сайта highlowtech.org. Она выведет на экран приглашение на выбор конвертируемого файла — выберите файл, который только что был экспортирован из Audacity.

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

Откройте программу /arduino/experiments/wav_arduino и замените всю строку, начинающуюся с последовательности 125, 119, 115, данными из буфера обмена. Это очень длинная строка, поэтому лучше всего выбрать ее, поместив курсор в начало строки, а затем, удерживая нажатой клавишу <Shift>, опустить курсор вниз и переместить его на последний символ строки. Для замены выбранного текста данными из буфера обмена, воспользуйтесь пунктом контекстного меню Insert (Вставить).

mkac_1504

Рис. 15.4. Программа Audacity: выбор параметров экспорта

Если нанести все вставленные числа на график, его форма будет напоминать ту, что вы уже видели в Audacity при записи звукового клипа.

Программа для Arduino

Перед тем как приступить к компиляции и запуску программы, нужно установить библиотеку PCM. Загрузите ZIP-архив из GitHub (https://github.com/damellis/PCM/zipball/master), распакуйте его, переименуйте папку в PCM и переместите ее в каталог своих библиотек Arduino, воспользовавшись рекомендациями, представленными во врезке «Установка библиотек Arduino» главы 12.

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

1
2
3
4
5
6
7
8
9
#include &lt;PCM.h&gt;
const unsigned char sample[] PROGMEM = { //<strong> 1</strong>
125, 119, 115, 115, 112, 116, 114, 113, 124, 126, 136, 145, 139,
};
void setup() {
startPlayback(sample, sizeof(sample)); //<strong> 2</strong>
}
void loop() { //<strong> 3</strong>
}

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

  1. Данные хранятся в массиве типа char, состоящем из восьмиразрядных чисел без знака. Команда PROGMEM обеспечивает хранение данных во флэш-памяти Arduino (в ней должно быть доступно около 32 Кбайт).
  2. Проигрывание звукового сэмпла осуществляет библиотека PCM. Функции startPlayback передается массив проигрываемых данных и размер данных в байтах.
  3. Звуковой клип проигрывается однократно при каждом перезапуске Arduino, поэтому функция loop() остается пустой.

Загружаем и выполняем программу

Загрузите программу в свой Arduino, и как только загрузка завершится, звуковой клип будет воспроизведен!

Сразу же после загрузки программы в нижней части Arduino IDE появится сообщение о том, сколько флэш-памяти Arduino было задействовано, примерно следующего содержания: Binary sketch size: 11,596 bytes (of a 32,256 byte maximum) (Размер двоичной программы: 11596 байтов (из максимально возможных 32256 байтов)). Если звуковой файл окажется слишком большим, вы получите сообщение об ошибке.

Подключение Arduino к усилителю

Как ни удивительно, но только что проведенный эксперимент оказался успешным, хотя его удалось проделать с применением только лишь Arduino.

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

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


Делитель напряжения

Делителем напряжения называется устройство, в котором для уменьшения напряжения используются два резистора. По сравнению с напряжением, изменяющимся, скажем, пропорционально изменению звукового сигнала, делитель напряжения изменяет его в фиксированной пропорции.

Например, на рис. 15.5 показан делитель напряжения, используемый для снижения сигнала напряжением 5 В от Arduino до более подходящего в качестве входа для усилителя звука значения в 0,5 В (или около того).


Напряжение в точке соединения двух резисторов (Vout) вычисляется по формуле:

В данном случае, когда на цифровом выходе выставлен высокий уровень сигнала (high) и Vin = 5 В, то:

Vout = 5 × 1/(1+10)=0,45 В.

Делитель напряжения

Рис. 15.5. Делитель напряжения

Макетную плату из разд. «Эксперимент: громкоговоритель без усилителя на Arduino» можно приспособить под подключение к усилителю, поместив один резистор над другим, как показано на рис. 15.6, и подключив верхний резистор номиналом 10 кОм к выводу D11, а нижний вывод нижнего резистора — к выводу GND (заземление). После этого останется только выбрать способ подключения контактной линии GND и линии, на которой встретились резисторы, к усилителю.

mkac_1506

Рис. 15.6. Схема подключения Arduino ко входу усилителя звукового сигнала в сборе

Для этого можно пожертвовать входным кабелем усилителя, укоротив его наполовину и зачистив провода на конце, чтобы их можно было вставить в гнезда монтажной платы. Обычно такой кабель имеет три жилы, поскольку большинство звуковых систем рассчитано на работу со стереосигналом. То есть, в нем имеется один общий (GND) провод и два отдельных провода: для левого и правого звуковых каналов (они обычно имеют изоляцию красного и белого цвета).

Вам нужно найти только общий (GND) провод, поскольку провода для левого и правого каналов лучше соединить вместе, чтобы монофонический сигнал от Arduino был слышен в обоих колонках. Найти общий (GND) провод можно с помощью мультиметра, установленного в режим проверки наличия контакта (рис. 15.7).

mkac_1507

Рис. 15.7. Тестирование входного кабеля звукового сигнала

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

Повторите теперь один из ранее проводившихся в этой главе экспериментов с Arduino, и получите намного более громкий и чистый звук.

Опубликовано

Прерывания и другие специальные функции

По материалам книги Джереми Блума “Изучаем Arduino: инструменты и методы технического волшебства. 2-е изд.”  (глава 13. Прерывания и другие специальные функции)

Изучаем Arduino: инструменты и методы технического волшебства. 2-е изд.

Исходный код и прочие электронные ресурсы:

Исходный код, видеоуроки и прочие электронные ресурсы для этой главы можно загрузить с веб-страницы https://www.exploringarduino.com/content2/ch13.

Исходный код для проектов этой главы можно также загрузить на вкладке Downloads веб-страницы издательства Wiley для этой книги: https://www.wiley.com/go/exploringarduino2e.

Что вы узнаете из этой главы

Все разрабатываемые нами до сих пор программы для Arduino были синхронного типа.

Это обстоятельство вызывает несколько проблем, одна из которых состоит в том, что пока исполняется функция delay(), не могут выполняться никакие другие операции. При этом плата Arduino способна решать несколько задач одновременно, поэтому при простое процессора теряются ценные процессорные циклы.

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

Аппаратные прерывания

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

Но, предположим, что нам нужно исполнить в цикле loop() довольно длительную процедуру. Например, нам нужно медленно постепенно повышать яркость светодиода или скорость вращения электродвигателя, используя для этого цикл for() и несколько операторов задержки delay(). Если управлять цветом или скоростью повышения яркости посредством кнопки, то мы пропустим нажатия кнопки, происходящие в течение исполнения функции delay(). Обычно, человеческая реакция достаточно инерционна, что позволяет выполнить большое количество функций в цикле loop() программы Arduino, включая опрос кнопки при каждой его итерации, не пропуская нажатий кнопки. Но когда цикл loop() содержит медленно исполняющиеся компоненты, существует вероятность пропустить события внешнего ввода.

Для решения подобных проблем и предназначены прерывания. Определенные контакты платы Arduino могут вызывать внешние аппаратные прерывания. Аппаратура микроконтроллера знает состояние этих контактов и может асинхронно предоставлять их значение коду прикладной программы. Таким образом, при обнаружении внешнего аппаратного прерывания исполнение главной программы можно приостановить, чтобы выполнить соответствующую процедуру обработки прерывания. Это прерывание может произойти в любой точке исполнения программы. На рис. 13.1 показан алгоритм процесса прерывания.

Рис. 13.1. Влияние внешнего прерывания на ход исполнения главной программы

Опрос состояния и прерывания: преимущества и недостатки каждого подхода

Аппаратные прерывания можно использовать как альтернативу постоянному опросу внешнего входных контактов в цикле loop(). Нельзя сказать, что один из этих подходов лучше другого, поскольку каждый из них обладает своими достоинствами и недостатками. При разработке системы необходимо рассмотреть все доступные варианты и выбрать из них тот, который лучше всего подходит для данного приложения. В этом разделе рассматриваются основные различия между опросом входных контактов и использованием прерываний, чтобы вы могли сами решить, какой из этих методов лучше всего подходит для вашего конкретного приложения.

Программная реализация

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

Аппаратная реализация

С точки зрения аппаратной реализации для большинства цифровых контактов нет разницы между опросом контакта в цикле и прерыванием, т. к. в обоих случаях просто считывается состояние входа. Но в случае прерываний, активируемых перепадом уровней сигнала, аппаратную составляющую необходимо должным образом модифицировать. Как рассматривалось в главе 2, при нажатии многих кнопок (которые часто используются для активирования входа) их контакты испытывают дребезг. Это представляет серьезную проблему, поскольку дребезг будет много раз активировать процедуру обработки прерывания, тогда как мы хотим вызвать ее только один раз. Еще хуже то, что здесь не подойдет созданная нами ранее программная функция защиты от дребезга, поскольку в процедуре обработки прерывания недопустима функция delay(). Поэтому если для входного сигнала с дребезгом требуется аппаратное прерывание, то сначала нужно аппаратно устранить дребезг. Но в случае входного сигнала свободного от дребезга (например, с поворотного кодера или с другой микросхемы) аппаратное прерывание не будет отличаться от опроса.

Многозадачность

Одна из основных причин для использования прерывания — возможность получения псевдо-многозадачности. Плата Arduino не способна поддерживать настоящую многозадачность, поскольку она оснащена только одним главным микроконтроллером, которым может исполнять только одну команду за раз. Но одну задачу можно кратковременно прерывать для перехода к другой, возвращаясь после нее к первой, и благодаря очень высокой скорости исполнения команд создается видимость одновременного выполнения этих задач, что и называется псевдо-многозадачностью. Например, используя прерывания, можно в процессе уменьшения яркости светодиода реагировать на нажатие кнопки, сигнал с которой управляет скоростью изменения яркости. При методе опроса получить значение состояния контакта внешнего входа можно только тогда, когда процесс исполнения кода в цикле loop() дойдет до функции digitalRead(). Это означает, что наличие в программе каких-либо медленных функций может создать трудности с эффективным мониторингом состояния входного сигнала.

Точность сбора данных

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

Но при этом нам необходимо не пропустить ни одного импульса от кодера. Эти импульсы довольно короткие (намного короче, чем импульсы, создаваемые нажатием кнопки), и если отслеживать их методом опроса в цикле loop(), то некоторые из них можно пропустить. Если поворотный кодер выдает только один импульс за полный поворот вала, то пропустив один такой импульс, программа будет считать, что двигатель вращается вдвое медленнее, чем в действительности. Таким образом, чтобы обеспечить захват важных событий, подобных рассмотренному, без аппаратных прерываний не обойтись. Но в случае сигналов с медленно изменяющимся состоянием (например, выходной сигнал кнопки) метод опроса может оказаться достаточным.

Возможности аппаратных прерываний Arduino

Для большинства плат Arduino сигналы прерываний можно подавать только на определенные контакты. Информацию, какие именно контакты вашей платы Arduino доступны для подачи на них сигналов прерываний, можно найти в документации по прерываниям на веб-сайте Arduino (blum.fyi/arduino-attach-interrupt).

Настройка контакта платы Arduino для работы в режиме источника аппаратных прерываний осуществляется с помощью функции attachInterrupt(). В первом аргументе функции указывается ID-номер прерывания, который не обязательно такой же самый, как и номер контакта. Определить, какой ID-номер прерывания можно присвоить конкретному контакту платы Arduino, можно с помощью функции digitalPinToInterrupt(). Для этого данной функции нужно просто передать номер цифрового контакта платы, и она сама определит соответствующий ID-номер прерывания в зависимости от версии платы Arduino, для которой компилируются скетч. Например, если мы хотим присвоить сигнал прерывания с кнопки контакту 2 платы Arduino Uno (или эквивалентной плате-клону с микроконтроллером ATmega328P), то в качестве первого аргумента функции attachInterrupt() нужно указать функцию digitalPinToInterrupt(2). Непосредственное указание одной функции в качестве аргумента другой функции вполне приемлемо[1], и, кроме того, делает код более аккуратным.

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

1
2
3
4
void toggleLed()
{
var = !var;
}

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

В последнем, третьем, аргументе функции attachInterrupt() указывается режим запуска прерывания. Прерывания Arduino могут запускаться низким уровнем сигнала (LOW) на контакте прерывания, изменением уровня сигнала (CHANGE), положительным перепадом уровня сигнала (RISING) или отрицательным перепадом уровня сигнала (FALLING). Некоторые платы Arduino также поддерживают запуск прерываний наличием сигнала высокого уровня (HIGH) на контакте прерывания. Наиболее часто используются режимы запуска CHANGE, RISING, and FALLING, поскольку они обеспечивают одноразовый запуск прерывания при изменении состояния внешнего входного сигнала, например, при смене уровня сигнала кнопки с низкого (LOW) на высокий (HIGH). Смена уровня сигнала с низкого (LOW) на высокий (HIGH) является положительным (RISING) перепадом, а с высокого (HIGH) на низкий (LOW) — отрицательным (FALLING). Режимы запуска LOW и HIGH менее распространены, поскольку в таких случаях прерывание будет запускаться постоянно, пока сохраняется данный уровень сигнала, блокируя исполнение остального кода программы.

Объединив все эти возможности, можно, например, при каждом нажатии кнопки, подключенной к контакту 2 платы Arduino Uno (т. е. изменении уровня ее выходного сигнала с низкого на высокий), вызывать рассмотренную ранее функцию toggleLED(). Для этого в секцию setup() нужно вставить следующую строку кода:

1
attachInterrupt(digitalPinToInterrupt(2), toggleLED, RISING);

Схема запуска прерывания кнопкой, оснащенной аппаратной защитой от дребезга

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

Схема аппаратной защиты от дребезга

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

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

Рис. 13.2. Дребезг выходного сигнала нажатой кнопки

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

Такую цепочку можно создать, подключив выход кнопки к входному контакту микроконтроллера через резистор, а входной контакт микроконтроллера на землю — через конденсатор. При отпущенной кнопке конденсатор заряжается до напряжения питания (+5 В) через повышающий резистор и добавленный резистор RC-цепи. Конденсатор можно рассматривать, как накопитель электроэнергии, заряжающийся от шины положительного питания. Пока кнопка отпущена, этот накопитель заполняется. А при нажатии кнопки, т. е. при замыкании ее контактов, конденсатор начинает разряжаться на землю через резистор RC-цепи и замкнутые контакты кнопки.

Разряд конденсатора происходит не моментально, а занимает некоторый период времени, в течение которого на входном контакте микроконтроллера напряжение сохраняется на уровне близком к 5 В. Конденсатор перезаряжается при каждом отпускании кнопки, т. е. при размыкании ее контактов. Длительность времени разряда конденсатора превышает период дребезга контактов кнопки, в результате чего к моменту полного разряда конденсатора контакты кнопки успокоятся в стабильном замкнутом состоянии. В результате уровень сигнала на входном контакте микроконтроллера изменяется с высокого к низкому только один раз. Продолжительность периодов заряда и разряда конденсатора, и соответственно, длительность защиты от дребезга, определяется номиналами резистора и конденсатора Рассмотренная схема защиты от дребезга контактов показана на рис. 13.3.

Рис. 13.3. Схема защиты от дребезга с RC-цепочкой (Рисунок создан в программе EAGLE)

Подключенный последовательно кнопке резистор R2 в схеме на рис. 13.3 предотвращает почти моментальный разряд конденсатора. В результате выходной сигнал при разряде конденсатора выглядит так, как показано на осциллограмме на рис. 13.4.

Рис. 13.4. Устранение последствий дребезга контактов с помощью RC-цепочки

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

Большинство современных микроконтроллеров обладают встроенной способностью работать с медленными положительными или отрицательными перепадами цифровых сигналов. Например, в справочном листке для микроконтроллера ATmega приводятся разные пороговые значения для низкого и высокого уровней входного сигнала. При переходе входного сигнала с высокого уровня на низкий, его напряжение должно опуститься ниже 0,2U+, чтобы уровень определился, как низкий. А при переходе входного сигнала с низкого уровня на высокий, его напряжение должно подняться выше ниже 0,7U+, чтобы уровень определился, как высокий. Здесь U+ обозначает напряжение питания микроконтроллера (5 В в моем случае). Этот промежуток обеспечивает устойчивость сигнала в процессе перехода и называется гистерезисом.

Прерывания платформы Arduino могут запускаться таким медленным перепадом уровня входного сигнала. Но желательно разобраться, каким именно образом реализуется эта возможность. Для этого мы создадим схему на триггере Шмитта, чтобы она работала независимо от Arduino. Триггер Шмитта представляет собой схему, которая может создавать резкий перепад выходного сигнала, когда входной сигнал превышает определенное пороговое значение. Полученный таким образом сигнал можно подавать на входной контакт платы Arduino. Для нашего эксперимента мы возьмем микросхему 74AHCT14 триггера Шмитта. Хотя данная микросхема содержит шесть элементов триггера Шмитта, нам понадобится только один из них. Многие производители выпускают логические микросхемы, которые функционально идентичны серии 7400. На рис. 13.5 показана цоколевка шестиэлементной микросхемы инвертирующих триггеров Шмитта производства компании STMicroelectronics.

Рис. 13.5. Цоколевка шестиэлементной микросхемы инвертирующих триггеров Шмитта (Источник: © STMicroelectronics. Репродукция с разрешения)

Мы пропустим выходной сигнал нашей схемы защиты от дребезга через один из таких триггеров Шмитта, прежде чем подавать его на входной контакт прерывания платы Arduino. Принципиальная схема такого устройства изображена на рис. 13.6.

Рис. 13.6. Схема аппаратной защиты от дребезга с обработкой выходного сигнала инвертирующим триггером Шмитта (Рисунок создан в программе EAGLE)

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

 

 

Рис. 13.7. Сигнал на выходе схемы подавления дребезга с инвертирующим триггером Шмитта

Такой сигнал вполне подходит для формирования аппаратного прерывания.

Монтаж схемы устранения дребезга

Теперь у нас есть достаточно знаний, чтобы собрать схему устранения дребезга кнопки. Для последующих экспериментов по тестированию аппаратной защиты от дребезга и кода обработки прерываний нам нужно будет собрать схему с RGB-светодиодом и кнопкой. Монтажная схема показана на рис. 13.8.

Рис. 13.8. Монтажная схема аппаратной защиты от дребезга (Рисунок создан в программе Fritzing)

Разработка программы

Следующая задача — создать простую программу для тестирования как нашей схемы защиты от дребезга, так и возможностей аппаратных прерываний платформы Arduino. Самым очевидным и полезным применением аппаратных прерываний с платформой Arduino будет получение возможности мониторинга состояния входных сигналов даже при выполнении операций заданной длительности, в которых используется функция delay(). Такая возможность может потребоваться во многих ситуациях, но мы рассмотрим одну из наиболее простых: постепенное изменение яркости светодиода методом широтно-импульсной модуляции посредством функции analogWrite(). В этом скетче яркость одного из элементов RGB-светодиода будет циклически повышаться до максимального значения 255, а затем понижаться до минимального значения 0. При нажатии кнопки будет меняться элемент RGB-светодиода с регулируемой таким образом яркостью. Решить эту задачу методом опроса невозможно, поскольку мы могли бы проверять состояние кнопки только по завершению цикла, в результате чего большинство нажатий кнопки были бы наверняка пропущены.

Но прежде чем приступать к рассмотрению программы для реализации этой задачи, нам нужно разобраться с понятием волатильных (временных) переменных. В частности, переменные, значения которых будут меняться при исполнении процедуры обработки прерывания, должны объявляться как волатильные: volatile. Это необходимо для правильной обработки этих переменных компилятором. Объявление переменной волатильной выполняется добавлением ключевого слова volatile в начале объявления:

volatile int selectedLED = 9;

Чтобы плата Arduino ожидала сигнала прерывания на соответствующем контакте, для этого контакта в разделе setup() нужно посредством функции attachInterrupt() задать режим отслеживания прерываний. В аргументах этой функции указывается ID-номер прерывания в виде функции digitalPinToInterrupt(), функция обработки прерывания, а также режим запуска прерывания (RISING, FALLING и т.п.). В нашей программе кнопка подключена к контакту 2 платы Arduino, которому функция digitalPinToInterrupt() присваивает ID-номер прерывания 0. При запросе прерывания вызывается функция swap(), а сам запуск осуществляется положительным перепадом сигнала прерывания. В результате, функция attachInterrupt() будет иметь такой вид:

attachInterrupt(digitalPinToInterrupt(2), swap, RISING);

Далее нужно создать функцию swap() и добавить ее в программу, полный код которой приведен в листинге 13.1. Это все, что нам необходимо сделать в отношении программной части. Подключив прерывание и создав функцию для его обработки, в остальной части основной программы можно выполнять любые требуемые операции. При запуске прерывания исполнение основной программы приостанавливается и исполняется функция обработки прерывания, по завершению которой возобновляется исполнение основной в точке, в которой оно было прервано. Поскольку прерывания останавливают ход основной программы, функции их обработки следует делать очень короткими и не использовать в них никаких пауз. В действительности, функция delay() даже не будет работать в функции обработки прерывания. Выяснив все это, теперь мы можем написать следующую программу, которая циклически повышает и понижает яркость одного из трех элементов RGB-светодиода и при нажатии кнопки переключается на другой, продолжая с ним делать то же самое.

Листинг 13.1. Программа hw_multitask.ino для демонстрации аппаратных прерываний

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Управление прерыванием, используя кнопку с аппаратной защитой
// от дребезга
// Контакты Arduino для считывания сигнала с кнопок
const int BTN = 2; // Сигнал кнопки после устранения дребезга
// подаем на контакт 2
const int RED = 11; // Подключаем катод красного светодиода
// к контакту 11
const int GREEN = 10; // Подключаем катод зеленого светодиода
// к контакту 10
const int BLUE = 9; // Подключаем катод синего светодиода
// к контакту 9
// Волатильные переменные могут модифицироваться
// функцией обработки прерывания
volatile int selectedLED = RED;
 
void setup()
{
pinMode(RED, OUTPUT);
pinMode(GREEN, OUTPUT);
pinMode(BLUE, OUTPUT);
// Начинаем исполнение с выключенным RGB-светодиодом
// Сигнал управления инвертирован, поскольку мы управляем катодом
digitalWrite(RED, HIGH);
digitalWrite(BLUE, HIGH);
digitalWrite(GREEN, HIGH);
// Поскольку сигнал на контакте инвертирован,
// ожидаем положительный перепад
attachInterrupt(digitalPinToInterrupt(BTN), swap, RISING);
}
 
void swap()
{
// Выключаем текущий составляющий светодиод. Высокий уровень (HIGH)
// выключает светодиод, поскольку это светодиод с общим анодом
digitalWrite(selectedLED, HIGH);
// Затем включаем другой составляющий светодиод
if (selectedLED == GREEN)
selectedLED = RED;
else if (selectedLED == RED)
selectedLED = BLUE;
else if (selectedLED == BLUE)
selectedLED = GREEN;
}
 
void loop()
{
// Постепенно повышаем яркость светодиода
// Сигнал управления инвертирован, поскольку мы управляем катодом
for (int i=255; i&gt;=0; i--)
{
analogWrite(selectedLED, i);
delay(10);
}
// Постепенно понижаем яркость светодиода
// Сигнал управления инвертирован, поскольку мы управляем катодом
for (int i=0; i&lt;=255; i++)
{
analogWrite(selectedLED, i);
delay(10);
}
delay(1000);
}

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


Примечание

  1. На веб-странице для этой главы (https://www.exploringarduino.com/content2/ch13) можно просмотреть видеоклип, демонстрирующий эту схему и скетч для нее в действии.
  2. А в данном случае даже рекомендуется. См. документацию по функции attachInterrupt() на веб-сайте Arduino https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/.
Опубликовано

Создание собственных библиотек

По материалам книги “Arduino. Большая книга рецептов“, 3-е изд. (авторы Джепсон Брайан, Марголис Майкл, Уэлдин Николас Роберт) (глава 16. Использование, модифицирование и создание библиотек)

Arduino. Большая книга рецептов, 3-издание

ЗАДАЧА

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

РЕШЕНИЕ

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

Большинство библиотек Arduino создаются в виде класса. Те из вас, кто знаком с языком С++ или Java, должны знать, что такое класс. Однако библиотеки можно также создавать и без использования классов, и здесь мы увидим, как это сделать.

Итак, давайте модифицируем скетч решения из разд. 7.1, чтобы включить функцию BlinkLED() в библиотеку.

Схема подключения светодиодов приводится на рис. 7.2, а описание ее работы — в разд. 7.1. Создаваемая нами библиотека будет содержать функцию blinkLED() из этого решения. В листинге 16.5 приводится код скетча blinkLibTest, с помощью которого будет выполняться тестирование созданной библиотеки.

Листинг 16.5. Скетч для тестирования созданной библиотеки

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* Скетч blinkLibTest
*/
 
#include "blinkLED.h"
const int firstLedPin = 3; // Контакты для подключения светодиодов
const int secondLedPin = 5;
const int thirdLedPin = 6;
 
void setup(){
pinMode(firstLedPin, OUTPUT); // Задаем выходной режим работы для
// контактов светодиодов
pinMode(secondLedPin, OUTPUT);
pinMode(thirdLedPin, OUTPUT);
}
 
void loop(){
// Мигаем каждым светодиодом один раз в 1,000 мс (1 секунду)
blinkLED(firstLedPin, 1000);
blinkLED(secondLedPin, 1000);
blinkLED(thirdLedPin, 1000);
}

Функцию blinkLED() нужно удалить из скетча решения, приведенного в разд. 7.1, и поместить в отдельный файл с названием blinkLED.cpp, как показано в листинге 16.6 (файлы *.cpp рассматриваются более подробно далее — в разд. «Обсуждение работы решения и возможных проблем» этого раздела).

Листинг 16.6. Код библиотеки blinkLED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* blinkLED.cpp
* Простая библиотека для мигания светодиодом с периодом в миллисекундах
*/
#include "Arduino.h" // Для версий среды Arduino IDE более ранних,
// чем версия 1.0, используйте файл Wprogram.h
#include "blinkLED.h"
// Мигаем светодиодом на этом контакте в течение duration миллисекунд
void blinkLED(int pin, int duration)
{
digitalWrite(pin, HIGH); // Включаем светодиод
delay(duration);
digitalWrite(pin, LOW); // Выключаем светодиод
delay(duration);
}
Большинство библиотек разрабатывается программистами, которые используют для этого свою среду разработки, но их с таким же успехом можно создавать и в любом обычном текстовом редакторе типа Блокнот.

А теперь создайте заголовочный файл blinkLED.h и скопируйте в него следующий код:

1
2
3
4
5
6
/*
* blinkLED.h
* Заголовочный файл для библиотеки BlinkLED
*/
#include "Arduino.h"
void blinkLED(int pin, int duration); // Прототип функции

Обсуждение работы решения и возможных проблем

Наша библиотека будет называться blinkLED и храниться в папке libraries (см. разд. 16.2). Создайте в этой папке вложенную папку blinkLED и переместите в нее файлы blinkLED.h и blinkLED.cpp. Затем создайте в папке blinkLED вложенную папку examples, а в ней — вложенную папку blinkLibTest. Создайте из скетча для тестирования библиотеки (см. листинг 16.5) файл с именем blinkLibTest.ino и поместите его в папку blinkLibTest. Путь к этому файлу должен выглядеть так: examples/blinkLibTest/ blinkLibTest.ino.

В результате мы переместили функцию blinkLED() скетча решения из разд. 7.1 в файл библиотеки blinkLED.cpp (расширением cpp обозначаются файлы с исходным кодом, созданным на языке С++, — С plus plus).

Используемые в документации библиотек Arduino термины функция и метод обозначают блоки кода — такие как blinkLED(). Термин метод служит для обозначения функциональных блоков классов. Оба термина обозначают именованные функциональные блоки, доступ к которым предоставляется посредством библиотек.

Файл blinkLED.cpp содержит исходный код функции blinkLED(), который идентичен коду этой функции в скетче из листинга 7.1, за исключением следующих двух строк в начале кода:

1
2
#include "Arduino.h" // Подключение заголовочного файла Arduino.h
#include "blinkLED.h"

Оператор #include “Arduino.h” требуется для библиотек, использующих любые функции или константы языка Arduino. При отсутствии этого оператора компилятор будет выдавать сообщения об ошибке для всех функций, используемых в скетче.

 

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

1
2
3
4
5
#if ARDUINO &gt;= 100
#include "Arduino.h // для версий 1.0 и более поздних
#else
#include "WProgram.h" // для более ранних версий
#endif

Следующая строка кода: #include “blinkLED.h” — содержит определение функции (которое также называется прототипом функции) для нашей библиотеки. Компилятор Arduino автоматически создает прототипы для всех функций в скетче при его компилировании, но не создает прототипов для функций, содержащихся в библиотеках, поэтому при создании библиотеки необходимо самому создать заголовочный файл, содержащий эти прототипы. При подключении библиотеки к скетчу в него добавляется именно этот заголовочный файл (см. разд. 16.1).

Каждая библиотека должна иметь файл, в котором объявляются названия предо- ставляемых функций. Такой файл называется заголовочным (header file) или включаемым (include file), а его название имеет формат НазваниеБиблиотеки.h. В рассматриваемом примере заголовочный файл называется blinkLED.h и размещается в той же папке, что и файл blinkLED.cpp.

Содержимое заголовочного файла для нашей библиотеки очень простое — объявление одной функции:

1
void blinkLED(int pin, int duration); // Прототип функции

Это объявление почти такое же, как и объявление этой функции в файле blinkLED.cpp:

1
void blinkLED(int pin, int duration)

Однако разница между этими двумя объявлениями функции хотя и тонкая, но критическая. Объявление прототипа функции в заголовочном файле завершается точкой с запятой. Это сообщает компилятору, что это просто объявление формата для функции, но не ее кода. А отсутствие завершающей точки с запятой в объявлении функции в файле исходного кода blinkLED.cpp сообщает компилятору, что это собственно исходный код функции.

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

Более подробную информацию по использованию заголовочных файлов и файлов *.cpp для создания модулей кода можно получить, прочитав какую-либо хорошую книгу по языку C++ (в разд. «Дополнительная информация» этого раздела рекомендуется несколько таких весьма популярных книг).

Разместив файлы blinkLED.cpp, blinkLED.h и blinkLibTest.ino в соответствующие вложенные папки в папке libraries, закройте среду разработки Arduino IDE, а затем снова запустите ее. Структура задействованных папок и файлов должна выглядеть следующим образом:

libraries/

└── blinkLED/

├── blinkLED.cpp

├── blinkLED.h

└── examples/

└── blinkLibTest/

└── blinkLibTest.ino

Среда Arduino IDE обновляет список доступных библиотек только при ее запуске. Чтобы установленная вручную библиотека отобразилась в этом списке, необходимо закрыть, а затем снова запустить среду Arduino IDE. И хотя среду Arduino IDE нужно перезапустить при исходном добавлении библиотеки в папку libraries, впоследствии — после модифицирования библиотеки — перезапуск не требуется.

Выполнив команду меню Файл | Примеры (Примеры из пользовательских библиотек) | blinkLED | blinkLibTest, откройте в окне Arduino IDE тестовый скетч blinkLibTest. Загрузите этот скетч в свою плату Arduino, и подключенные к ней светодиоды должны начать мигать так же, как и при исполнении скетча решения из разд. 7.1.

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

Эта задача решается добавлением в заголовочный файл библиотеки трех строк кода со значениями констант, которые обычно размещаются непосредственно перед кодом объявления прототипа функции:

1
2
3
4
5
// Константы для периода мигания светодиодов
const int BLINK_SHORT = 250;
const int BLINK_MEDIUM = 500;
const int BLINK_LONG = 1000;
void blinkLED(int pin, int duration); // Прототип функции

Затем надо модифицировать код в функции loop() скетча примера, как показано в листинге 16.7. Загрузив модифицированный скетч в плату, мы увидим, что каждый светодиод мигает с другой частотой.

Листинг 16.7. Модифицированный код функции loop() для мигания светодиодами с разной частотой

1
2
3
4
5
void loop(){
blinkLED(firstLedPin, <strong>BLINK_SHORT</strong>);
blinkLED(secondLedPin, <strong>BLINK_MEDIUM</strong>);
blinkLED(thirdLedPin, <strong>BLINK_LONG</strong>);
}

Также легко добавляются в библиотеку и новые функции. В листинге 16.8 приводится код главного цикла loop(), который мигает каждым светодиодом заданное для него количество раз.

Листинг 16.8. Код цикла loop() для мигания каждым светодиодом в течение заданного для него периода

1
2
3
4
5
void loop(){
blinkLED(firstLedPin, BLINK_SHORT, 5); // <strong>Мигает 5 раз</strong>
blinkLED(secondLedPin, BLINK_MEDIUM, 3); // <strong>Мигает 3 раза</strong>
blinkLED(thirdLedPin, BLINK_LONG); // <strong>Мигает один раз</strong>
}

Чтобы добавить эту функцию в библиотеку, в файл blinkLED.h нужно добавить ее прототип, как показано в листинге 16.9.

Листинг 16.9. Добавление прототипа новой функции в заголовочный файл

1
2
3
4
5
6
7
8
9
10
11
12
/*
* blinkLED.h
* Заголовочный файл для библиотеки blinkLED
*/
#include "Arduino.h"
// Константы для периода мигания светодиодов
const int BLINK_SHORT = 250;
const int BLINK_MEDIUM = 500;
const int BLINK_LONG = 1000;
void blinkLED(int pin, int duration);
<strong>// Определение новой функции для мигания заданного количества раз</strong>
<strong>void blinkLED(int pin, int duration, int repeats);</strong>

А в файл blinkLED.cpp добавляем исходный код функции, как показано в листинге 16.10.

Листинг 16.10. Исходный код функции мигания светодиодами заданное количество раз

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
* blinkLED.cpp
* Простая библиотека для мигания светодиодом с периодом в миллисекундах
*/
#include "Arduino.h"
#include "blinkLED.h"
// Мигаем светодиодом на этом контакте в течение duration миллисекунд
 
void blinkLED(int pin, int duration)
{
digitalWrite(pin, HIGH); // Включаем светодиод
delay(duration);
digitalWrite(pin, LOW); // Выключаем светодиод
delay(duration);
}
/* Функция для повторения мигания */
void blinkLED(int pin, int duration, int repeats)
{
while(repeats)
{
blinkLED(pin, duration);
repeats = repeats -1;
};
}

Для созданной библиотеки можно добавить возможность выделения цветом ключевых слов при просмотре исходного кода скетча в окне редактора скетчей среды IDE. Для этого нужно создать специальный файл keywords.txt. Это обычный текстовый файл, который содержит список ключевых слов и их типов: каждый тип ключевого слова выделяется другим цветом. Ключевые слова и типы разделяются табуляцией, а не просто пробелом. В листинге 16.11 приводится пример файла keywords.txt, в котором задается выделение цветом констант периода мигания светодиода.

Листинг 16.11. Файл keywords.txt для библиотеки blinkLED

1
2
3
4
5
6
7
8
9
10
11
#######################################
# Methods and Functions (KEYWORD2)
#######################################
blinkLED KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
 
BLINK_SHORT LITERAL1
BLINK_MEDIUM LITERAL1
BLINK_LONG LITERAL1

Сохраните его в папке blinkLED и перезапустите среду Arduino IDE. Если теперь открыть файл скетча blinkLibTest.ino в среде Arduino IDE, то константы BLINK_SHORT, BLINK_MEDIUM и BLINK_LONG будут выделяться цветом.

Дополнительная информация

Дополнительные примеры создания библиотек рассматриваются в разд. 16.5.

Дополнительная информация по созданию библиотек для Arduino приводится в справочном документе «Writing a Library for Arduino» (https://oreil.ly/vLNvx).

Рекомендуем также следующие книги по программированию на C++:

  • «Practical C++ Programming» («Практическое программирование на языке C++»), автор Steve Oualline, издательство O’Reilly;
  • «C++ Primer Plus» («Язык программирования С++»), автор Stephen Prata, издательство Sams;
  • «C++ Primer» («Учебник C++ для начинающих»), авторы Stanley B. Lippman, Josee Lajoie и Barbara E. Moo, издательство Addison-Wesley Professional.
Опубликовано

Управление направлением и скоростью вращения щеточного электродвигателя с помощью датчиков

По материалам книги “Arduino. Большая книга рецептов“, 3-е изд. (авторы Джепсон Брайан, Марголис Майкл, Уэлдин Николас Роберт) (глава 8. Управление
электродвигателями)

Arduino. Большая книга рецептов, 3-издание

ЗАДАЧА

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

РЕШЕНИЕ

Двигатели для этого решения подключаются так же, как показано на рис. 8.10, но к схеме добавлены два фоторезистора (или фототранзистора — подробную информацию по фототранзисторам см. в разд. 1.6), как показано в схеме на рис. 8.12. Конденсаторы номиналом 0,1 мкФ на электродвигателях должны быть керамическими.

C:\Users\acer\Documents\#Electronics\Магазин электроники\фрагменты из книг\2788. Arduino Cookbook. Recipes to Begin, Expand, and Enhance\verstka\pic\08\8.12.png

Рис. 8.12. Управление двумя щеточными электродвигателями с помощью датчиков

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

Листинг 8.12. Управление двумя электродвигателями с помощью фотодатчиков

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* Скетч Brushed_H_Bridge_Direction
* Управляет электродвигателями на основании сигналов фотодатчиков,
* направляя шасси робота к источнику света
*/
 
int leftPins[] = {5,7,4}; // Один контакт для ШИМ (скорость),
// два контакта для направления левого двигателя
int rightPins[] = {6,3,2}; // То же, для правого двигателя
const int MIN_PWM = 64; // Это значение может быть в диапазоне от 0 до MAX_PWM
const int MAX_PWM = 128; // Это значение может быть в диапазоне от 50 до 255
const int leftSensorPin = A0; // Контакты для подключения фотодатчиков
const int rightSensorPin = A1;
 
int sensorThreshold = 0; // Пороговый уровень освещенности на датчике
// для вращения электродвигателя
void setup()
{
 
for(int i=1; i &lt; 3; i++)
{
pinMode(leftPins[i], OUTPUT);
pinMode(rightPins[i], OUTPUT);
}
}
 
void loop()
{
int leftVal = analogRead(leftSensorPin);
int rightVal = analogRead(rightSensorPin);
if(sensorThreshold == 0) // Датчики откалиброваны?
{
// Если нет, калибруем до уровня освещенности несколько выше
// среднего уровня освещенности окружающей среды
sensorThreshold = ((leftVal + rightVal) / 2) + 100 ;
}
if( leftVal &gt; sensorThreshold || rightVal &gt; sensorThreshold)
{
// Если уровень освещенности достаточно высокий
// для движения вперед
setSpeed(rightPins, map(rightVal,0,1023, MIN_PWM, MAX_PWM));
setSpeed(leftPins, map(leftVal ,0,1023, MIN_PWM, MAX_PWM));
}
}
 
void setSpeed(int pins[], int speed )
{
if(speed &lt; 0)
{
digitalWrite(pins[1],HIGH);
digitalWrite(pins[2],LOW);
speed = -speed;
}
else
{
digitalWrite(pins[1],LOW);
digitalWrite(pins[2],HIGH);
}
analogWrite(pins[0], speed);
}

Обсуждение работы решения и возможных проблем

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

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

Сигнал с фотодатчиков подается на аналоговые контакты A0 и A1 платы Arduino,
с которых он считывается функцией analogRead() (эта функция подробно рассматривается в разд. 6.3). В начале исполнения скетча измеряется уровень фоновой освещенности, и это пороговое значение используется для определения минимального уровня освещенности, необходимого, чтобы начинать движение шасси. К измеренному пороговому уровню освещенности добавляется запас величиной в 100 еди­ниц, чтобы небольшие изменения уровня фоновой освещенности не вызывали движения. Значение уровня освещенности, возвращенное функцией analogRead(), преобразовывается с помощью функции map() в значение ШИМ. Константе MIN_PWM следует задать приблизительное значение ШИМ, при котором шасси должно начинать движение (слишком низкие значения не дадут достаточного крутящего момента. Точное значение надо будет определить экспериментальным путем). А константе MAX_PWM нужно задать значение (до 255), определяющее максимальную скорость шасси.

Скорость вращения электродвигателей управляется функцией setSpeed(). Для управления направлением вращения каждого электродвигателя используются два контакта платы Arduino и еще один контакт — для управления скоростью вращения. Номера этих контактов хранятся в массивах leftPins и rightPins для левого и правого двигателей соответственно. В первом элементе массива хранится номер контакта для управления скоростью вращения, а в двух других — для управления направлением.

В любом из решений, в котором используется микросхема H-моста L293, вместо нее можно альтернативно применить микросхему TB6612FNG. На рис. 8.13 показано подключение к плате Arduino микросхемы H-моста TB6612 (установленной на адаптерной плате артикул 713 компании Pololu).

C:\Users\acer\Documents\#Electronics\Магазин электроники\фрагменты из книг\2788. Arduino Cookbook. Recipes to Begin, Expand, and Enhance\verstka\pic\08\8.13.png

Рис. 8.13. Подключение адаптерной платы микросхемы H-моста TB6612 производства компании Pololu

Количество контактов платы Arduino, задействованных для управления схемой, можно уменьшить, добавив в схему дополнительный компонент для управления контактами направления H-моста. Такой компонент представляет собой транзистор или логический элемент для инвертирования логического уровня, подаваемого на другой ввод H-моста, позволяя использовать только один контакт платы Arduino для управления направлением вращения электродвигателя. Соответствующую принципиальную схему можно найти на веб-сайте Arduino, но также существуют и готовые решения — например, шилд H-моста Arduino Motor Shield (артикул 7630049200371, https://store.arduino.cc/arduino-motor-shield-rev3) или шилд Ardumoto компании SparkFun (артикул DEV-09213). Оба эти шилда используют микросхему L298, которая является более мощной альтернативой микросхеме L293. Эти шилды вставляются в гнездовые разъемы платы Arduino и требует только подключения к электродвигателю и его источнику питания.

В листинге 8.13 приводится скетч для управления электродвигателями с помощью шилда Arduino Motor Shield (контакты A0 и A1 платы Arduino задействованы для измерения тока, поэтому для управления в скетче используются контакты A2 и A3).

Листинг 8.13. Управление электродвигателями с помощью шилда Arduino Motor Shield

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
* Скетч Brushed_H_Bridge_Direction sketch for motor shield
* Управляет электродвигателями на основании сигналов фотодатчиков,
* направляя шасси робота к источнику света
*/
int leftPins[] = {3,12}; // Один контакт для ШИМ-сигнала (скорость),
// один — для направления
int rightPins[] = {11,13};
const int MIN_PWM = 64; // Это значение может быть в диапазоне
// от 0 до MAX_PWM
const int MAX_PWM = 128; // Это значение может быть в диапазоне от 50 до 255
const int leftSensorPin = A2; // Аналоговые контакты для подключения фотодатчиков
const int rightSensorPin = A3;
int sensorThreshold = 0; // Пороговый уровень освещенности на датчике
// для вращения электродвигателя
void setup()
{
pinMode(leftPins[1], OUTPUT);
pinMode(rightPins[1], OUTPUT);
}
 
void loop(){
int leftVal = analogRead(leftSensorPin);
int rightVal = analogRead(rightSensorPin);
if(sensorThreshold == 0) // Датчики откалиброваны?
{
// Если нет, калибруем до уровня освещенности несколько выше
// среднего уровня освещенности окружающей среды
sensorThreshold = ((leftVal + rightVal) / 2) + 100 ;
}
if( leftVal &gt; sensorThreshold || rightVal &gt; sensorThreshold)
{
// Если уровень освещенности достаточно высокий для движения вперед
setSpeed(rightPins, map(rightVal,0,1023, MIN_PWM, MAX_PWM));
setSpeed(leftPins, map(leftVal, 0,1023, MIN_PWM, MAX_PWM));
}
}
void setSpeed(int pins[], int speed )
{
if(speed &lt; 0)
{
digitalWrite(pins[1], HIGH);
speed = -speed;
}
else
{
digitalWrite(pins[1], LOW);
}
analogWrite(pins[0], speed);
}

Функция loop() здесь идентична этой функции скетча из листинге 8.12. Функция setSpeed() скетча содержит меньший объем кода, чем ее предыдущая версия, поскольку аппаратное обеспечение шилда позволяет использовать только один контакт платы Arduino для управления направлением вращения электродвигателя.
В шилде Ardumoto используются другие контакты, поэтому для работы с ним код скетча нужно откорректировать следующим образом:

1
2
3
int leftPins[] = {3, 2}; // Один контакт для ШИМ-сигнала (скорость),
// один — для направления
int rightPins[] = {11, 4};

В листинге 8.14 приводится код скетча, реализующий эту же функциональность на основе шилда Adafruit Motor Shield V2 (https://oreil.ly/kFygk), подключение которого показано на рис. 8.14. В скетче используется библиотека Adafruit_MotorShield, которая устанавливается стандартным способом с помощью Менеджера библиотек.

Шилд Adafruit Motor Shield V2 поддерживает подключение четырех электродвигателей, но в скетче из листинге 8.14 предусмотрено, что два электродвигателя подключены к разъемам шилда 3 и 4.

Листинг 8.14. Управление электродвигателями с помощью шилда Adafruit Motor Shield V2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
* Скетч Brushed_H_Bridge_Direction sketch for Adafruit Motor shield
* Управляет электродвигателями на основании сигналов фотодатчиков,
* направляя шасси робота к источнику света
*/
#include &lt;Wire.h&gt;
#include &lt;Adafruit_MotorShield.h&gt; // Подключаем библиотеку Adafruit_MotorShield
// Создаем экземпляр объекта шилда
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *leftMotor = AFMS.getMotor(1);
Adafruit_DCMotor *rightMotor = AFMS.getMotor(2);
const int MIN_PWM = 64; // Это значение может быть в диапазоне от 0 до MAX_PWM
const int MAX_PWM = 128; // Это значение может быть в диапазоне от 50 до 255
const int leftSensorPin = A0; // Аналоговые контакты для подключения фотодатчиков
const int rightSensorPin = A1;
int sensorThreshold = 0; // Пороговый уровень освещенности на датчике
// для вращения электродвигателя
void setup()
{
AFMS.begin(); // Инициализируем экземпляр шилда частотой
// по умолчанию 1,6 КГц
}
 
void loop()
{
int leftVal = analogRead(leftSensorPin);
int rightVal = analogRead(rightSensorPin);
if(sensorThreshold == 0) // Датчики откалиброваны?
{
// Если нет, калибруем до уровня освещенности несколько выше
// среднего уровня освещенности окружающей среды
sensorThreshold = ((leftVal + rightVal) / 2) + 100 ;
}
if( leftVal &gt; sensorThreshold || rightVal &gt; sensorThreshold)
{
// Если уровень освещенности достаточно высокий для движения вперед
setSpeed(rightMotor, map(rightVal,0,1023, MIN_PWM, MAX_PWM));
setSpeed(leftMotor, map(leftVal ,0,1023, MIN_PWM, MAX_PWM));
}
}
void setSpeed(Adafruit_DCMotor *motor, int speed )
{
if(speed &lt; 0)
{
motor-&gt;run(BACKWARD);
speed = -speed;
}
else
{
motor-&gt;run(FORWARD);
}
motor-&gt;setSpeed(speed);
}

C:\Users\acer\Documents\#Electronics\Магазин электроники\фрагменты из книг\2788. Arduino Cookbook. Recipes to Begin, Expand, and Enhance\verstka\pic\08\8.14.png

Рис. 8.14. Подключение шилда Adafruit Motor Shield V2

В случае использования иного шилда, чем только что рассмотренные, надо будет уточнить по его справочному листку, что значения в скетче совпадают с контактами, используемыми для сигналов управления скоростью (ШИМ) и направления.

Дополнительная информация

Подробная информация по адаптерной плате компании Pololu для H-моста TB6612FNG приводится в справочном листке на нее (https://oreil.ly/bD_83).

Дополнительная информация по шилду Ardumoto компании SparkFun приводится на его веб-странице (https://oreil.ly/XZTCY).

Дополнительная информация по шилду Arduino Motor Shield приводится на его веб-странице (https://oreil.ly/2gKoX).

Дополнительная информация по шилду Adafruit Motor Shield V2 приводится на его веб-странице (https://oreil.ly/T19_o).

Опубликовано

Подключение к Интернету с помощью платы расширения Arduino Ethernet shield

По материалам книги В. Петина “Проекты с использованием контроллера Arduino. 4-е изд.” (глава 13. Arduino и Интернет вещей)

Проекты с использованием контроллера Arduino. 4-е изд.

Самый распространенный метод обеспечить доступ платы Arduino к сети Интернет — использование платы Ethernet shield (рис. 13.1). Ethernet shield — это плата расширения, которая устанавливается на плату Arduino сверху. Она дает ей возможность выступать в роли сетевого устройства и общаться по проводной сети с аналогичными устройствами, с обычными компьютерами, принтерами, сервисами в Интернете и прочими сетевыми ресурсами. Последняя версия платы Ethernet Shield Rev3 полностью совместима с Arduino Mega2560.

Плата Ethernet shield основана на микросхеме Wiznet W5100, которая поддерживает как TCP-, так и UDP-протоколы. Одновременно открытыми могут быть до четырех подключений.

Плата обладает стандартным Ethernet-портом для подключения к сети с помощью патч-корда витой пары и набором контактов для подключения к Arduino. Для общения между собой Ethernet shield и Arduino задействуют контакты 4-й и с 10-го по 13-й, поэтому их использование в других целях в присутствии платы расширения невозможно.

Для программирования сетевого взаимодействия подключается библиотека Ethernet из стандартного дистрибутива. При использовании этой библиотеки необходимо указывать MAC-адрес платы (уникальный идентификатор любого сетевого устройства). В более новых версиях Ethernet-шилда MAC-адрес можно увидеть на наклейке на плате. Если такой наклейки нет, то просто введите любую похожую комбинацию, — главное, чтобы в вашей сети не было устройств с совпадающими MAC-адресами.
На плате размещен слот для карты памяти формата microSD, которая может быть использована для хранения ресурсов, раздаваемых по сети. Для взаимодействия с такой картой следует подключить, например, библиотеку sdfatlib.
Для отправки данных в облачные сервисы в примерах этого раздела мы воспользуемся веб-клиентом на основе платы Arduino c установленной на нее платой расширения Ethernet shield.

ris_04_04

Рис. 13.1. Плата Ethernet shield Rev3

13.1.1. Получение IP-адреса по DHCP

Соединим Ethernet shield с платой Arduino и создадим простой пример получения ими IP-адреса по DHCP. Соединяется Ethernet shield с платой Arduino так же просто, как и любой другой шилд, — просто состыкуйте их вместе. Cледует учесть, что установка других шилдов поверх Ethernet shield весьма затруднительна. Это связано с большими размерами имеющегося на плате Ethernet shield разъема RJ-45, служащего для подключения сетевого кабеля, поэтому, если вы хотите использовать совместно с Arduino еще и другие шилды, лучше их размещать между Arduino и Ethernet shield.

Итак, подключим плату Arduino к USB-порту компьютера, а Ethernet shield подсоединим c помощью сетевого кабеля к маршрутизатору, имеющему выход в Интернет (рис. 13.2).

Скетч, обеспечивающий получение IP-адреса по DHCP, представлен в листинге 13.1, а пример назначения статического IP-адреса — в листинге 13.2.

ris_04_05

Рис. 13.2. Подключение к плате Arduino платы расширения Ethernet shield Rev3

Листинг 13.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Получение IP-адреса по DHCP
// MAC-адрес Ethernet shield (можно увидеть на наклейке на плате) или
// произвольный уникальный в сети
 
#include &lt;Ethernet.h&gt;
#include &lt;SPI.h&gt;
byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02};
void setup() {
 
// Open serial communications and wait for port to open:
 
Serial.begin(9600);
}
 
// запуск Ethernet-соединения
if (Ethernet.begin(mac) == 0) {
Serial.println("Failed to configure Ethernet using DHCP");
for (;;)
;
}
 
// печать в последовательный порт полученного по DHCP адреса
Serial.print("My IP address: ");
for (byte thisByte = 0; thisByte &lt; 4; thisByte++) {
Serial.print(Ethernet.localIP()[thisByte], DEC);
Serial.print(".");
}
Serial.println();
}
 
void loop() {;}

Электронный архив

Полный вариант рассмотренного скетча находится в папке examples\13\_13_01 сопровождающего книгу электронного архива (см. приложение 2).


Листинг 13.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Получение статического IP-адреса
// MAC-адрес Ethernet shield (можно увидеть на наклейке на плате) или
// произвольный уникальный в сети
 
#include &lt;Ethernet.h&gt;
#include &lt;SPI.h&gt;
byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02};
 
// IP-адрес, назначаемый Ethernet shield:
byte ip[] = { 192, 168, 0, 111 };
// IP-адрес dns сервера:
byte sdns[] = { 192, 168, 1, 1 };
// адрес шлюза:
byte gateway[] = { 192, 168, 0, 1 };
// маска:
byte subnet[] = { 255, 255, 255, 0 };
 
void setup() {
Serial.begin(9600);
// запуск Ethernet-соединения
Ethernet.begin(mac, ip, sdns, gateway, subnet);
delay(1000);
Serial.println(Ethernet.localIP());
}
 
void loop() {;}

Электронный архив
Полный вариант рассмотренного скетча находится в папке examples\13\_13_02 сопровождающего книгу электронного архива (см. приложение 2).