Исходный код программы
В программе первым делом необходимо инициализировать выходные контакты для подключения двигателей (через драйвер мотора).
Затем в функции setup задать направление работы для этих контактов (на вывод данных).
После этого мы будем считывать данные из последовательного порта Arduino, получаемые им от последовательного порта Bluetooth модуля, и выполнять соответствующие инструкции.
Затем мы запрограммируем функции для различных направлений движения машины. Всего будет использоваться пять условий для задания направления движения машины, представленных в следующей таблице:
Далее представлен полный текст программы.
if(str[i-1]==’1′)
{
Serial.println(“Forward”);
forward();
i=0;
}
else if(str[i-1]==’2′)
{
Serial.println(“Left”);
right();
i=0;
}
else if(str[i-1]==’3′)
{
Serial.println(“Right”);
left();
i=0;
}
else if(str[i-1]==’4′)
{
Serial.println(“Backward”);
backward();
i=0;
}
else if(str[i-1]==’5′)
{
Serial.println(“Stop”);
Stop();
i=0;
}
delay(100);
}
}
#define m11 11 // задний двигатель #define m12 12 #define m21 10 // передний двигатель #define m22 9 charstr[2],i; voidforward() { digitalWrite(m11,LOW); digitalWrite(m12,LOW); digitalWrite(m21,HIGH); digitalWrite(m22,LOW); } voidbackward() { digitalWrite(m11,LOW); digitalWrite(m12,LOW); digitalWrite(m21,LOW); digitalWrite(m22,HIGH); } voidleft() { digitalWrite(m11,HIGH); digitalWrite(m12,LOW); delay(100); digitalWrite(m21,HIGH); digitalWrite(m22,LOW); } voidright() { digitalWrite(m11,LOW); digitalWrite(m12,HIGH); delay(100); digitalWrite(m21,HIGH); digitalWrite(m22,LOW); } voidStop() { digitalWrite(m11,LOW); digitalWrite(m12,LOW); digitalWrite(m21,LOW); digitalWrite(m22,LOW); } voidsetup() { Serial.begin(9600); pinMode(m11,OUTPUT); pinMode(m12,OUTPUT); pinMode(m21,OUTPUT); pinMode(m22,OUTPUT); } voidloop() { while(Serial.available()) { charch=Serial.read(); str[i ]=ch; if(str[i–1]==‘1’) { Serial.println(“Forward”); forward(); i=0; } elseif(str[i–1]==‘2’) { Serial.println(“Left”); right(); i=0; } elseif(str[i–1]==‘3’) { Serial.println(“Right”); left(); i=0; } elseif(str[i–1]==‘4’) { Serial.println(“Backward”); backward(); i=0; } elseif(str[i–1]==‘5’) { Serial.println(“Stop”); Stop(); i=0; } delay(100); } } |
Код программы для «малыша»:
#include <SoftwareSerial.h> // Подключаем библиотеку SoftwareSerial для общения с модулем по программной шине UART #include <iarduino_Bluetooth_HC05.h> // Подключаем библиотеку iarduino_Bluetooth_HC05 для работы с Trema Bluetooth модулем HC-05 SoftwareSerial softSerial(9, 10); // Создаём объект softSerial указывая выводы RX, TX (можно указывать любые выводы Arduino UNO). Вывод 2 Arduino подключается к выводу TX модуля, вывод 3 Arduino подключается к выводу RX модуля iarduino_Bluetooth_HC05 hc05(13); // Создаём объект hc05 указывая любой вывод Arduino, который подключается к выводу K модуля // uint8_t pinShield_H2 = 4; // Вывод, подключенный к драйверу, для задания направления вращения левым мотором uint8_t pinShield_E2 = 5; // Вывод ШИМ, подключенный к драйверу, для задания скорости левого мотора uint8_t pinShield_E1 = 6; // Вывод ШИМ, подключенный к драйверу, для задания скорости правого мотора uint8_t pinShield_H1 = 7; // Вывод, подключенный к драйверу, для задания направления вращения правым мотором uint8_t pinLED_RED = 12; // Вывод с красным светодиодом uint8_t pinLED_BLUE = 11; // Вывод с синим светодиодом uint16_t time_period = 200; // Частота мигания светодиодов (в миллисекундах) uint8_t valSpeed = 255; // Максимальная скорость ШИМ (число от 0 до 255) bool arrRoute[2] = {1, 1}; // Направление движения для каждого мотора ([0]- правый мотор, [1] - левый мотор) uint16_t arrSpeed[2]; // Скорость для каждого мотора ([0]- правый мотор, [1] - левый мотор) uint32_t tmrLED; // Время последнего включения светодиодов uint32_t flgTime; // Флаг для задания времени принятия пакетов от Bluetooth телефона uint8_t flg; // Флаг кнопок uint32_t tmrWait; // Время до начала сопряжения с новыми устройствами bool flg_LED; // Флаг включения светодиодов // </iarduino_bluetooth_hc05.h></softwareserial.h> void setup() { // // BLUETOOTH МОДУЛЬ // Serial.begin (9600); // Инициируем передачу данных по аппаратной шине UART для вывода результата в монитор последовательного порта Serial.print ("begin: "); // Выводим текст "begin: " в монитор последовательного порта if (hc05.begin(softSerial)) {Serial.println("Ok");} // Инициируем работу с Trema модулем hc05, указывая объект softSerial через который осуществляется связь по шине UART else {Serial.println("Error");} // Если работа с модулем не инициирована, то выводим сообщение об ошибке tmrWait = millis(); // Устанавливаем таймер ожидания сопряжения while (!hc05.checkConnect() && millis()<tmrWait 60000) {;} // Ждём в течении 60 секунд сопряжения с последним устройством из памяти if (millis()<tmrWait 60000) {Serial.println("Connect with last ADR");} // Если сопряжение произошло, то выдаём в монитор порта сообщение об этом else { // Если сопряжение не произошло, то if (hc05.createSlave("BT_CAR", "1234")) // Создаем ведомую роль модулю, указывая его имя и pin-код (в примере имя = "BT_CAR", pin-код = "1234") {Serial.println("Slave create");} // Если ведомая роль была создана, выводим сообщение об успехе в монитор порта, else {Serial.println("Slave not create");} // а если не была создана - выводим сообщение об ошибке в монитор порта. } // // МОТОРЫ // pinMode(pinShield_H2, OUTPUT); // Конфигурируем вывод pinShield_H2 как выход (направление вращения левого мотора) pinMode(pinShield_E2, OUTPUT); // Конфигурируем вывод pinShield_E2 как выход (скорость вращения левого мотора, ШИМ) pinMode(pinShield_E1, OUTPUT); // Конфигурируем вывод pinShield_E1 как выход (скорость вращения правого мотора, ШИМ) pinMode(pinShield_H1, OUTPUT); // Конфигурируем вывод pinShield_H1 как выход (направление вращения правого мотора) // СВЕТОДИОДЫ // pinMode(pinLED_RED,OUTPUT); // Конфигурируем вывод pinLED_RED как выход pinMode(pinLED_BLUE,OUTPUT); // Конфигурируем вывод pinLED_BLUE как выход tmrLED = millis(); // Устанавливаем таймер светодиодов равным millis() flg_LED = 0; // Сбрасываем флаг светодиодов } // void loop() { // if (softSerial.available()) { // Если есть принятые данные, то ... String str; // Создаём строку str while (softSerial.available()) { // Выполняем цикл пока есть что читать ... str = char(softSerial.read()); // Читаем очередной принятый символ из UART в строку str delay(5); // Задержка на 5 мс на случай медленного приёма } // Цикл завершён, значит читать больше нечего // КНОПКИ ДВИЖЕНИЯ // // Флаг времени Флаг кнопки // if (str == "II") { flgTime = millis(); flg = 1; } // Кнопка "стрелка вверх-влево" if (str == "FF") { flgTime = millis(); flg = 2; } // Кнопка "стрелка вверх" if (str == "GG") { flgTime = millis(); flg = 3; } // Кнопка "стрелка вверх-вправо" if (str == "RR") { flgTime = millis(); flg = 4; } // Кнопка "стрелка влево" if (str == "SS") { flgTime = millis(); flg = 5; } // СТОП if (str == "LL") { flgTime = millis(); flg = 6; } // Кнопка "стрелка вправо" if (str == "JJ") { flgTime = millis(); flg = 7; } // Кнопка "стрелка вниз-влево" if (str == "BB") { flgTime = millis(); flg = 8; } // Кнопка "стрелка вниз" if (str == "HH") { flgTime = millis(); flg = 9; } // Кнопка "стрелка вниз-вправо" // КНОПКИ ДОПОЛНИТЕЛЬНЫХ ФУНКЦИЙ // // Если кнопка нажата меняем флаг // if (str == "SWS" || str == "SwS") {flg_LED = !flg_LED;} // Кнопка включения светодиодов if (str == "S0S") {flg = 10; } // Ползунок скорости в положении 0 if (str == "S1S") {flg = 11; } // Ползунок скорости в положении 1 if (str == "S2S") {flg = 12; } // Ползунок скорости в положении 2 if (str == "S3S") {flg = 13; } // Ползунок скорости в положении 3 if (str == "S4S") {flg = 14; } // Ползунок скорости в положении 4 if (str == "S5S") {flg = 15; } // Ползунок скорости в положении 5 if (str == "S6S") {flg = 16; } // Ползунок скорости в положении 6 if (str == "S7S") {flg = 17; } // Ползунок скорости в положении 7 if (str == "S8S") {flg = 18; } // Ползунок скорости в положении 8 if (str == "S9S") {flg = 19; } // Ползунок скорости в положении 9 if (str == "SqS") {flg = 20; } // Ползунок скорости в положении 10 // ====================================================================================================================================================== switch (flg) {//Направление левого мотора Направление правого мотора Скорость левого мотора Скорость правого мотора case 1: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = (valSpeed / 2); arrSpeed[0] = valSpeed; break; // С-З case 2: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = valSpeed; arrSpeed[0] = valSpeed; break; // С case 3: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = valSpeed; arrSpeed[0] = (valSpeed / 2); break; // С-В case 4: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = 0; arrSpeed[0] = valSpeed; break; // З case 5: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = 0; arrSpeed[0] = 0; break; // Стоп case 6: arrRoute[1] = 1; arrRoute[0] = 1; arrSpeed[1] = valSpeed; arrSpeed[0] = 0; break; // В case 7: arrRoute[1] = 0; arrRoute[0] = 0; arrSpeed[1] = (valSpeed / 2); arrSpeed[0] = valSpeed; break; // Ю-З case 8: arrRoute[1] = 0; arrRoute[0] = 0; arrSpeed[1] = valSpeed; arrSpeed[0] = valSpeed; break; // Ю case 9: arrRoute[1] = 0; arrRoute[0] = 0; arrSpeed[1] = valSpeed; arrSpeed[0] = (valSpeed / 2); break; // Ю-В } // } // ======================================================================================================================================================= if (flg == 10){valSpeed = 5;} // 0 режим скорости else if(flg == 11){valSpeed = 30;} // 1 режим скорости else if(flg == 12){valSpeed = 55;} // 2 режим скорости else if(flg == 13){valSpeed = 80;} // 3 режим скорости else if(flg == 14){valSpeed = 105;} // 4 режим скорости else if(flg == 15){valSpeed = 130;} // 5 режим скорости else if(flg == 16){valSpeed = 155;} // 6 режим скорости else if(flg == 17){valSpeed = 180;} // 7 режим скорости else if(flg == 18){valSpeed = 205;} // 8 режим скорости else if(flg == 19){valSpeed = 230;} // 9 режим скорости else if(flg == 20){valSpeed = 255;} // 10 режим скорости // if (flg_LED) { // Если флаг установлен (была нажата кнопка включения фары) if (millis() - tmrLED > time_period) { // мигаем светодиодами с заданной частотой tmrLED = millis(); // сохраняем время digitalWrite(pinLED_RED, digitalRead(pinLED_BLUE)); // управляем питанием красного светодиода digitalWrite(pinLED_BLUE, !digitalRead(pinLED_BLUE)); // управляем питанием синего светодиода } // } else { // если флаг сброшен, то digitalWrite(pinLED_RED, LOW); // гасим светодиоды digitalWrite(pinLED_BLUE, LOW); // } // if (flgTime > millis()) { // Если millis() переполнен, то flgTime = 0; // сбрасываем флаг в ноль } // // ПОДАЧА ЗНАЧЕНИЙ СКОРОСТИ И НАПРАВЛЕНИЯ ВРАЩЕНИЯ НА ВЫВОДЫ // if (flgTime > (millis() - 500)) { // Если сигналы с телефона приходят (в течении 50 мс) digitalWrite(pinShield_H2, arrRoute[1]); // тогда задаем направление вращения правого мотора digitalWrite(pinShield_H1, arrRoute[0]); // и левого мотора analogWrite(pinShield_E2, arrSpeed[1]); // Задаём скорость вращения для правого мотора analogWrite(pinShield_E1, arrSpeed[0]); // и для левого мотора } else { // Если пакеты не приходят analogWrite(pinShield_E2, 0); // Останавливаем работу моторов analogWrite(pinShield_E1, 0); // } // } //
Ссылка для скачивания скетча.
В данном коде управление роботом осуществляется в три основных этапа: получение данных с телефона; изменение значений переменных arrSpeed, arrRoute; подача питания на моторы и задание направления их вращения. Так же в коде присутствуют дополнительные блоки: включение/выключение светодиодов; изменение скорости вращения колёс; вход в режим сопряжения.
- Получение данных с пульта:
- Данный блок начинается с оператора
if
, в условии которого написаноsoftSerial.available()
. Это условие будет верно, если в последовательный порт будут приходить данные с телефона, в противном случае условие будет ложно; - Далее следует еще один оператор
while
, условием которого опять являетсяsoftSerial.available()
. Если условие верно, то значение, поступившее в последовательный порт, будет записано в переменнуюstr
; задержка в 5 миллисекунд сделана для того, чтобы при низкой скорости передачи Arduino успел полностью прочитать значение из последовательного порта в переменнуюstr
;
- Данный блок начинается с оператора
- Изменение значений переменных arrSpeed, arrRoute:
- Данный блок начинается с оператора
if
, в условии которого написаноstr == XX
. В зависимости от значенияXX
, которое принимает переменнаяstr
(одно из 9 для основных функций), сбрасываетсяflgTime
– таймер начала выполнения функции, а так же устанавливается значение флагаflg
(от 1 до 9); - Далее следует конструкция
switch...case
, в которой операторswitch
сравнивает значение флагаflg
с операторомcase
и, в зависимости от значения флагаflg
, выполняет код, где задаётся, на какой мотор будет подано питание (переменнаяarrSpeed
) и с каким направлением вращения ( переменнаяarrRoute
);
- Данный блок начинается с оператора
- Подача питания на моторы и задание направления их вращения:
- Данный блок начинается с оператора
if
, в условии которого написаноflgTime > (millis() - 50)
. Это условие будет верно в течении 50 миллисекунд после начала приёма сигнала от телефона и установит на выводах Arduino значения переменныхarrSpeed
иarrRoute
. Если же сигнала в течении 50 миллисекунд не поступит, то операторelse
сбросит значение скорости в 0 и робот остановится;
- Данный блок начинается с оператора
- Включение/выключение светодиодов:
- Данный блок включает в себя 2 части: получение сигнала с телефона и изменение флага
flg_LED
; включение/выключение светодиодов; - Первая часть начинается с оператора
if
, в условии которого написано( str == "SWS" || str == "SwS")
. Условие будет верно, если значение переменнойstr
будет равноSWS
ИЛИSwS
, что приведёт к установке флагаflg_LED
в противоположное от нынешнего значение; - Вторая часть начинается с оператора
if
, в условии которого написано(flg_LED)
. Условие будет верно, если значение флагаflg_LED
будетtrue
, что приведёт к включению светодиодов. Если же значение флагаflg_LED
будетfalse
, то светодиоды погаснут.
- Данный блок включает в себя 2 части: получение сигнала с телефона и изменение флага
- Изменение скорости вращения колёс:
- Данный бок включает в себя 2 части: получение сигнала с телефона и изменение флага
flg
; изменение значения переменнойvalSpeed
; - Первая часть начинается с оператора
if
, в условии которого написаноstr == XX
. В зависимости от значенияXX
, которое принимает переменнаяstr
(одно из 11 для дополнительных функций), устанавливается значение флагаflg
(от 10 до 20); - Вторая часть начинается с оператора
if
, в условии которого написаноflg == XX
. В зависимости от значенияXX
, которое принимает переменнаяflg
(одно из 11 для дополнительных функций), устанавливается значение переменнойvalSpeed
(от 5 до 255);
- Данный бок включает в себя 2 части: получение сигнала с телефона и изменение флага
- Вход в режим сопряжения (выполняется при подаче питания на робота):
- Данный блок находится в коде
void setup()
и начинается с оператораif
, в условии которого написаноhc05.begin(softSerial)
. Это условие будет верно, если произошла успешная инициализация с Bluetooth модулем по шине UART, о чём будет выведено сообщение в монитор последовательного порта; - Далее происходит сброс таймера
tmrWait
и идёт проверка условия!hc05.checkConnect() && millis()<tmrWait 60000
в операторе циклаwhile
. До тех пор, пока не произойдёт сопряжения Bluetooth модуля робота и телефона И не истечёт минута(60 сек), модуль будет выполнять пустой цикл. - После цика
while
следует операторif
, в условии которого написаноmillis()<tmrWait 60000
. Условие будет верно, если одно из условие!hc05.checkConnect()
цикла while изменится на противоположное и произойдёт это раньше, чем через 60 секунд от подачи питания на робота. Тогда в монитор последовательного порта будет выведено сообщение о том, что сопряжение произошло с ранее созданной парой из памяти устройства. - Если по истечении минуты не произошло сопряжение ранее созданной пары, то далее следует оператор
else
, который выполняет вызов функцииcreateSlave("BT_CAR", "1234")
объектаhc05
, которая назначает Bluetooth модулю робота роль ведомого с именем “BT_CAR” и PIN-кодом “1234”, разрывает ранее установленную связь с мастером (если она была) и стирает список ранее созданных пар. После этого модуль начинает ожидать подключения мастера, который правильно укажет имя и PIN-код модуля. Об успешном или, наоборот, неудачном выполнении функции будет выведено сообщение в монитор последовательного порта.
- Данный блок находится в коде
Получение данных и работа с Trema-модулем Bluetooth HC-05 осуществляется через функции и методы объекта hc05 библиотеки iarduino_Bluetooth_HC05, с подробным описанием которых можно ознакомиться на странице Wiki – Trema-модуль bluetooth HC-05.
Роботизированная машина. часть iii: bluetooth
Данная статья интересна именно с точки зрения программирования и управления по Bluetooth. Вопросы по сборке подробно освещены в предыдущей статье.
Первое, что необходимо сделать, это дополнить робота модулем HC-06 для общения по Bluetooth. Предварительно потребуется выполнить настройку HC-06. Можно использовать модуль HC-05, но он дороже, а его функционал окажется избыточным для данной задачи.
Следующий шаг – установить на телефон бесплатную программу Bluetooth RC Controller. Программа имитирует основные органы управления пульта и отправляет команды по Bluetooth в текстовом виде.
Как видно, интерфейс программы интуитино понятен. Левый верхний угол – это индикатор соединения с модулем HC-06 (зелёный говорит о том, что соединение установлено). По центру располагается “компас”, который показывает выбранное направление движения, а по переферии располагаются органы управления. Было рашено задействовать абсолютно все для нашего автомобиля. Перечисляем слева направло.
В итоге, почти все кнопки пульта сразу получили прямое соответствие имеющимся командам в машинке. Но вот кусок кода регулировки скоростей пришлось дописывать и отлаживать отдельно. Ещё нетривиально получилось с диагональными направлениями. Они реализованы через разницу скоростей на левом и правом борту.
Программа для прошивки приведена ниже. Опрос сонара в моём варианте отключен, но его можно вернуть. Удивительно, но вариант с остановкой вблизи препятствия мне показался невыносимым при ручном управлении.
*
// 4WD RoboCar // Sonar Bluetooth // 2023-January-20 // v.11a (bluetooth) // (c) 2023, Vladimir E. DRACH // Global variables: int Critical = 14; // Критическое расстояние до препятствия в [см] int BT_Step = 10; // Время движения при получении одного СИМВОЛА по BT [мс] byte randomNumber; // Случайное число byte Cost = 60; // Штраф (назначается за неспособность ехать вперёд) byte Profit = 180; // Очки, т.е. "прибыль", которая плавно растёт при движении вперёд byte velocity = 220; // Скорость моторов [1..255] // Подобрать экспериментально: const byte SPEED_MIN = 100; // минимальная скорость моторов, если меньше - моторы не смогут вращаться const byte SPEED_MAX = 250; // максимальная скорость моторов // для управления по Bluetooth char btCommand = 'S'; // счетчики для определения потери связи с Bluetooth unsigned long btTimer0 = 2000; //Stores the time (in millis since execution started) unsigned long btTimer1 = 0; //Stores the time when the last command was received from the phone // Описываем подключение драйвера двигателей // A - правый борт // В - левый борт int enableB = 3; //~ int pinB2 = 4; // int pinB1 = 5; // int enableA = 6; //~ int pinA1 = 7; //~ int pinA2 = 8; // #define illumination A0 // подключаем составной светодиод // Подключаем ультразвуковой датчик #define trigPin 9 #define echoPin 10 #define light 11 // На этот вывод подключены фары int Buzzer = 12;// Подключаем зуммер 12 (!) // Фоторезистор подключен к АЦП #define PHOTO_SENSOR A5 #define Sweep 8000 // скорость нарастания и убывания частоты #define Woo_wait_sec 2 // сколько с. длится гудение на макс. частоте void setup() { // Определяем направление работы линий pinMode (enableA, OUTPUT); pinMode (pinA1, OUTPUT); pinMode (pinA2, OUTPUT); pinMode (enableB, OUTPUT); pinMode (pinB1, OUTPUT); pinMode (pinB2, OUTPUT); pinMode (13 , OUTPUT); pinMode (light , OUTPUT); pinMode (trigPin, OUTPUT); pinMode (echoPin, INPUT) ; pinMode (illumination, OUTPUT); enableMotors(); SayBeep(); delay(2000); bii(); digitalWrite(13, LOW); // Выключаем встроенный диод Serial.begin(9600); // Инициализация последовательного порта } // Описываем варианты работы моторов void motorAforward() { digitalWrite (pinA1, HIGH); digitalWrite (pinA2, LOW); } void motorBforward() { digitalWrite (pinB1, LOW); digitalWrite (pinB2, HIGH); } void motorAbackward() { digitalWrite (pinA1, LOW); digitalWrite (pinA2, HIGH); } void motorBbackward() { digitalWrite (pinB1, HIGH); digitalWrite (pinB2, LOW); } void motorAstop() { digitalWrite (pinA1, HIGH); digitalWrite (pinA2, HIGH); } void motorBstop() { digitalWrite (pinB1, HIGH); digitalWrite (pinB2, HIGH); } void motorAon() { digitalWrite (enableA, HIGH); } void motorBon() { digitalWrite (enableB, HIGH); } void motorAoff() { digitalWrite (enableA, LOW); } void motorBoff() { digitalWrite (enableB, LOW); } // Описываем варианты движения машины void goForward (int duration) { motorAforward(); motorBforward(); delay (duration); } void goBackward (int duration) { motorAbackward(); motorBbackward(); delay (duration); } void rotateRight (int duration) { motorAbackward(); motorBforward(); delay (duration); } void rotateLeft (int duration) { motorAforward(); motorBbackward(); delay (duration); } void FullStop (int duration) { motorAstop(); motorBstop(); delay (duration); } void disableMotors() { motorAoff(); motorBoff(); } void enableMotors() { motorAon(); motorBon(); // SetVelocity(SPEED_MAX, SPEED_MAX); не очень работает :( } void SetVelocity(int A, int B) { analogWrite (enableA, A); analogWrite (enableB, B); } void CheckLight () { int val = analogRead(PHOTO_SENSOR); if (val < 500) { // Темновато, включаем фары digitalWrite(light, HIGH); } else { // Светло, выключаем фары digitalWrite(light, LOW); } } // Пользуемся УЗ датчиком расстояния int distance() { int duration, distance; digitalWrite(trigPin, HIGH); delayMicroseconds(1000); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = (duration/2) / 29.1; // Переводим в сантиметры return distance; } // Функция запуска автомобиля void launch() { int distance_measured; distance_measured = distance(); // Serial.print(distance_0); // Serial.println(" сантиметров. "); SetVelocity (velocity, velocity); // Движемся вперёд, пока расстояние до преграды > критического [cm] while (distance_measured > Critical) { CheckLight (); // проверяем, не пора ли зажигать фары? goForward(30); // Едем вперёд некоторое время randomNumber = random(1,100); // передёрнули затвор генератора ПСЧ if (Profit < 254 ) { Profit ; }; // Serial.print(Profit); // Serial.println(" очков. "); distance_measured = distance(); } FullStop(100); // Останов, т.к. впереди помеха } void Woo(int freq, long duration){ // первый параметр частота, чем ниже он тем выше частота, второй длительность long time = duration/2/freq; for(long t = 0; t < time; t ) { digitalWrite(Buzzer, HIGH); delayMicroseconds(freq); digitalWrite(Buzzer, LOW); delayMicroseconds(freq); } } void Syren() { for(int i = 0; i <= 1; i ){ // делаем виу-виу 2 раза for(int f = 2000; f >= 100; f=f-40){ // нарастание частоты Woo(f, Sweep); } // Woo(400, Woo_wait_sec*100); // сколько длится гудение на максимальной частоте for(int f = 100; f <= 2000; f=f 40){ // убывание частоты Woo(f, Sweep); } } } void panic() { int distance_tmp; int distance_new = 32000; int angle = 600; // угол поворота, измеряем в [мс] FullStop(100); // Сначала останавливаемся Profit = 255; // Забываем про старые штрафы! // Serial.print(Profit); // Serial.println(" очков. "); digitalWrite(illumination, HIGH); // включаем мигалку Syren (); Syren (); Syren (); delay(1000); // пауза, для отдыха // Выполняем манёвры на высокой скорости (ведь паника!) analogWrite (enableA, velocity); analogWrite (enableB, velocity); // 0) Начинаем крутиться волчком в какую-то сторону: randomNumber = random(1,100); if (randomNumber < 50) { motorAbackward(); motorBforward(); } else { motorBbackward(); motorAforward(); }; // Истерично продолжаем крутиться, пока гудит сирена: Syren (); delay (1000); // Ждём FullStop(1000); delay (100); // Ждём // 1) Ищем хоть какое-то направление, куда вообще можно ехать distance_tmp = distance(); do { rotateRight(angle); // Крутимся FullStop(1000); // Ждём и смотрим вдаль distance_tmp = distance(); } while(distance_tmp < Critical); // повторяем поворот, если расстояние всё ещё мало SayBeep(); // rotateLeft(angle); // Возвращаемся на один шаг // 2) Пытаемся выбрать лучшее направление! do { distance_tmp = distance(); rotateRight(angle); // Крутимся и проверяем дистанцию FullStop(900); // Ждём и смотрим вдаль distance_new = distance(); delay (800); // Ждём и смотрим вдаль } while( distance_new > distance_tmp ); // Не угадали, раньше было лучше, rotateLeft(angle); // поэтому поворачиваемся обратно FullStop(1000); // Восстанавливаем дыхание, успокаиваемся digitalWrite(illumination, LOW); // успокоились, выключаем мигалку delay (410); // Ждём SayBeep(); } void rollBack() { digitalWrite(13, HIGH); // Включаем встроенный диод! // Сначала откатываемся назад на случайное количество шагов randomNumber = random(700,2100); //analogWrite (enableA, 150); //analogWrite (enableB, 189); goBackward(randomNumber); FullStop(200); // Восстанавливаем дыхание, успокаиваемся // Случайным образом выбираем направление поворота: randomNumber = random(1,100); do { // поворачиваемся в выбранную сторону на случайный угол if (randomNumber < 50) { rotateRight(random(450,1300)); } else { rotateLeft(random(350,1400)); }; FullStop(1000); // Выключаем моторы tone(Buzzer, 600, 333); delay (700); // передышка! } while( distance() < Critical ); // повторяем поворот, если расстояние всё ещё мало analogWrite (enableA, velocity); analogWrite (enableB, velocity); FullStop(600); // Восстанавливаем дыхание, успокаиваемся digitalWrite(13, LOW); // Выключаем встроенный диод // SayBeep(); delay (200); // передышка! } void avoid() { CheckLight (); // проверяем, не пора ли зажигать фары? tone(Buzzer, 2100, 110); delay (700); // передышка! // Штрафуем сами себя: if (Profit > Cost) { Profit = Profit - Cost; // Ещё есть возможность оплатить штраф, rollBack(); // тогда откат назад с разворотом // Serial.print(Profit); // Serial.println(" очков. "); } else { // Нельзя штрафовать, всё плохо, значит запутались - паникуем panic(); } } void SayBeep(){ tone(Buzzer, 700, 109); delay(200); tone(Buzzer, 1200, 109); delay(200); tone(Buzzer, 2600, 240); delay(350); noTone(Buzzer); } void boo(){ tone(Buzzer, 600, 200); delay (300); tone(Buzzer, 410, 600); delay (600); } void bii(){ tone(Buzzer, 1611, 90); delay (150); tone(Buzzer, 1611, 90); delay (150); tone(Buzzer, 2111, 400); delay (440); } void SafeForward (int duration){ int distance_measured; distance_measured = distance(); if ( distance_measured > Critical ) {goForward(duration);} else { FullStop(10); boo(); } } void BluetoothControl() { for(int i = 0; i <= 4; i ){ // digitalWrite(illumination, HIGH); // включаем мигалку digitalWrite(13, HIGH); // Включаем встроенный диод! FullStop(80); // digitalWrite(illumination, LOW); // вЫключаем мигалку digitalWrite(13, LOW); // Включаем встроенный диод! FullStop(900); velocity = SPEED_MIN 10; // Выставлям скорость поменьше, т.к. SetVelocity(velocity, velocity); // на Android будет миниальная скорость по умолчанию! } do{ // CheckLight (); // проверяем, не пора ли зажигать фары? aquire(); } while ( 1==1 ); } void SelfControl() { do{ randomNumber = random(2,5); // вхолостую выбираем псевдо-случайное число launch(); // запускаем автомобиль вперёд до встречи с преградой avoid(); // откатываемся от препятствия и как-то поворачиваемся CheckLight (); // проверяем, не пора ли зажигать фары? } while ( 1==1 ); } void aquire () { if (Serial.available() > 0) { btTimer1 = millis(); btCommand = Serial.read(); switch (btCommand){ case 'F': goForward(BT_Step); // Можно просто ехать вперёд наобум, // SafeForward (BT_Step); // а можно включить сонар break; case 'B': goBackward(BT_Step); break; case 'L': rotateLeft(BT_Step); break; case 'R': rotateRight(BT_Step); break; case 'S': FullStop(BT_Step); break; case 'G': // Вперёд, подкручивая вправо if (velocity < SPEED_MAX-99) { SetVelocity((velocity 99), (velocity-99));} else {SetVelocity(SPEED_MAX, SPEED_MIN);} // выставили дифференциал goForward (BT_Step); SetVelocity(velocity, velocity); // вернули скорость break; case 'I': // Вперёд, подкручивая влево if (velocity < SPEED_MAX-99) { SetVelocity((velocity-99), (velocity 99));} else {SetVelocity(SPEED_MIN,SPEED_MAX);} // выставили дифференциал goForward (BT_Step); SetVelocity(velocity, velocity); // вернули скорость break; /*case 'J': //BR if (velocity < SPEED_MAX-80) { SetVelocity((velocity-80), (velocity 80));} else {SetVelocity((velocity-80),SPEED_MAX);} // выставили дифференциал goBackward(BT_Step); SetVelocity(velocity, velocity); // вернули скорость break; case 'H': //BL if (velocity < SPEED_MAX-80) { SetVelocity((velocity 80), (velocity-80));} else {SetVelocity(SPEED_MAX, (velocity-80));} // выставили дифференциал goBackward(BT_Step); SetVelocity(velocity, velocity); // вернули скорость break; */ case 'W': // Зажгли фары digitalWrite(light, HIGH); break; case 'w': // Погасили фары digitalWrite(light, LOW); break; case 'D': // Everything OFF FullStop(100); break; case 'X': // аварийка digitalWrite(illumination, HIGH); break; case 'x': // аварийка digitalWrite(illumination, LOW); break; case 'U': // Back ON digitalWrite(13, HIGH); // Включаем встроенный диод break; case 'u': // Back OFF digitalWrite(13, LOW); // Выключаем встроенный диод break; case 'V': // Пискнуть весело (в оригинале - гудок ВКЛ) bii(); break; case 'v': // Пискнуть грустно (в оригинале (гудок ВЫКЛ) boo(); break; default: // Get SPEED_CURRENT if ( btCommand == 'q' ){ velocity = SPEED_MAX; SetVelocity(velocity, velocity); } else { // Символы '0' - '9' эквивалентны кодам integer 48 - 57 соответственно if ( (btCommand >= 48) && (btCommand <= 57) ) { // Subtracting 48 changes the range from 48-57 to 0-9. // Multiplying by 25 changes the range from 0-9 to 0-225. velocity = SPEED_MIN (btCommand - 48) * 15; SetVelocity(velocity, velocity); } } // else } // switch } // if (Serial.available() > 0) else { btTimer0 = millis(); // Узнаём текущее время (millis since execution started) //Check if it has been 500ms since we received last btCommand. if ((btTimer0 - btTimer1) > 800) { //More than 800 ms have passed since last btCommand received, car is out of range. FullStop(1000); digitalWrite(illumination, HIGH); // включаем мигалку boo (); FullStop(4000); Syren(); FullStop(8000); } } } void loop() { CheckLight (); // проверяем, не пора ли зажигать фары? // Serial.println("System ready..."); if ( distance() > Critical ) { // Путь вперёд свободен, SelfControl(); // робот отправляется в самостоятельное путешествие } else { BluetoothControl(); // Впереди преграда, отдаём управление водителю } }
*
Выбор режима (ручное управление или демо) выполняется в самом начале за счёт проверки расстояния до преграды. При включении машина смотрит в стену – ручное управление по Bluetooth. При включении нет преград перед машиной – самостоятельное движение (демо-режим).
§
В качестве корпуса для прибора была использована коробка от конфет. В неё, на латунных стоечках, встроен микроконтроллерный модуль Arduino UNO, который и будет управлять трёхцветным (сверхъярким) светодиодом. Там же поместился футляр для элементов питания АА, причём отлично подошли уже прилично потрёпанные батарейки от радиоуправляемой машинки – вообще, они были приготовлены к утилизации.
Внезапно родилась идея, а нельзя ли как-то дополнительно управлять светильником? На глаза попался миниатюрный джойстик. Да, именно то, что нужно!
В результате светильник плавно меняет цвета красный-зелёный-голубой, но при нажатии на кнопку джойстика переходит в режим ручного управления (в котром уровень яркости каждого цвета определяется положением рукоятки джойсктика). Повторное нажатие на кнопку приводит к выходу из режима ручного управления.
Текст программы изначально был позаимствован из открытых источников, но затем сильно модифицирован. Получился более мягкий и нежный ночной светильник.
const int pinRed = 11; const int pinGreen = 9; const int pinBlue = 10; const int swPin = A5; const int pinX = A4; // X const int pinY = A3; // Y const int ledPin = 13; boolean ledOn = false; // текущее состояние кнопки boolean prevSw = false; // предыдущее состояние кнопки void setup() { pinMode(ledPin, OUTPUT); pinMode(pinRed, OUTPUT); pinMode(pinGreen, OUTPUT); pinMode(pinBlue, OUTPUT); pinMode(pinX, INPUT); pinMode(pinY, INPUT); pinMode(swPin, INPUT); digitalWrite(swPin, HIGH); // включаем встроенный подтягивающий резистор } boolean isLedOn() { // ОПРЕДЕЛЯЕМ НАЖАТИЕ КНОПКИ if (digitalRead(swPin) == HIGH && prevSw == LOW) { ledOn = !ledOn; prevSw = HIGH; } else prevSw = digitalRead(swPin); digitalWrite(ledPin, ledOn); // включаем светодиод на пине 13 return ledOn; } void CtlMode() { // РЕЖИМ "Ручное управление" int X = analogRead(pinX); // считываем положение джойстика int Y = analogRead(pinY); int BLUE = map(Y, 000, 1023, 0, 255); // маппинг значений int GREEN = map(X, 512, 1023, 0, 255); int RED = map(X, 511, 0, 0, 255); analogWrite(pinRed, RED); // включение каналов R,G,B analogWrite(pinGreen, GREEN); analogWrite(pinBlue, BLUE); } void DemoMode() { // РЕЖИМ "Демонстрация" for (int i=0; i <= 255; i ) { if (isLedOn()) { break; } // при нажатии кнопки выходим из цикла analogWrite(pinRed, i); // работает канал RED analogWrite(pinGreen, 0); analogWrite(pinBlue, 0); delay(7); } for (int i=255; i >= 0; i--) { if (isLedOn()) { break; } // при нажатии кнопки выходим из цикла analogWrite(pinRed, i); // работает канал RED analogWrite(pinGreen, 0); analogWrite(pinBlue, 0); delay(7); } for (int i=0; i <= 255; i ) { if (isLedOn()) { break; } // при нажатии кнопки выходим из цикла analogWrite(pinRed, 0); analogWrite(pinGreen, 0); analogWrite(pinBlue, i); // работает канал BLUE delay(7); } for (int i=255; i >= 0; i--) { if (isLedOn()) { break; } // при нажатии кнопки выходим из цикла analogWrite(pinRed, 0); analogWrite(pinGreen, 0); analogWrite(pinBlue, i); // работает канал BLUE delay(7); } for (int i=0; i <= 255; i ) { if (isLedOn()) { break; } // при нажатии кнопки выходим из цикла analogWrite(pinRed, 0); analogWrite(pinGreen, i); // работает канал GREEN analogWrite(pinBlue, 0); delay(7); } for (int i=255; i >= 0; i--) { if (isLedOn()) { break; } // при нажатии кнопки выходим из цикла analogWrite(pinRed, 0); analogWrite(pinGreen, i); // работает канал GREEN analogWrite(pinBlue, 0); delay(7); } } void loop() { // если нажата кнопка и горит светодиод на пине 13, включаем режим "Ручое управление" if (isLedOn()) CtlMode(); else DemoMode(); // иначе включаем демонстрационный режим }
Когда вся конструкция была опробована на рабочем столе, пришло время встраивать её в квартиру. Диод удачно вписывается в нишу, чтобы световой поток падал на стену, тогда комната будет освещена лишь отражённым светом. Яркости вполне хватает в ночное время, даже с избытком. Провода от коробки до диода удлинены, теперь можно разместить джойстик на прикроватной тумбочке.
§
Акселерометр ADXL345 отлично подходит для измерения статического ускорения свободного падения, если стоит задача по измерению угла наклона, а также подходит для измерения динамического ускорения, обусловленного движением или ударом. Высокая разрешающая способность (4 mg/LSB) позволяет измерять изменение наклона менее 1 градуса.
Собранное устройство работает следующим образом. Акселерометр ADXL345, работающий в диапазоне -16g и имеющий разрешение 13-бит, отправляет информацию о положении на микроконтроллерный модуль по шине I2C (может работать и по SPI). Микроконтроллер декодирует данные, полученные от акселерометра, затем выполняет преобразование, используя специализированную функцию atan2.
Затем формируется управляющее воздействие на серво-мотор. Важной особенностью является отсутствие библиотек для серво-мотора.
Тестовый листинг программы приведён ниже.
/* Подключение акселерометра по шине I2C: VCC: 5V GND: ground SCL: UNO SCL (А5?) SDA: UNO SDA (А4?) */ #include <Servo.h> Servo motor; // Определяем имя серво-привода #include <Math.h> // Вроде, нужна для atan2 #include <Wire.h> // Registers for ADXL345 #define ADXL345_ADDRESS (0xA6 >> 1) // address for device is 8 bit but shift to the // right by 1 bit to make it 7 bit because the // wire library only takes in 7 bit addresses #define ADXL345_REGISTER_XLSB (0x32) float Angle; // угол поворота мотора float prev_Angle; // временная переменная для угла float value; // промежуточное значение Y int accelerometer_data[3]; // void because this only tells the chip to send data to its output register // writes data to the slave's buffer void i2c_write(int address, byte reg, byte data) { // Send output register address Wire.beginTransmission(address); // Connect to device Wire.write(reg); // Send data Wire.write(data); //low byte Wire.endTransmission(); } // void because using pointers // microcontroller reads data from the sensor's input register void i2c_read(int address, byte reg, int count, byte* data) { // Used to read the number of data received int i = 0; // Send input register address Wire.beginTransmission(address); // Connect to device Wire.write(reg); Wire.endTransmission(); // Connect to device Wire.beginTransmission(address); // Request data from slave // Count stands for number of bytes to request Wire.requestFrom(address, count); while (Wire.available()) // slave may send less than requested { char c = Wire.read(); // receive a byte as character data[i] = c; i ; } Wire.endTransmission(); } void init_adxl345() { byte data = 0; i2c_write(ADXL345_ADDRESS, 0x31, 0x0B); // 13-bit mode - 16g i2c_write(ADXL345_ADDRESS, 0x2D, 0x08); // Power register i2c_write(ADXL345_ADDRESS, 0x1E, 0x00); // X i2c_write(ADXL345_ADDRESS, 0x1F, 0x00); // Y i2c_write(ADXL345_ADDRESS, 0x20, 0x05); // Z // Проверка работоспособности i2c_read(ADXL345_ADDRESS, 0X00, 1, &data); if (data == 0xE5) Serial.println("It works well!"); else Serial.println("Failure... :("); } void read_adxl345() { byte bytes[6]; memset(bytes, 0, 6); // Чтение шести байтов из ADXL345 i2c_read(ADXL345_ADDRESS, ADXL345_REGISTER_XLSB, 6, bytes); // Распаковка данных for (int i = 0; i < 3; i) { accelerometer_data[i] = (int)bytes[2 * i] (((int)bytes[2 * i 1]) << 8); } } void setup() { motor.attach(9); // Прицепили серво-мотор (9 or 10) Wire.begin(); Serial.begin(9600); for (int i = 0; i < 3; i) { accelerometer_data[i] = 0; } init_adxl345(); } void react() { // Теперь посылаем данные в мотор // Angle = map(value, 0, 1.0, 0, 180); // диапазон акселерометра отображается на 0..180 или 0..20 градусов Angle = value * 180.0; // Angle = constrain(Angle, 1, 180); // ограничиваем значения диапазоном от 1 до 180 Serial.print("t"); Serial.print("Угол: "); Serial.print(Angle); Serial.println("t"); if (Angle != prev_Angle) { motor.write(Angle); // установить угол поворота мотора prev_Angle = Angle; // запомнить текущий угол } } void loop() { read_adxl345(); Serial.print("ACCEL: "); Serial.print(float(accelerometer_data[0]) * 3.9 / 1000); // 3.9mg/LSB scale factor in 13-bit mode Serial.print("t"); Serial.print(float(accelerometer_data[1]) * 3.9 / 1000); Serial.print("t"); Serial.print(float(accelerometer_data[2]) * 3.9 / 1000); Serial.print("n"); // value = float((accelerometer_data[1]) * 3.9 / 1000); value = atan2(float((accelerometer_data[1]) * 3.9 / 1000), float((accelerometer_data[0]) * 3.9 / 1000)); if ( (value > 0) && (value < 1.0)) { react (); } delay(100); }
§
Типовые характеристики дисплея следующие
Display Capacity: 16 × 2 characters.
Chip Operating Voltage: 4.5 ~ 5.5V.
Working Current: 2.0mA (5.0V).
Optimum working voltage of the module is 5.0V.
Character Size: 2.95 * 4.35 (W * H) mm.
Схема подключения к микроконтроллерным модулям Arduino Uno и Mega 2560 выглядит следующим образом.
Листинг программы для тестов можно использовать следующий
/* __________________ 1602 LCD 8-bit bus control __________________ */ int DI = 12; int RW = 11; int DB[] = {3, 4, 5, 6, 7, 8, 9, 10}; // use array to select pin for bus int Enable = 2; void LcdCommandWrite(int value) { // define all pins int i = 0; for (i = DB[0]; i <= DI; i ) // assign value for bus { digitalWrite(i, value & 01); // for 1602 LCD, it uses D7-D0( not D0-D7) for signal identification; here, itΓÇÖs used for signal inversion. value >>= 1; } digitalWrite(Enable, LOW); delayMicroseconds(1); digitalWrite(Enable, HIGH); delayMicroseconds(1); // wait for 1ms digitalWrite(Enable, LOW); delayMicroseconds(1); // wait for 1ms } void LcdDataWrite(int value) { // initialize all pins int i = 0; digitalWrite(DI, HIGH); digitalWrite(RW, LOW); for (i = DB[0]; i <= DB[7]; i ) { digitalWrite(i, value & 01); value >>= 1; } digitalWrite(Enable, LOW); delayMicroseconds(1); digitalWrite(Enable, HIGH); delayMicroseconds(1); digitalWrite(Enable, LOW); delayMicroseconds(1); // wait for 1ms } void setup (void) { int i = 0; for (i = Enable; i <= DI; i ) { pinMode(i, OUTPUT); } delay(100); // initialize LCD after a brief pause // for LCD control LcdCommandWrite(0x38); // select as 8-bit interface, 2-line display, 5x7 character size delay(64); LcdCommandWrite(0x38); // select as 8-bit interface, 2-line display, 5x7 character size delay(50); LcdCommandWrite(0x38); // select as 8-bit interface, 2-line display, 5x7 character size delay(20); LcdCommandWrite(0x06); // set input mode // auto-increment, no display of shifting delay(20); LcdCommandWrite(0x0E); // display setup // turn on the monitor, cursor on, no flickering delay(20); LcdCommandWrite(0x01); // clear the scree, cursor position returns to 0 delay(100); LcdCommandWrite(0x80); // display setup // turn on the monitor, cursor on, no flickering delay(20); } void loop (void) { LcdCommandWrite(0x01); // clear the screen, cursor position returns to 0 delay(10); LcdCommandWrite(0x80 3); delay(10); // Show welcome message LcdDataWrite('W'); LcdDataWrite('e'); LcdDataWrite('l'); LcdDataWrite('c'); LcdDataWrite('o'); LcdDataWrite('m'); LcdDataWrite('e'); LcdDataWrite(' '); LcdDataWrite('t'); LcdDataWrite('o'); delay(10); LcdCommandWrite(0xc0 1); // set cursor position at second line, second position delay(10); LcdDataWrite('M'); LcdDataWrite('G'); LcdDataWrite('T'); LcdDataWrite('U'); LcdDataWrite('-'); LcdDataWrite('w'); LcdDataWrite('o'); LcdDataWrite('r'); LcdDataWrite('k'); LcdDataWrite('s'); LcdDataWrite('h'); LcdDataWrite('o'); LcdDataWrite('p'); delay(3300); LcdCommandWrite(0x01); // clear the screen, cursor returns to 0 delay(10); LcdDataWrite('I'); LcdDataWrite(' '); LcdDataWrite('a'); LcdDataWrite('m'); LcdDataWrite(' '); LcdDataWrite('a'); LcdDataWrite(' '); LcdDataWrite('b'); LcdDataWrite('o'); LcdDataWrite('s'); LcdDataWrite('s'); delay(3000); LcdCommandWrite(0x02); // set mode as new characters replace old ones, where there is no new ones remain the same delay(10); LcdCommandWrite(0x80 5); // set cursor position at first line, sixth position delay(10); LcdDataWrite('V'); LcdDataWrite('.'); delay(220); LcdDataWrite('E'); delay(220); LcdDataWrite('.'); delay(220); LcdDataWrite(' '); LcdDataWrite('D'); delay(220); LcdDataWrite('r'); delay(220); LcdDataWrite('a'); delay(220); LcdDataWrite('c'); delay(220); LcdDataWrite('h'); delay(5000); }
После прошивки можем наблюдать, что на дисплее отображается текст
В результате видим пример работы жидкокристаллического дисплея
Очевидным недостатком данного решения является крайне интенсивное использование линий ввода-вывода микроконтроллера или микроконтроллерного модуля. Для снижения количества используемых выводов можно рекомендовать подключение по 4-битной шине.