Исходный код программы
Первым шагом программы является инициализация выходных контактов (с помощью драйвера мотора) для подключения моторов.
В функции настройки определите направление действия (для вывода данных) для этих контактов.
Следующим шагом будет чтение данных, которые последовательный порт 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 Карстр[2],i; voidforward() { Цифровая запись (m11,LOW) ; Цифровая запись (m12,LOW) ; digitalWrite(m21,HIGH); digitalWrite(m22,LOW); } voidbackward() { Цифровая запись (m11, LOW) ; digitalWrite(m12,LOW); digitalWrite(m21,LOW); Цифровая запись (m22,HIGH) ; } Недействительна функция lewa(). { Цифровая запись (m11, HIGH) ; Цифровая запись (m12,LOW); Задержка(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); } Отмена установки() { 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”); Дальше( ) ; i=0; } elseif(str[i-1]==’2′) { Serial.println(“Left”); Правый(); 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; } Задержка(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 и arr Route; подача питания на двигатели и задание их направления. Кроме блоков переключения светодиодов, в коде есть и другие блоки: изменение скорости вращения колес; вход в режим сопряжения.
- Получение данных с пульта:
- Этот блок начинается с оператора 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, выполняет код, определяющий, какой двигатель будет запитан (переменная arrSred) и с каким направлением вращения (переменная arrRoute);
- Подача питания на моторы и задание направления их вращения:
- Этот блок начинается с оператора if, условием которого является flgTime > (millis() – 50). Это условие будет истинным в течение 50 миллисекунд после получения сигнала телефона и установит значения переменных arrSpeed и arrRoute на выходах Arduino. Если в течение 50 миллисекунд сигнал не поступает, значение скорости сбрасывается на 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 для дополнительных функций), устанавливается значение переменной flg (от 5 до 255);
- Вход в режим сопряжения (выполняется при подаче питания на робота):
- Этот блок находится в коде void setup() и начинается с оператора if, условием которого является hc05.begin(softSerial). Это условие будет истинным, если инициализация с модулем Bluetooth через шину UART прошла успешно, о чем будет сообщено в монитор последовательного порта;
- Затем перезапускается таймер tmrWait и проверяется условие !hc05.checkConnect() && millis().
Для работы с модулем Trema Bluetooth HC-05 необходимо использовать объект hc05 библиотеки iarduino_Bluetooth_HC05, подробности о которой можно найти в Вики – Модуль Trema Bluetooth HC-05.
Роботизированная машина. часть iii: bluetooth
Статья интересна именно тем, что в ней рассматриваются вопросы программирования и управления Bluetooth. Подробное обсуждение вопросов сборки можно найти в предыдущей статье.
Сначала необходимо присоединить модуль связи HC-06 Bluetooth к роботу. HC-06 должен быть настроен заранее. Использование HC-05 для этой задачи было бы излишним и более дорогостоящим.
Далее необходимо загрузить на телефон бесплатное приложение Bluetooth RC Controller. В этой программе имитируются основные элементы управления пульта дистанционного управления, а команды передаются по Bluetooth в текстовом виде.
Как видите, пользоваться интерфейсом программы очень просто. В левом верхнем углу отображается индикатор соединения с модулем HC-06 (зеленый цвет означает, что соединение установлено). По периферии расположены элементы управления, а компас в центре показывает, в каком направлении двигаться. Мы решили использовать абсолютно все для нашего автомобиля. Перечисляем слева направо.
Поэтому почти все кнопки на пульте дистанционного управления соответствовали непосредственно существующим командам машины. Для управления скоростью пришлось писать отдельно и отлаживать отдельно. Диагональные направления были еще одной нетривиальной вещью, которую мы сделали. Для их реализации используется разница скоростей слева и справа.
Прошивка показана ниже. В моей версии опрос сонара отключен, но его можно включить обратно. Меня очень расстроила невозможность остановиться возле препятствий при ручном управлении.
*
// 4WD RoboCar // Sonar Bluetooth // 2022-January-20 // v.11a (bluetooth) // (c) 2022, 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-битной шины может уменьшить количество используемых выводов.