Ему просто жизненно необходимо обмениваться с микроконтроллером информацией. Возникают ситуации, когда нужно вручную, управляя с ПК или ноутбука активировать ту или иную функцию в программе микроконтроллера.
Но давайте ближе к делу. Обмениваться данными с Arduino не так сложно, но вся загвоздка в том, что данные передаются посимвольно, а это очень плохо. В поисках этой проблемы пришлось провести достаточно долгое время, пока на хабрахабре не наткнулся на одну замечательную библиотеку. Автор реализовал в ней функцию приемки чисел, т.е. вы можете отправлять контроллеру числа, состоящие более, чем из одной цифры, и он все отработает корректно. Качайте библиотеку (ссылка), распаковывайте ее в hardwarelibraries, и переходим к практике.
Первым делом напишем скетч, и загрузим его в Arduino (Freeduino)
#include void setup() {
Serial.begin(9600); // устанавливаем скорость порта
PinMode(9, OUTPUT); // устанавливаем 9 ногу как выход для динамика
} void loop()
Long int Number; Serial.print(«Enter number: «);
Number = SerialInput.InputNumber(); // ВВодим число Serial.print(«Result = «);
Serial.println(Number * Number, DEC);
Beep(500);
} void beep(unsigned char delayms){
AnalogWrite(9, 20); // значение должно находится между 0 и 255
// поэкспериментируйте для получения хорошего тона
AnalogWrite(9, 0); // 0 — выключаем пьезо
Delay(delayms); // пауза delayms мс
Что все это значит. Постарался код снабдить подробными комментариями, вроде все должно быть понятно. Данный скетч просит ввести вас любое число, после чего выдает его квадрат, и воспроизводит звуковой сигнал через подсоединенный к 9 пину пьезо-динамик.
И вот, самое интересное — пришло время пробовать. Для коммутации с контроллером я рекомендую использовать бесплатную программу putty . В настройках Connection type выберите Serial и вместо COM1 впишите корректный номер порта (можно подглядеть в среде программирования Arduino меню Tools->Serial Port). Нажимаем Open, и видим в консоли надпись Enter number, вводим любое число (в рамках разумного), жмем Enter, и видим результат.
Все, можно радоваться и прыгать от радости. Естественно все это можно улучшить, например сперва вывести отправить с контроллера в консоль менюшку, в которой подробно расписать команды. Например вводите число 0 — включается светодиодная подсветка, нажимаете 1 — гаснет. Таким образом можете хоть 100500 команд засунуть, лишь бы хватило памяти микроконтроллера (которой так мало). А о том, как расширить доступную память поговорим в следующий раз.
UPD: часть кода порезал парсер движка, поэтому вот исходник
Для связи микроконтроллера с компьютером чаще всего применяют COM-порт. В этой статье мы покажем, как передать команды управления из компьютера и передать данные с контроллера.
Подготовка к работе
Большинство микроконтроллеров обладают множеством портов ввода-вывода. Для связи с ПК наиболее пригоден из них протокол UART. Это протокол последовательной асинхронной передачи данных. Для его преобразования в интерфейс USB на плате есть конвертор USB-RS232 – FT232RL.
Для выполнения примеров их этой статьи вам будет достаточно только Arduino-совместимая плата. Мы используем . Убедитесь, что на вашей плате установлен светодиод, подключенный к 13му выводу и есть кнопка для перезагрузки.
Для примера загрузим на плату код, выводящий таблицу ASCII. ASCII представляет собой кодировку для представления десятичных цифр, латинского и национального алфавитов, знаков препинания и управляющих символов.
int symbol = 33 ; void setup() { Serial. begin(9600 ) ; Serial. println(" ASCII Table ~ Character Map " ) ; } void loop() { Serial. write(symbol) ; Serial. print(" , dec: " ) ; Serial. print(symbol) ; Serial. print(" , hex: " ) ; Serial. print(symbol, HEX) ; Serial. print(" , oct: " ) ; Serial. print(symbol, OCT) ; Serial. print(" , bin: " ) ; Serial. println(symbol, BIN) ; if (symbol = = 126 ) { while (true) { continue ; } } symbol+ + ; }Переменная symbol хранит код символа. Таблица начинается со значения 33 и заканчивается на 126, поэтому изначально переменной symbol присваивается значение 33.
Для запуска работа порта UART служит функция Serial.begin()
. Единственный ее параметр – это скорость. О скорости необходимо договариваться на передающей и приемной стороне заранее, так как протокол передачи асинхронный. В рассматриваемом примере скорость 9600бит/с.
Для записи значения в порт используются три функции:
- Serial.write() – записывает в порт данные в двоичном виде.
- Serial.print() может иметь много значений, но все они служат для вывода информации в удобной для человека форме. Например, если информация, указанная как параметр для передачи, выделена кавычками – терминальная программа выведет ее без изменения. Если вы хотите вывести какое-либо значение в определенной системе исчисления, то необходимо добавить служебное слово: BIN-двоичная, OCT – восьмеричная, DEC – десятичная, HEX – шестнадцатеричная. Например, Serial.print(25,HEX) .
- Serial.println() делает то же, что и Serial.print() , но еще переводит строку после вывода информации.
Для проверки работы программы необходимо, чтобы на компьютере была терминальная программа, принимающая данные из COM-порта. В Arduino IDE уже встроена такая. Для ее вызова выберите в меню Сервис->Монитор порта. Окно этой утилиты очень просто:
Теперь нажмите кнопку перезагрузки. МК перезагрузится и выведет таблицу ASCII:
Обратите внимание на вот эту часть кода:
if (symbol = = 126 ) { while (true) { continue ; } }Она останавливает выполнение программы. Если вы ее исключите – таблица будет выводиться бесконечно.
Для закрепления полученных знаний попробуйте написать бесконечный цикл, который будет раз в секунду отправлять в последовательный порт ваше имя. В вывод добавьте номера шагов и не забудьте переводить строку после имени.
Отправка команд с ПК
Прежде чем этим заниматься, необходимо получить представление относительного того, как работает COM-порт.
В первую очередь весь обмен происходит через буфер памяти. То есть когда вы отправляете что-то с ПК устройству, данные помещаются в некоторый специальный раздел памяти. Как только устройство готово – оно вычитывает данные из буфера. Проверить состояние буфера позволяет функция Serial.avaliable()
. Эта функция возвращает количество байт в буфере. Чтобы вычитать эти байты необходимо воспользоваться функцией Serial.read()
. Рассмотрим работу этих функций на примере:
После того, как код будет загружен в память микроконтроллера, откройте монитор COM-порта. Введите один символ и нажмите Enter. В поле полученных данных вы увидите: “I received: X”
, где вместо X
будет введенный вами символ.
Программа бесконечно крутится в основном цикле. В тот момент, когда в порт записывается байт функция Serial.available() принимает значение 1, то есть выполняется условие Serial.available() > 0
. Далее функция Serial.read()
вычитывает этот байт, тем самым очищая буфер. После чего при помощи уже известных вам функций происходит вывод.
Использование встроенного в Arduino IDE монитора COM-порта имеет некоторые ограничения. При отправке данных из платы в COM-порт вывод можно организовать в произвольном формате. А при отправке из ПК к плате передача символов происходит в соответствии с таблицей ASCII. Это означает, что когда вы вводите, например символ “1”, через COM-порт отправляется в двоичном виде “00110001” (то есть “49” в десятичном виде).
Немного изменим код и проверим это утверждение:
После загрузки, в мониторе порта при отправке “1” вы увидите в ответ: “I received: 110001”. Можете изменить формат вывода и просмотреть, что принимает плата при других символах.
Управление устройством через COM-порт
Очевидно, что по командам с ПК можно управлять любыми функциями микроконтроллера. Загрузите программу, управляющую работой светодиода:
int val = 0 ; void setup() { Serial. begin(9600 ) ; } void loop() { if (Serial. available() > 0 ) { val = Serial. read() ; if (val= = "H" ) digitalWrite(13 , HIGH) ; if (val= = "L" ) digitalWrite(13 , LOW) ; } }При отправке в COM-порт символа “H” происходит зажигание светодиода на 13ом выводе, а при отправке “L” светодиод будет гаснуть.
Если по результатам приема данных из COM-порта вы хотите, чтобы программа в основном цикле выполняла разные действия, можно выполнять проверку условий в основном цикле. Например.
У меня есть устройство, построенное с помощью Arduino uno:
Программное обеспечение Arduino, установленное на Arduino uno
можно управлять с помощью последовательных команд
можно управлять с помощью физических кнопок и датчиков
при изменении любой кнопки/датчика он записывает текущее состояние в последовательный
Если в течение 5 секунд не было отправлено ни одного сообщения, оно отправляет серийное сообщение без изменений
Что нужно:
Используйте ESP8266 для обеспечения моста между текущим программным обеспечением Arduino и MQTT/web
Я могу запрограммировать ESP8266 как веб-сервер, клиент MQTT и т. д. с помощью Arduino IDE или Lua (но я предпочитаю IDE Arduino, так как я могу повторно использовать части кода для генерации/интерпретации связи). Р>
ESP8266 будет обрабатывать все, что требуется для wifi/web/MQTT; без модуля MQTT часть Arduino будет работать автономно, только пульт дистанционного управления будет отсутствовать.
Я хотел бы внести минимальные изменения в код Arduino (или, если возможно, ни один из них). Любые изменения потребуют обширного повторного тестирования, которого я стараюсь избегать.
ESP8266 может отсутствовать в некоторых установках.
Какие варианты я нашел:
- <�Литий> Последовательный литий>
ESP8266 может считывать последовательный выход и быть мостом между сетью/MQTT и последовательным, будет сохранять текущее состояние в памяти, которое будет отправлено по запросу, чтобы избежать опроса устройства каждый раз.
Одним из преимуществ является отсутствие изменений/испытаний кода, необходимых для части Arduino.
Сделайте Arduino мастером I2C и ESP8266 подчиненным (или наоборот) и реализуйте двунаправленную связь. Получил эту идею, прочитав эту тему .
Другая информация о последовательных командах:
Пакет данных (описание команды или состояния) состоит из 1-20 символов с возможным пиком 20 пакетов за 5 секунд и в среднем по одному пакету каждые 3 секунды. Если необходимо, я могу заставить это отправить 5 целых чисел без знака вместо буквенно-цифровых символов.
Если требуется больше, чем I2C/последовательные контакты, я могу перейти на Arduino Mega (так что количество бесплатных контактов не является проблемой).
Есть ли другие варианты для этого? (протоколы, готовые библиотеки для последовательной связи и т. д.). Я пытаюсь не изобретать велосипед.
Спасибо за ваше время!
31 ответы
Большинство учебников I2C сделали каждого Arduino рабом и мастером, но this лучше, потому что каждый Arduino является либо ведущим, либо ведомым (не оба), и переключение не требуется. Это облегчает ситуацию.
I2C лучше, чем серийный, потому что вы можете добавить еще Arduinos в ту же шину.
Я реализовал I2C между двумя Arduinos, и это не сложнее, что чтение/запись на последовательный порт (который вы уже сделали). И я уверен, вы можете обобщить свой код последовательного порта для работы как с последовательным, так и с I2C-сообщением.
Это мой пример (просто доказательство концепции). Ведомый Arduino управляет некоторыми контактами, темп. датчик и сторожевой таймер по приказу мастера Ардуино. Если ведомый не получает бит во времени, он сбрасывает мастер Arduino.
Мастер-код
#include #define CMD_SENSOR 1 #define CMD_PIN_ON 2 #define CMD_PIN_OFF 3 #define CMD_LUMEN 4 #define CMD_BEAT 5 const byte SLAVE_ADDRESS = 42; const byte LED = 13; char buffer; void setup () { Serial.begin(9600); Serial.println("Master"); Wire.begin (); pinMode (LED, OUTPUT); digitalWrite(LED, HIGH); delay(1000); digitalWrite(LED, LOW); Wire.beginTransmission (SLAVE_ADDRESS); Serial.println("Send LED on"); Wire.write (CMD_PIN_ON); Wire.write (2); Wire.write (10); Wire.endTransmission(); int x = Wire.requestFrom(SLAVE_ADDRESS, 1); Serial.print("status="); Serial.println(x); } //end of setup void loop () { Serial.println("."); Wire.beginTransmission (SLAVE_ADDRESS); Wire.write (CMD_SENSOR); Wire.endTransmission(); int x = Wire.requestFrom(SLAVE_ADDRESS, 1); Serial.print("Disponibles = "); Serial.println(x); int temp = (int) Wire.read(); Serial.println(temp); Wire.beginTransmission (SLAVE_ADDRESS); Wire.write (CMD_LUMEN); Wire.endTransmission(); Wire.requestFrom(SLAVE_ADDRESS, 2); int light = Wire.read() << 8 | Wire.read(); Serial.print("Light="); Serial.println(light); Wire.beginTransmission (SLAVE_ADDRESS); Wire.write (CMD_BEAT); Wire.endTransmission(); Wire.requestFrom(SLAVE_ADDRESS, 1); delay (5000); } //end of loop
Ведомый код
/* Esclavo I2C Recibe los siguientes comandos <- 1° byte -> <- 2° byte -> <- 3° byte -> CMD_SENSOR CMD_PIN_ON n° de pin duracion en segundos CMD_PIN_OFF n° de pin CMD_LUMEN CMD_BEAT Cada comando recibe una respuesta, ya sea el valor pedido o un status. */ #include #include typedef struct { int pin; unsigned long off; } PIN_PGMA; /* Lista de pines que se pueden activar via CMD_PIN_ON. */ #define PIN_LUMEN A0 #define PIN_LED 2 #define PIN_RESET 3 PIN_PGMA pgma = { {PIN_LED, 0}, {PIN_RESET, 0} }; const int pgmaSize = sizeof(pgma)/sizeof(PIN_PGMA); #define CMD_SENSOR 1 #define CMD_PIN_ON 2 #define CMD_PIN_OFF 3 #define CMD_LUMEN 4 #define CMD_BEAT 5 #define ST_OK 0 #define ST_BAD_PIN 1 #define ST_TIME_0 2 #define ST_BAD_LEN 3 #define MY_ADDRESS 42 // Maximo tiempo de espera entre comandos CMD_BEAT. Pasado // ese tiempo, se activa el PIN_RESET. // En milisegundos. #define BEAT_INTERVAL 10000 unsigned long lastBeat; // Largo del reset en milisegundos. #define RESET_LENGTH 250 byte cmd = 0; byte status = 0; int thermoDO = 11; int thermoCS = 12; int thermoCLK = 13; MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO); void setup () { Serial.begin(9600); pinMode(PIN_LUMEN, INPUT); analogRead(PIN_LUMEN); for (int i = 0; i < pgmaSize; i++) { pinMode(pgma[i].pin, OUTPUT); digitalWrite(pgma[i].pin, LOW); } lastBeat = millis(); Wire.begin (MY_ADDRESS); Wire.onReceive (receiveCommand); Wire.onRequest (sendAnswer); } void loop() { unsigned long now = millis(); // Baja la linea de RESET si no ha recibido un beat ultimamente. unsigned long diff = now - lastBeat; if (diff > BEAT_INTERVAL) { resetPin(); } // Recorre la lista de pines y apaga aquellos cuyo tiempo termino. for (int i = 0; i < pgmaSize; i++) { if (pgma[i].off > 0 && pgma[i].off <= now) { Serial.print("off pin="); Serial.println(pgma[i].pin); pgma[i].off = 0; digitalWrite(pgma[i].pin, LOW); } } } // called by interrupt service routine when outgoing data is requested void sendAnswer() { byte temp; int lightReading; switch (cmd) { case CMD_SENSOR: temp = thermocouple.readCelsius(); Wire.write(temp); break; case CMD_LUMEN: lightReading = analogRead(PIN_LUMEN); Wire.write(lightReading >> 8); Wire.write(lightReading % 0xFF); break; case CMD_PIN_ON: case CMD_PIN_OFF: case CMD_BEAT: Wire.write(status); status = ST_OK; break; } cmd = 0; } // called by interrupt service routine when incoming data arrives void receiveCommand (int howMany) { cmd = Wire.read (); status = ST_OK; switch (cmd) { case CMD_PIN_ON: cmdPinOn();; break; case CMD_PIN_OFF: cmdPinOff(); break; case CMD_BEAT: lastBeat = millis(); break; } } //end of receiveEvent void cmdPinOff() { if (Wire.available() != 1) { status = ST_BAD_LEN; } else { int pin = Wire.read(); int i = searchPin(pin); if (i < 0) { status = ST_BAD_PIN; } else { pgma[i].off = 0; digitalWrite(pin, LOW); } } } int searchPin(int pin) { int i = pgmaSize - 1; while (i >= 0 && pgma[i].pin != pin) { i--; } return i; } /* * Programa el encendido y duracion del RESET. */ void resetPin() { if (digitalRead(PIN_RESET) == LOW) { unsigned long now = millis(); int i = searchPin(PIN_RESET); pgma[i].off = now + RESET_LENGTH; lastBeat = now; digitalWrite(PIN_RESET, HIGH); } } void cmdPinOn() { if (Wire.available() != 2) { status = ST_BAD_LEN; } else { int pin = Wire.read(); int len = Wire.read(); int i = searchPin(pin); Serial.print("pin="); Serial.print(pin); Serial.print(",index="); Serial.println(i); if (i < 0) { status = ST_BAD_PIN; Serial.println("bad pin"); } else { if (len == 0) { status = ST_TIME_0; Serial.println("ban len"); } else { pgma[i].off = millis() + len * 1000; digitalWrite(pin, HIGH); Serial.println("ok"); } } } }
Загрузим стандартный пример «Physical Pixel» через меню File\Examples\4.Communication\PhysicalPixel. Эта программа ждет данные от компьютера. При получении символа ‘H’ тестовый индикатор загорается, при получении символа ‘L’ – гаснет. Разберем ее исходный код:
int
outputPin =
13
;
//здесь храним номер контакта
int
val;
//здесь будет храниться принятый символ
void
setup()
{
Serial.begin
(9600
)
;
//установка порта на скорость 9600 бит/сек
pinMode(outputPin, OUTPUT)
;
//устанавливаем 13 контакт в режим вывода
}
void
loop()
{
if
(Serial.available
()
)
{
//если есть принятый символ,
val =
Serial.read
()
;
// то читаем его и сохраняем в val
if
(val ==
"H"
)
{
// если принят симовол "H",...
digitalWrite(outputPin, HIGH)
;
// то включаем светодиод
}
if
(val ==
"L"
)
{
// если принят симовол "L",
digitalWrite(outputPin, LOW)
;
// то выключаем светодиод
}
}
}
Обратите внимание на вложенные условия и порядок следования отрывающих и закрывающих фигурных скобок. Для удобства чтения кода программы каждый следующий уровень вложенности сдвинут вправо. Кроме того, редактор помогает читать код – если Вы поставите курсор справа от скобки, он выделит соответствующую ей парную скобку.
Как проверить работу этой программы после того, как Вы загрузите ее в микроконтроллер? Нужно найти способ отправлять символы на COM-порт компьютера, чтобы микроконтроллер принимал и обрабатывал их. Существует множество вариантов решения этой задачи.
Используем встроенный в среду разработки Arduino монитор COM-порта
Это наиболее простой, и понятный начинающим метод.
Монитор COM-порта запускается через меню Tools\Serial Monitor, либо через панель инструментов. В старых версиях ПО монитор был доступен только через панель инструментов: . Вызвав монитор убедитесь, что выбрана та же самая скорость обмена, что и в программе микроконтроллера. Теперь можно вводить любые символы в поле ввода справа, и нажимать кнопку «Send» – введенные символы будут отправлены в порт, и там их примет Ваша программа. Введите там латинскую букву «H», нажмите «Send» – тестовый светодиод загорится. Если послать «L» – погаснет. Кстати, все данные, которые Ваша программа будет посылать на COM-порт будут выводиться в окне снизу.
Используем программу эмуляции терминала HyperTerminal
Это немного более сложный в реализации вариант обмена.
В состав Windows обычно включена программа эмуляции терминала HyperTerminal. В Windows XP ее можно найти в меню Пуск \ Все программы \ Программы \ Стандартные \ Связь \ HyperTerminal. При запуске нужно отказаться от создания подключения, выбрать меню Файл \ Свойства. В появившемся диалоге выбрать свой COM-порт, нажать «Настроить», и настроить параметры связи в соответствии с рисунком:
Можно воспользоваться другим эмулятором терминала - все они обычно имеют подобный функционал и схожие настройки.
Нажмите «OK» в обоих окнах, и попав в основное окно программы, любую клавишу на клавиатуре – HyperTerminal подключится к COM-порту. Теперь все набираемые на клавиатуре символы попадают через COM-порт в микроконтроллер, а все, что отправляет микроконтроллер, попадает на экран. Понажимайте клавиши «H» и «L» (следите за выбранным языком, и регистром) – тестовый светодиод должен загораться и гаснуть.
Напишем собственную программу для ПК!
Этот вариант для настоящих энтузиастов, желающих программировать не только Freeduino, но и ПК. А почему бы и нет? Нам не потребуется изучать детали программирования последовательного порта под Windows, или еще какие-то сложные вещи. Специально для решения подобных простых задач существует язык Processing (http://processing.org), очень похожий синтаксисом и даже средой разработки на программное обеспечение Arduino.
Установите и запустите Processing – Вы увидите среду разработки, похожую на Arduino.
Исходный код программы для языка Processing есть в комментариях ниже основного текста примера Physical Pixel. Здесь он приведен с минимальными изменениями – мы исправили открытие порта, чтобы можно было легко заменить его номер:
import
processing.serial.*
;
Serial port;
void
setup()
{
size(200
, 200
)
;
noStroke()
;
frameRate(10
)
;
port =
new
Serial(this
, "COM5"
, 9600
)
;
// !!! Здесь прописать свой COM-порт!!!
}
boolean
mouseOverRect()
//Возвращает истину, если курсор внутри квадрата
{
return
((mouseX >=
50
)
&&
(mouseX <=
150
)
&&
(mouseY >=
50
)
&
(mouseY <=
150
)
)
;
}
void
draw()
{
background(#222222
)
;
if
(mouseOverRect()
)
// Если курсор внутри квадрата….
{
fill(#BBBBB0)
;
// сменить цвет на поярче
port.write
("H"
)
;
// послать "H" в микроконтроллер
}
else
{
// если не внутри...
fill(#666660
)
;
// сменить цвет на потемнее
port.write
("L"
)
;
// послать "L" в микроконтроллер
}
rect(50
, 50
, 100
, 100
)
;
// нарисовать квадрат
}
Запустите программу (через меню Sketch \ Run) – появится окно с квадратом, при помещении в который курсора мыши, будет загораться светодиод на Freeduino.
Описание языка Processing и его возможностей выходит за рамки этого простого повествования, но во многих примерах для Arduino в комментариях ниже основного текста программы представлен код Processing для ПК, взаимодействующий с Freeduino.
Настало время для объединения этих двух методик, чтобы получить полноценный двухсторонний обмен информацией между Android и Arduino.
Для этого, мы соберем простенький проект с использованием ультразвукового дальномера и пьезо-буззера.
Алгоритм работы
На экране устройства с Android отображается активити с кнопкой "Измерить", при нажатии на которую, на плату Arduino приходит соответствующая команда. Плата Arduino обрабатывает команду и начинает цикл измерений, после чего вычисляется средняя дистанция до препятствия в сантиметрах. Данное расстояние до обьекта, передается обратно в Android-устройство, где отображается в виде текста, а также на ползунке (ProgressBar).
В Android также происходит обработка данных: если расстояние до обьекта меньше 20 см, то происходит передача управляющего сигнала на Arduino для включения буззера. Это естественно можно было бы сделать и в коде Arduino, но для наглядности я возложил эту задачу на плечи Android устройства. В общем получился небольшой парктроник.
Итак, для начала необходимо определится с управляющими командами. Они должны быть одинаково определены и в Arduino и в Android устройстве. Я выбрал следующие числа:
1 - команда разрешения передачи
2 - команда запрета передачи
3 - команда включения буззера
С первыми двумя командами получилось немного запутано, т.к. я не смог заставить Android корректно принимать один посыл с данными (пробовал и в цикле передавать и по времени, но Android упорно не хочет принимать данные, подозреваю, что это связанно с ADB и при использовании Accessory Mode таких проблем быть не должно). Поэтому когда Arduino принял команду 1 (разрешение передачи) он производит замер расстояния и включает беспрерывную передачу данных в цикле. Как только Android принял данные, он передает к Arduino команду 2, чтобы тот остановил передачу данных.
Программа для Arduino
Скетч для Arduino:
#include #include // Adb connection. Connection * connection; // Adb connection. #define COMMAND_SEND_TRUE 1 // команда разрешения передачи #define COMMAND_SEND_FALSE 2 // команда запрета передачи #define COMMAND_PLAY_BEEP 3 // команда включения буззера const int numOfReadings = 10; // кол-во замеров (элементов массива) int readings; // значения измерений в массиве int arrayIndex = 0; // индекс элемента в массиве int total = 0; // всего значений int averageDistance = 0; // средняя дистанция // настройка пинов и переменных для УЗ датчика int echoPin = 2; // DYP_ME007 ECHO pin int initPin = 3; // DYP_ME007 TRIG pin int BeeperPin = 8; // pin буззера unsigned long pulseTime = 0; // длительность пульса в микросекундах unsigned long distance = 0; // расстояние в (см) boolean SendToAndroid = false; void setup() { pinMode(initPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(BeeperPin, OUTPUT); // Буззер // формируем массив for (int thisReading = 0; thisReading < numOfReadings; thisReading++) { readings = 0; } Serial.begin(115200); // Инициализация подсистемы ADB. ADB::init(); // Open an ADB stream to the phone"s shell. Auto-reconnect. Use any unused port number eg:4568 connection = ADB::addConnection("tcp:4568", true, adbEventHandler); } void loop() { if(SendToAndroid == true) makeDimension(); ADB::poll(); // Poll the ADB subsystem. } void adbEventHandler(Connection * connection, adb_eventType event, uint16_t length, uint8_t * data) { if (event == ADB_CONNECTION_RECEIVE) // Если приняли данные { Serial.print("data:"); // Вывод в Serial Monitor для отладки Serial.println(data,DEC); if((data) == COMMAND_SEND_TRUE) SendToAndroid = true; // Флаг, что надо вкл. передачу данных else if ((data) == COMMAND_SEND_FALSE) SendToAndroid = false; //Флаг, что данные приняты и откл. передачу данных else if ((data) == COMMAND_PLAY_BEEP) playBeep(); } else if (event == ADB_CONNECTION_OPEN) Serial.println("ADB connection open"); else if (event == ADB_CONNECTION_CLOSE) Serial.println("ADB connection close"); else { Serial.println(event); } } void makeDimension() { for (int i = 0; i < numOfReadings; i++) { digitalWrite(initPin, HIGH); // посылаем импульс длительностью 10мс delayMicroseconds(10); digitalWrite(initPin, LOW); pulseTime = pulseIn(echoPin, HIGH); // Считываем длительность пришедшего импульса distance = pulseTime/58; // Дистанция = (длит. импульса / 58) см total= total - readings; readings = distance; total= total + readings; arrayIndex = arrayIndex + 1; // После того, как достигли последнего элемента, начинаем сначала if (arrayIndex >= numOfReadings) { arrayIndex = 0; } //Serial.println(distance, DEC); } averageDistance = total / numOfReadings; // вычисляем среднюю дистанцию //Serial.println(averageDistance, DEC); connection->write(2,(uint8_t*)&averageDistance); // Отсылаем 2 байта delay(10); } void playBeep() { for (int j = 0; j < 10; j++) { analogWrite(BeeperPin, 20); delay(50); analogWrite(BeeperPin, 0); delay(150); } }
В самом начале мы определяем 3 константы - это команды для передачи сообщений между устройствами: COMMAND_SEND_TRUE = 1, COMMAND_SEND_FALSE = 2, COMMAND_PLAY_BEEP = 3
Обработчик adbEventHandler() вызывается каждый раз при принятии данных и при наступлении других событий от ADB (открытие и закрытие соединения).
Функция makeDimension() производит 10 замеров расстояний, а затем вычисляет по ним среднее значение, которое через команду connection->write >() 2-мя байтами отправляется в Android устройство.
С функцией playBeep() все просто - она предназначена для проигрывания 10-ти коротких звуков через буззер.
Программа для Android
Наше окно активити будет состоять из следующих ключевых элементов:
кнопка (Button) - для посылки команды измерения расстояния
текстовое поле (TextView) - для отображения полученного расстояния
прогресс-бар (ProgressBar) - для визуального отображения расстояния (максимум - 500 см)
иконка соединения (ImageView) - отображается при активном соединении с Android устройством.
XML файл данного активити см. в прикрепленных файлах
Файл для главного Activity содержит следующий код:
Package com.example.arduino54; import java.io.IOException; import org.microbridge.server.Server; import org.microbridge.server.AbstractServerListener; import com.example.arduino54.R; import android.os.AsyncTask; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Button; public class MainActivity extends Activity { private int Distance = 0; public final String APP_NAME = "arduino54"; public final byte COMMAND_SEND_TRUE = 1; // Команда разрешения передачи public final byte COMMAND_SEND_FALSE = 2; // Команда запрета передачи public final byte COMMAND_PLAY_BEEP = 3; // Команда включения буззера public final int SYS_COMMAND_DATA = 0; // Внутренняя команда: передача данных public final int SYS_COMMAND_CONNECTED = 1; // Внутренняя команда: соединение установлено public final int SYS_COMMAND_DISCONNECTED = 2; // Внутренняя команда: соединение потеряно Server server = null; ImageView connectedImage; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Создаем TCP сервер (на основе сервера MicroBridge LightWeight) try { server = new Server(4568); //Этот же порт необходимо использовать и на ADK-плате server.start(); } catch (IOException e) { Log.e(APP_NAME, "Unable to start TCP server", e); System.exit(-1); } connectedImage = (ImageView) findViewById(R.id.imageConnected); connectedImage.setAlpha(20); Button Button1 = (Button)findViewById(R.id.button1); Button1.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { server.send(new byte {(byte) COMMAND_SEND_TRUE}); //Посылаем данные //Log.d(APP_NAME, "data_send:"+bSend); } catch (IOException e) { Log.e(APP_NAME, "Problem sending TCP message", e); } } }); server.addListener(new AbstractServerListener() { @Override public void onReceive(org.microbridge.server.Client client, byte data) { Log.d(APP_NAME, "data0:"+data+"; data1:"+data); if (data.length<2) Log.e(APP_NAME, "Размер данных менее 2-х байт:"+data.length); else { try { server.send(new byte {(byte) COMMAND_SEND_FALSE}); //Посылаем данные } catch (IOException e) { Log.e(APP_NAME, "Problem sending TCP message", e); } } Distance = ((data << 8) | (data & 0xFF)); // Формируем слово из 2-х байт //Any update to UI can not be carried out in a non UI thread like the one used //for Server. Hence runOnUIThread is used. runOnUiThread(new Runnable() { //@Override public void run() { new UpdateData().execute(Distance,SYS_COMMAND_DATA); } }); } //@Override public void onClientConnect(org.microbridge.server.Server server, org.microbridge.server.Client client){ Log.d(APP_NAME, "ClientConnected"); runOnUiThread(new Runnable() { public void run() { new UpdateData().execute(0,SYS_COMMAND_CONNECTED); } }); } public void onClientDisconnect(org.microbridge.server.Server server, org.microbridge.server.Client client){ Log.d(APP_NAME, "ClientDisconnected"); runOnUiThread(new Runnable() { public void run() { new UpdateData().execute(0,SYS_COMMAND_DISCONNECTED); } }); } }); } @Override protected void onDestroy (){ super.onDestroy(); server.stop(); } class UpdateData extends AsyncTask< Integer, Integer, Integer> { // Called to initiate the background activity @Override protected Integer doInBackground(Integer... ArdState) { if((ArdState < 20) && (ArdState != 0)){ //Если расстояние меньше 20см try { server.send(new byte {(byte) COMMAND_PLAY_BEEP}); } catch (IOException e) { Log.e(APP_NAME, "Problem sending TCP message", e); } } return (ArdState); //Возвращаем в onPostExecute() } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); // Not used in this case } @Override protected void onPostExecute(Integer... result) { Log.d(APP_NAME, "onPostExecute:"+result); Log.d(APP_NAME, "onPostExecute:"+result); if(result == 1){ connectedImage.setAlpha(255); } else if(result == 2){ connectedImage.setAlpha(20); } TextView txt_Distance_Arduino = (TextView) findViewById(R.id.textDistance); txt_Distance_Arduino.setText(String.valueOf(result+" см")); // Выводим на activity дистанцию ProgressBar mProgressBar = (ProgressBar)findViewById(R.id.progressBar1); mProgressBar.setProgress(result); } } }
Здесь мы для класса server определяем метод server.addListener(new AbstractServerListener() {}) , а также: onReceive(), onClientConnect() и onClientDisconnect() который вызывается при получении данных от сервера MicroBridge, при соединении и разьединении.
На кнопке Button1 мы вешаем обработчик события нажатия setOnClickListener() . При нажатии на кнопку вызывается данный метод и посылает на плату Arduino команду COMMAND_SEND_TRUE, по которой Arduino производит измерение расстояния и передачу значения расстояния.
В методе onReceive() , как только мы приняли данные, то мы сразу же отсылаем обратно команду COMMAND_SEND_FALSE, для того, чтобы Arduino выключил передачу пакетов. Об этом алгоритме я писал выше.
Обратите внимание, что для передачи данных отдельному потоку, мы используем внутренние системные команды SYS_COMMAND_DATA, SYS_COMMAND_CONNECTED и SYS_COMMAND_DISCONNECTED . Команды передаются 2-м элементом массива, а в первом элементе содержится измеренное расстояние, полученное от Arduino.
При срабатывании события onClientConnect() , создается новый поток в который передается массив с командой SYS_COMMAND_CONNECTED (в нашем случае 0), и в методе onPostExecute() путем установки значения Alpha в максимальное 255, происходит отображения иконки соединения. При поступлении команды SYS_COMMAND_DISCONNECTED устанавливается Alpha в значение 20, иконка становится блеклой и ее почти не видно, это означает что соединение не установлено. Прозрачность Alpha устанавливается методом setAlpha(int) .
Когда поток принимает данные из метода onReceive, то в методе doInBackground() происходит сравнение условия, и если расстояние не рано нулю и меньше 20 см, то методом server.send() посылается команда COMMAND_PLAY_BEEP для включения буззера на плате Arduino.
В методе onPostExecute() происходит вывод UI элементов для отображения численного значения расстояния и полоски на прогрессбаре.
В прикрепленном файле вы можете скачать проекты для Arduino и Android, а также все необходимые библиотеки