Роботизированная машина. Часть III: Bluetooth

Роботизированная машина. Часть III: Bluetooth Квадрокоптеры

Исходный код программы

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

Затем в функции setup задать направление работы для этих контактов (на вывод данных).

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

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

Далее представлен полный текст программы.

Код программы для «малыша»:

#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 части: получение сигнала с телефона и изменение флага flg; изменение значения переменной valSpeed;
    • Первая часть начинается с оператора if, в условии которого написано str == XX. В зависимости от значения XX, которое принимает переменная str (одно из 11 для дополнительных функций), устанавливается значение флага flg(от 10 до 20);
    • Вторая часть начинается с оператора if, в условии которого написано flg == XX. В зависимости от значения XX, которое принимает переменная flg (одно из 11 для дополнительных функций), устанавливается значение переменнойvalSpeed(от 5 до 255);
  • Вход в режим сопряжения (выполняется при подаче питания на робота):
    • Данный блок находится в коде 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 в текстовом виде.

Роботизированная машина. Часть III: 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, который и будет управлять трёхцветным (сверхъярким) светодиодом. Там же поместился футляр для элементов питания АА, причём отлично подошли уже прилично потрёпанные батарейки от радиоуправляемой машинки – вообще, они были приготовлены к утилизации.

Роботизированная машина. Часть III: Bluetooth

Внезапно родилась идея, а нельзя ли как-то дополнительно управлять светильником? На глаза попался миниатюрный джойстик. Да, именно то, что нужно!

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

Роботизированная машина. Часть III: Bluetooth

Роботизированная машина. Часть III: Bluetooth

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

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 и Uno

 1602 и 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);
}

После прошивки можем наблюдать, что на дисплее отображается текст

 Роботизированная машина. Часть III: Bluetooth

В результате видим пример работы жидкокристаллического дисплея

Роботизированная машина. Часть III: Bluetooth

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

Смотрите про коптеры:  Топ 30 лучших фильмов про роботов и искусственный интеллект. Часть 1
Оцените статью
Радиокоптер.ру
Добавить комментарий